Resolve RoomId in RoomFlowNode.
This commit is contained in:
parent
09faa6d1c9
commit
235ef2a71b
9 changed files with 98 additions and 51 deletions
|
|
@ -43,17 +43,19 @@ import io.element.android.libraries.architecture.BaseFlowNode
|
|||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncFailure
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
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.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.util.Optional
|
||||
|
|
@ -87,43 +89,72 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
data object Loading : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object JoinRoom : NavTarget
|
||||
data class JoinRoom(val roomId: RoomId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class RoomAliasError(val roomAlias: RoomAlias) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
||||
}
|
||||
|
||||
private var roomMembershipJob: Job? = null
|
||||
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
resolveRoomId()
|
||||
}
|
||||
|
||||
private fun resolveRoomId() {
|
||||
lifecycleScope.launch {
|
||||
when (val i = inputs.roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
client.resolveRoomAlias(i.roomAlias)
|
||||
.onFailure {
|
||||
Timber.e(it, "Failed to resolve room alias")
|
||||
backstack.newRoot(NavTarget.RoomAliasError(i.roomAlias))
|
||||
}
|
||||
.onSuccess {
|
||||
subscribeToRoomInfoFlow(it)
|
||||
}
|
||||
}
|
||||
is RoomIdOrAlias.Id -> {
|
||||
subscribeToRoomInfoFlow(i.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToRoomInfoFlow(roomId: RoomId) {
|
||||
client.getRoomInfoFlow(
|
||||
inputs.roomIdOrAlias
|
||||
roomId
|
||||
).onEach { roomInfo ->
|
||||
Timber.d("Room membership: ${roomInfo.map { it.currentUserMembership }}")
|
||||
val info = roomInfo.getOrNull()
|
||||
if (info?.currentUserMembership == CurrentUserMembership.JOINED) {
|
||||
backstack.newRoot(NavTarget.JoinedRoom(info.id))
|
||||
// When leaving the room from this session only, navigate up.
|
||||
roomMembershipJob?.cancel()
|
||||
roomMembershipJob = roomMembershipObserver.updates
|
||||
.filter { update -> update.roomId == info.id && !update.isUserInRoom }
|
||||
.onEach {
|
||||
navigateUp()
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||
} else {
|
||||
backstack.newRoot(NavTarget.JoinRoom)
|
||||
backstack.newRoot(NavTarget.JoinRoom(roomId))
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
// When leaving the room from this session only, navigate up.
|
||||
roomMembershipObserver.updates
|
||||
.filter { update -> update.roomId == roomId && !update.isUserInRoom }
|
||||
.onEach {
|
||||
navigateUp()
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Loading -> loadingNode(buildContext)
|
||||
NavTarget.JoinRoom -> {
|
||||
val inputs = JoinRoomEntryPoint.Inputs(inputs.roomIdOrAlias, roomDescription = inputs.roomDescription)
|
||||
is NavTarget.JoinRoom -> {
|
||||
val inputs = JoinRoomEntryPoint.Inputs(
|
||||
roomId = navTarget.roomId,
|
||||
roomIdOrAlias = inputs.roomIdOrAlias,
|
||||
roomDescription = inputs.roomDescription,
|
||||
)
|
||||
joinRoomEntryPoint.createNode(this, buildContext, inputs)
|
||||
}
|
||||
is NavTarget.JoinedRoom -> {
|
||||
|
|
@ -131,6 +162,7 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
val inputs = JoinedRoomFlowNode.Inputs(navTarget.roomId, initialElement = inputs.initialElement)
|
||||
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||
}
|
||||
is NavTarget.RoomAliasError -> roomAliasErrorNode(buildContext, navTarget.roomAlias)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +172,15 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun roomAliasErrorNode(buildContext: BuildContext, roomAlias: RoomAlias) = node(buildContext) {
|
||||
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
AsyncFailure(
|
||||
throwable = Exception("Unable to resolve alias ${roomAlias.value}"),
|
||||
onRetry = { resolveRoomId() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
BackstackView(transitionHandler = JumpToEndTransitionHandler())
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.bumble.appyx.core.node.Node
|
|||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
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.RoomIdOrAlias
|
||||
import java.util.Optional
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
|
|||
fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs): Node
|
||||
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val roomDescription: Optional<RoomDescription>,
|
||||
) : NodeInputs
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@ class JoinRoomNode @AssistedInject constructor(
|
|||
private val acceptDeclineInviteView: AcceptDeclineInviteView,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
private val inputs: JoinRoomEntryPoint.Inputs = inputs()
|
||||
private val presenter = presenterFactory.create(inputs.roomIdOrAlias, inputs.roomDescription)
|
||||
private val presenter = presenterFactory.create(
|
||||
inputs.roomId,
|
||||
inputs.roomIdOrAlias,
|
||||
inputs.roomDescription,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import io.element.android.features.invite.api.response.InviteData
|
|||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
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.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
|
|
@ -38,19 +39,24 @@ import kotlinx.coroutines.launch
|
|||
import java.util.Optional
|
||||
|
||||
class JoinRoomPresenter @AssistedInject constructor(
|
||||
@Assisted private val roomId: RoomId,
|
||||
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
|
||||
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
) : Presenter<JoinRoomState> {
|
||||
interface Factory {
|
||||
fun create(roomIdOrAlias: RoomIdOrAlias, roomDescription: Optional<RoomDescription>): JoinRoomPresenter
|
||||
fun create(
|
||||
roomId: RoomId,
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
roomDescription: Optional<RoomDescription>,
|
||||
): JoinRoomPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): JoinRoomState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val roomInfo by matrixClient.getRoomInfoFlow(roomIdOrAlias).collectAsState(initial = Optional.empty())
|
||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||
val contentState by produceState<ContentState>(initialValue = ContentState.Loading(roomIdOrAlias), key1 = roomInfo) {
|
||||
value = when {
|
||||
roomInfo.isPresent -> {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
|||
import io.element.android.libraries.architecture.Presenter
|
||||
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 java.util.Optional
|
||||
|
||||
|
|
@ -37,8 +38,13 @@ object JoinRoomModule {
|
|||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
): JoinRoomPresenter.Factory {
|
||||
return object : JoinRoomPresenter.Factory {
|
||||
override fun create(roomIdOrAlias: RoomIdOrAlias, roomDescription: Optional<RoomDescription>): JoinRoomPresenter {
|
||||
override fun create(
|
||||
roomId: RoomId,
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
roomDescription: Optional<RoomDescription>,
|
||||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = client,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import io.element.android.features.invite.api.response.anAcceptDeclineInviteStat
|
|||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
|
|
@ -49,7 +51,7 @@ class JoinRoomPresenterTest {
|
|||
val presenter = createJoinRoomPresenter()
|
||||
presenter.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID))
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias()))
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown)
|
||||
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
|
||||
}
|
||||
|
|
@ -245,6 +247,7 @@ class JoinRoomPresenterTest {
|
|||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = matrixClient,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
||||
|
|
@ -255,7 +258,7 @@ class JoinRoomPresenterTest {
|
|||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String? = A_ROOM_NAME,
|
||||
topic: String? = "A room about something",
|
||||
alias: String? = "#alias:matrix.org",
|
||||
alias: RoomAlias? = RoomAlias("#alias:matrix.org"),
|
||||
avatarUrl: String? = null,
|
||||
joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN,
|
||||
numberOfMembers: Long = 2L
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ interface MatrixClient : Closeable {
|
|||
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
||||
suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result<String>
|
||||
fun roomMembershipObserver(): RoomMembershipObserver
|
||||
fun getRoomInfoFlow(roomIdOrAlias: RoomIdOrAlias): Flow<Optional<MatrixRoomInfo>>
|
||||
fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<MatrixRoomInfo>>
|
||||
|
||||
fun isMe(userId: UserId?) = userId == sessionId
|
||||
|
||||
|
|
|
|||
|
|
@ -562,33 +562,18 @@ class RustMatrixClient(
|
|||
|
||||
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
|
||||
|
||||
override fun getRoomInfoFlow(roomIdOrAlias: RoomIdOrAlias): Flow<Optional<MatrixRoomInfo>> {
|
||||
override fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<MatrixRoomInfo>> {
|
||||
return flow {
|
||||
val roomId = when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
resolveRoomAlias(roomIdOrAlias.roomAlias)
|
||||
.onFailure {
|
||||
// TODO Get a way to emit an error
|
||||
Timber.e("Unable to resolve room alias ${roomIdOrAlias.roomAlias}")
|
||||
emit(Optional.empty())
|
||||
return@flow
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
is RoomIdOrAlias.Id -> roomIdOrAlias.roomId
|
||||
var room = getRoom(roomId)
|
||||
if (room == null) {
|
||||
emit(Optional.empty())
|
||||
awaitRoom(roomId, INFINITE)
|
||||
room = getRoom(roomId)
|
||||
}
|
||||
if (roomId != null) {
|
||||
var room = getRoom(roomId)
|
||||
if (room == null) {
|
||||
emit(Optional.empty())
|
||||
awaitRoom(roomId, INFINITE)
|
||||
room = getRoom(roomId)
|
||||
}
|
||||
room?.use {
|
||||
room.roomInfoFlow
|
||||
.map { roomInfo -> Optional.of(roomInfo) }
|
||||
.collect(this)
|
||||
}
|
||||
room?.use {
|
||||
room.roomInfoFlow
|
||||
.map { roomInfo -> Optional.of(roomInfo) }
|
||||
.collect(this)
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class FakeMatrixClient(
|
|||
Result.success(it)
|
||||
}
|
||||
|
||||
var getRoomInfoFlowLambda = { _: RoomIdOrAlias ->
|
||||
var getRoomInfoFlowLambda = { _: RoomId ->
|
||||
flowOf<Optional<MatrixRoomInfo>>(Optional.empty())
|
||||
}
|
||||
|
||||
|
|
@ -293,5 +293,5 @@ class FakeMatrixClient(
|
|||
return Result.success(visitedRoomsId)
|
||||
}
|
||||
|
||||
override fun getRoomInfoFlow(roomIdOrAlias: RoomIdOrAlias) = getRoomInfoFlowLambda(roomIdOrAlias)
|
||||
override fun getRoomInfoFlow(roomId: RoomId) = getRoomInfoFlowLambda(roomId)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue