Browse Source

Resolve RoomId in RoomFlowNode.

pull/2713/head
Benoit Marty 5 months ago committed by Benoit Marty
parent
commit
235ef2a71b
  1. 75
      appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt
  2. 2
      features/joinroom/api/src/main/kotlin/io/element/android/features/joinroom/api/JoinRoomEntryPoint.kt
  3. 6
      features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomNode.kt
  4. 10
      features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt
  5. 8
      features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt
  6. 7
      features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt
  7. 2
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
  8. 35
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  9. 4
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

75
appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt

@ -43,17 +43,19 @@ import io.element.android.libraries.architecture.BaseFlowNode @@ -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( @@ -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( @@ -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( @@ -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())

2
features/joinroom/api/src/main/kotlin/io/element/android/features/joinroom/api/JoinRoomEntryPoint.kt

@ -21,6 +21,7 @@ import com.bumble.appyx.core.node.Node @@ -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 { @@ -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

6
features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomNode.kt

@ -37,7 +37,11 @@ class JoinRoomNode @AssistedInject constructor( @@ -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) {

10
features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt

@ -30,6 +30,7 @@ import io.element.android.features.invite.api.response.InviteData @@ -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 @@ -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 -> {

8
features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt

@ -25,6 +25,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription @@ -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 { @@ -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,

7
features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt

@ -23,7 +23,9 @@ import io.element.android.features.invite.api.response.anAcceptDeclineInviteStat @@ -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 { @@ -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 { @@ -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 { @@ -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

2
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt

@ -95,7 +95,7 @@ interface MatrixClient : Closeable { @@ -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

35
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

@ -562,33 +562,18 @@ class RustMatrixClient( @@ -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()
}

4
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

@ -107,7 +107,7 @@ class FakeMatrixClient( @@ -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( @@ -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…
Cancel
Save