Merge pull request #5436 from element-hq/feature/fga/space_list_destroy

Misc : destroy SpaceRoomList
This commit is contained in:
ganfra 2025-10-01 12:21:43 +02:00 committed by GitHub
commit 6b6cf93167
24 changed files with 199 additions and 170 deletions

View file

@ -9,15 +9,15 @@ package io.element.android.x.di
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.appnav.di.RoomComponentFactory import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
@Inject @Inject
class DefaultRoomComponentFactory( class DefaultRoomGraphFactory(
private val sessionGraph: SessionGraph, private val sessionGraph: SessionGraph,
) : RoomComponentFactory { ) : RoomGraphFactory {
override fun create(room: JoinedRoom): Any { override fun create(room: JoinedRoom): Any {
return sessionGraph.roomGraphFactory return sessionGraph.roomGraphFactory
.create(room, room) .create(room, room)

View file

@ -7,18 +7,15 @@
package io.element.android.x.di package io.element.android.x.di
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.GraphExtension import dev.zacsweers.metro.GraphExtension
import dev.zacsweers.metro.Provides import dev.zacsweers.metro.Provides
import io.element.android.libraries.architecture.NodeFactoriesBindings import io.element.android.libraries.architecture.NodeFactoriesBindings
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@GraphExtension(RoomScope::class) @GraphExtension(RoomScope::class)
interface RoomGraph : NodeFactoriesBindings { interface RoomGraph : NodeFactoriesBindings {
@ContributesTo(SessionScope::class)
@GraphExtension.Factory @GraphExtension.Factory
interface Factory { interface Factory {
fun create( fun create(

View file

@ -7,8 +7,6 @@
package io.element.android.x.di package io.element.android.x.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.GraphExtension import dev.zacsweers.metro.GraphExtension
import dev.zacsweers.metro.Provides import dev.zacsweers.metro.Provides
import io.element.android.libraries.architecture.NodeFactoriesBindings import io.element.android.libraries.architecture.NodeFactoriesBindings
@ -19,7 +17,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
interface SessionGraph : NodeFactoriesBindings { interface SessionGraph : NodeFactoriesBindings {
val roomGraphFactory: RoomGraph.Factory val roomGraphFactory: RoomGraph.Factory
@ContributesTo(AppScope::class)
@GraphExtension.Factory @GraphExtension.Factory
interface Factory { interface Factory {
fun create(@Provides matrixClient: MatrixClient): SessionGraph fun create(@Provides matrixClient: MatrixClient): SessionGraph

View file

@ -9,6 +9,6 @@ package io.element.android.appnav.di
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
fun interface RoomComponentFactory { fun interface RoomGraphFactory {
fun create(room: JoinedRoom): Any fun create(room: JoinedRoom): Any
} }

View file

@ -20,7 +20,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.appnav.di.RoomComponentFactory import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.RoomNavigationTarget
import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
@ -56,7 +56,7 @@ class JoinedRoomLoadedFlowNode(
private val sessionCoroutineScope: CoroutineScope, private val sessionCoroutineScope: CoroutineScope,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val activeRoomsHolder: ActiveRoomsHolder, private val activeRoomsHolder: ActiveRoomsHolder,
roomComponentFactory: RoomComponentFactory, roomGraphFactory: RoomGraphFactory,
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>( ) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = when (val input = plugins.filterIsInstance<Inputs>().first().initialElement) { initialElement = when (val input = plugins.filterIsInstance<Inputs>().first().initialElement) {
@ -83,7 +83,7 @@ class JoinedRoomLoadedFlowNode(
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
private val callbacks = plugins.filterIsInstance<Callback>() private val callbacks = plugins.filterIsInstance<Callback>()
override val graph = roomComponentFactory.create(inputs.room) override val graph = roomGraphFactory.create(inputs.room)
init { init {
lifecycle.subscribe( lifecycle.subscribe(

View file

@ -17,7 +17,7 @@ import com.bumble.appyx.navmodel.backstack.activeElement
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.appnav.di.RoomComponentFactory import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.RoomNavigationTarget
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint
@ -70,7 +70,7 @@ class JoinedRoomLoadedFlowNodeTest {
} }
} }
private class FakeRoomComponentFactory : RoomComponentFactory { private class FakeRoomGraphFactory : RoomGraphFactory {
override fun create(room: JoinedRoom): Any { override fun create(room: JoinedRoom): Any {
return Unit return Unit
} }
@ -110,7 +110,7 @@ class JoinedRoomLoadedFlowNodeTest {
roomDetailsEntryPoint = roomDetailsEntryPoint, roomDetailsEntryPoint = roomDetailsEntryPoint,
appNavigationStateService = FakeAppNavigationStateService(), appNavigationStateService = FakeAppNavigationStateService(),
sessionCoroutineScope = this, sessionCoroutineScope = this,
roomComponentFactory = FakeRoomComponentFactory(), roomGraphFactory = FakeRoomGraphFactory(),
matrixClient = FakeMatrixClient(), matrixClient = FakeMatrixClient(),
activeRoomsHolder = activeRoomsHolder, activeRoomsHolder = activeRoomsHolder,
) )

View file

@ -19,7 +19,7 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.appnav.di.RoomComponentFactory import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint
import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
@ -36,7 +36,7 @@ import kotlinx.parcelize.Parcelize
class ChangeRoomMemberRolesRootNode( class ChangeRoomMemberRolesRootNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
roomComponentFactory: RoomComponentFactory, roomGraphFactory: RoomGraphFactory,
) : ParentNode<ChangeRoomMemberRolesRootNode.NavTarget>( ) : ParentNode<ChangeRoomMemberRolesRootNode.NavTarget>(
navModel = PermanentNavModel( navModel = PermanentNavModel(
navTargets = setOf(NavTarget), navTargets = setOf(NavTarget),
@ -54,7 +54,7 @@ class ChangeRoomMemberRolesRootNode(
private val inputs = inputs<Inputs>() private val inputs = inputs<Inputs>()
override val graph = roomComponentFactory.create(inputs.joinedRoom) override val graph = roomGraphFactory.create(inputs.joinedRoom)
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return createNode<ChangeRolesNode>( return createNode<ChangeRolesNode>(

View file

@ -26,7 +26,7 @@ class DefaultChangeRoomMemberRolesEntyPointTest {
ChangeRoomMemberRolesRootNode( ChangeRoomMemberRolesRootNode(
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
roomComponentFactory = { }, roomGraphFactory = { },
) )
} }
val room = FakeJoinedRoom() val room = FakeJoinedRoom()

View file

@ -13,6 +13,7 @@ import android.os.Parcelable
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
@ -22,13 +23,16 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.features.space.impl.di.SpaceFlowGraph
import io.element.android.features.space.impl.leave.LeaveSpaceNode import io.element.android.features.space.impl.leave.LeaveSpaceNode
import io.element.android.features.space.impl.root.SpaceNode import io.element.android.features.space.impl.root.SpaceNode
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.DependencyInjectionGraphOwner
import io.element.android.libraries.di.SessionScope 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.RoomId
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -37,6 +41,8 @@ import kotlinx.parcelize.Parcelize
class SpaceFlowNode( class SpaceFlowNode(
@Assisted val buildContext: BuildContext, @Assisted val buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
matrixClient: MatrixClient,
graphFactory: SpaceFlowGraph.Factory,
) : BaseFlowNode<SpaceFlowNode.NavTarget>( ) : BaseFlowNode<SpaceFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Root, initialElement = NavTarget.Root,
@ -44,9 +50,11 @@ class SpaceFlowNode(
), ),
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
) { ), DependencyInjectionGraphOwner {
private val inputs: SpaceEntryPoint.Inputs = inputs() private val inputs: SpaceEntryPoint.Inputs = inputs()
private val callback = plugins.filterIsInstance<SpaceEntryPoint.Callback>().single() private val callback = plugins.filterIsInstance<SpaceEntryPoint.Callback>().single()
private val spaceRoomList = matrixClient.spaceService.spaceRoomList(inputs.roomId)
override val graph = graphFactory.create(spaceRoomList)
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
@ -56,6 +64,15 @@ class SpaceFlowNode(
data object Leave : NavTarget data object Leave : NavTarget
} }
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
onDestroy = {
spaceRoomList.destroy()
}
)
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.Leave -> { NavTarget.Leave -> {

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.space.impl.di
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.GraphExtension
import dev.zacsweers.metro.Provides
import io.element.android.libraries.architecture.NodeFactoriesBindings
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
@GraphExtension(SpaceFlowScope::class)
interface SpaceFlowGraph : NodeFactoriesBindings {
@ContributesTo(SessionScope::class)
@GraphExtension.Factory
interface Factory {
fun create(@Provides spaceRoomList: SpaceRoomList): SpaceFlowGraph
}
}

View file

@ -0,0 +1,10 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.space.impl.di
abstract class SpaceFlowScope private constructor()

View file

@ -15,20 +15,15 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowScope
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class) @ContributesNode(SpaceFlowScope::class)
@AssistedInject @AssistedInject
class LeaveSpaceNode( class LeaveSpaceNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: LeaveSpacePresenter.Factory, private val presenter: LeaveSpacePresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
private val inputs: SpaceEntryPoint.Inputs = inputs()
private val presenter = presenterFactory.create(inputs)
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()

View file

@ -15,17 +15,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.architecture.runUpdatingState
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.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.persistentSetOf
@ -35,18 +32,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@AssistedInject @Inject
class LeaveSpacePresenter( class LeaveSpacePresenter(
@Assisted private val inputs: SpaceEntryPoint.Inputs, private val spaceRoomList: SpaceRoomList,
matrixClient: MatrixClient,
) : Presenter<LeaveSpaceState> { ) : Presenter<LeaveSpaceState> {
@AssistedFactory
fun interface Factory {
fun create(inputs: SpaceEntryPoint.Inputs): LeaveSpacePresenter
}
private val spaceRoomList = matrixClient.spaceService.spaceRoomList(inputs.roomId)
@Composable @Composable
override fun present(): LeaveSpaceState { override fun present(): LeaveSpaceState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()

View file

@ -18,36 +18,33 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowScope
import io.element.android.libraries.androidutils.R import io.element.android.libraries.androidutils.R
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient 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.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ContributesNode(SessionScope::class) @ContributesNode(SpaceFlowScope::class)
@AssistedInject @AssistedInject
class SpaceNode( class SpaceNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: SpacePresenter.Factory, private val presenter: SpacePresenter,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val spaceRoomList: SpaceRoomList,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin { interface Callback : Plugin {
fun onOpenRoom(roomId: RoomId, viaParameters: List<String>) fun onOpenRoom(roomId: RoomId, viaParameters: List<String>)
fun onLeaveSpace() fun onLeaveSpace()
} }
private val inputs: SpaceEntryPoint.Inputs = inputs()
private val callback = plugins.filterIsInstance<Callback>().single() private val callback = plugins.filterIsInstance<Callback>().single()
private val presenter = presenterFactory.create(inputs)
private fun onShareRoom(context: Context) = lifecycleScope.launch { private fun onShareRoom(context: Context) = lifecycleScope.launch {
matrixClient.getRoom(inputs.roomId)?.use { room -> matrixClient.getRoom(spaceRoomList.roomId)?.use { room ->
room.getPermalink() room.getPermalink()
.onSuccess { permalink -> .onSuccess { permalink ->
context.startSharePlainTextIntent( context.startSharePlainTextIntent(

View file

@ -13,11 +13,8 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
@ -31,19 +28,12 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@AssistedInject @Inject
class SpacePresenter( class SpacePresenter(
@Assisted private val inputs: SpaceEntryPoint.Inputs, private val spaceRoomList: SpaceRoomList,
private val client: MatrixClient, private val client: MatrixClient,
private val seenInvitesStore: SeenInvitesStore, private val seenInvitesStore: SeenInvitesStore,
) : Presenter<SpaceState> { ) : Presenter<SpaceState> {
@AssistedFactory
fun interface Factory {
fun create(inputs: SpaceEntryPoint.Inputs): SpacePresenter
}
private val spaceRoomList = client.spaceService.spaceRoomList(inputs.roomId)
@Composable @Composable
override fun present(): SpaceState { override fun present(): SpaceState {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {

View file

@ -12,8 +12,12 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.features.space.impl.di.FakeSpaceFlowGraph
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode import io.element.android.tests.testutils.node.TestParentNode
import org.junit.Rule import org.junit.Rule
@ -34,6 +38,12 @@ class DefaultSpaceEntryPointTest {
SpaceFlowNode( SpaceFlowNode(
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
matrixClient = FakeMatrixClient(
spaceService = FakeSpaceService(
spaceRoomListResult = { _: RoomId -> FakeSpaceRoomList(A_ROOM_ID) }
)
),
graphFactory = FakeSpaceFlowGraph.Factory
) )
} }
val callback = object : SpaceEntryPoint.Callback { val callback = object : SpaceEntryPoint.Callback {

View file

@ -0,0 +1,25 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.space.impl.di
import com.bumble.appyx.core.node.Node
import io.element.android.libraries.architecture.AssistedNodeFactory
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import kotlin.reflect.KClass
class FakeSpaceFlowGraph : SpaceFlowGraph {
object Factory : SpaceFlowGraph.Factory {
override fun create(spaceRoomList: SpaceRoomList): SpaceFlowGraph {
return FakeSpaceFlowGraph()
}
}
override fun nodeFactories(): Map<KClass<out Node>, AssistedNodeFactory<*>> {
return emptyMap()
}
}

View file

@ -10,15 +10,11 @@
package io.element.android.features.space.impl.leave package io.element.android.features.space.impl.leave
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SPACE_NAME import io.element.android.libraries.matrix.test.A_SPACE_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
import io.element.android.libraries.previewutils.room.aSpaceRoom import io.element.android.libraries.previewutils.room.aSpaceRoom
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -29,15 +25,7 @@ import org.junit.Test
class LeaveSpacePresenterTest { class LeaveSpacePresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createLeaveSpacePresenter( val presenter = createLeaveSpacePresenter()
matrixClient = FakeMatrixClient(
spaceService = FakeSpaceService(
spaceRoomListResult = {
FakeSpaceRoomList()
},
),
),
)
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
assertThat(state.spaceName).isNull() assertThat(state.spaceName).isNull()
@ -51,11 +39,7 @@ class LeaveSpacePresenterTest {
fun `present - current space name`() = runTest { fun `present - current space name`() = runTest {
val fakeSpaceRoomList = FakeSpaceRoomList() val fakeSpaceRoomList = FakeSpaceRoomList()
val presenter = createLeaveSpacePresenter( val presenter = createLeaveSpacePresenter(
matrixClient = FakeMatrixClient( spaceRoomList = fakeSpaceRoomList,
spaceService = FakeSpaceService(
spaceRoomListResult = { fakeSpaceRoomList },
),
),
) )
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
@ -71,12 +55,10 @@ class LeaveSpacePresenterTest {
} }
private fun createLeaveSpacePresenter( private fun createLeaveSpacePresenter(
inputs: SpaceEntryPoint.Inputs = SpaceEntryPoint.Inputs(A_ROOM_ID), spaceRoomList: SpaceRoomList = FakeSpaceRoomList(),
matrixClient: MatrixClient = FakeMatrixClient(),
): LeaveSpacePresenter { ): LeaveSpacePresenter {
return LeaveSpacePresenter( return LeaveSpacePresenter(
inputs = inputs, spaceRoomList = spaceRoomList,
matrixClient = matrixClient,
) )
} }
} }

View file

@ -12,13 +12,10 @@ package io.element.android.features.space.impl.root
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.features.invite.test.InMemorySeenInvitesStore
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
import io.element.android.libraries.previewutils.room.aSpaceRoom import io.element.android.libraries.previewutils.room.aSpaceRoom
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
@ -33,17 +30,8 @@ class SpacePresenterTest {
val paginateResult = lambdaRecorder<Result<Unit>> { val paginateResult = lambdaRecorder<Result<Unit>> {
Result.success(Unit) Result.success(Unit)
} }
val presenter = createSpacePresenter( val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
client = FakeMatrixClient( val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
spaceService = FakeSpaceService(
spaceRoomListResult = {
FakeSpaceRoomList(
paginateResult = paginateResult,
)
},
),
),
)
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
assertThat(state.currentSpace).isNull() assertThat(state.currentSpace).isNull()
@ -61,17 +49,8 @@ class SpacePresenterTest {
val paginateResult = lambdaRecorder<Result<Unit>> { val paginateResult = lambdaRecorder<Result<Unit>> {
Result.success(Unit) Result.success(Unit)
} }
val presenter = createSpacePresenter( val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
client = FakeMatrixClient( val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
spaceService = FakeSpaceService(
spaceRoomListResult = {
FakeSpaceRoomList(
paginateResult = paginateResult,
)
},
),
),
)
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
advanceUntilIdle() advanceUntilIdle()
@ -84,25 +63,20 @@ class SpacePresenterTest {
@Test @Test
fun `present - has more to load value`() = runTest { fun `present - has more to load value`() = runTest {
val fakeSpaceRoomList = FakeSpaceRoomList( val paginateResult = lambdaRecorder<Result<Unit>> {
paginateResult = { Result.success(Unit) }, Result.success(Unit)
) }
val presenter = createSpacePresenter( val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
client = FakeMatrixClient( val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
spaceService = FakeSpaceService(
spaceRoomListResult = { fakeSpaceRoomList },
),
),
)
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
advanceUntilIdle() advanceUntilIdle()
assertThat(state.hasMoreToLoad).isTrue() assertThat(state.hasMoreToLoad).isTrue()
fakeSpaceRoomList.emitPaginationStatus( spaceRoomList.emitPaginationStatus(
SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = false) SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = false)
) )
assertThat(awaitItem().hasMoreToLoad).isFalse() assertThat(awaitItem().hasMoreToLoad).isFalse()
fakeSpaceRoomList.emitPaginationStatus( spaceRoomList.emitPaginationStatus(
SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = true) SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = true)
) )
assertThat(awaitItem().hasMoreToLoad).isTrue() assertThat(awaitItem().hasMoreToLoad).isTrue()
@ -111,56 +85,46 @@ class SpacePresenterTest {
@Test @Test
fun `present - current space value`() = runTest { fun `present - current space value`() = runTest {
val fakeSpaceRoomList = FakeSpaceRoomList( val paginateResult = lambdaRecorder<Result<Unit>> {
paginateResult = { Result.success(Unit) }, Result.success(Unit)
) }
val presenter = createSpacePresenter( val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
client = FakeMatrixClient( val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
spaceService = FakeSpaceService(
spaceRoomListResult = { fakeSpaceRoomList },
),
),
)
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
advanceUntilIdle() advanceUntilIdle()
assertThat(state.currentSpace).isNull() assertThat(state.currentSpace).isNull()
val aSpace = aSpaceRoom() val aSpace = aSpaceRoom()
fakeSpaceRoomList.emitCurrentSpace(aSpace) spaceRoomList.emitCurrentSpace(aSpace)
assertThat(awaitItem().currentSpace).isEqualTo(aSpace) assertThat(awaitItem().currentSpace).isEqualTo(aSpace)
} }
} }
@Test @Test
fun `present - children value`() = runTest { fun `present - children value`() = runTest {
val fakeSpaceRoomList = FakeSpaceRoomList( val paginateResult = lambdaRecorder<Result<Unit>> {
paginateResult = { Result.success(Unit) }, Result.success(Unit)
) }
val presenter = createSpacePresenter( val spaceRoomList = FakeSpaceRoomList(paginateResult = paginateResult)
client = FakeMatrixClient( val presenter = createSpacePresenter(spaceRoomList = spaceRoomList)
spaceService = FakeSpaceService(
spaceRoomListResult = { fakeSpaceRoomList },
),
),
)
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
advanceUntilIdle() advanceUntilIdle()
assertThat(state.children).isEmpty() assertThat(state.children).isEmpty()
val aSpace = aSpaceRoom() val aSpace = aSpaceRoom()
fakeSpaceRoomList.emitSpaceRooms(listOf(aSpace)) spaceRoomList.emitSpaceRooms(listOf(aSpace))
assertThat(awaitItem().children).containsExactly(aSpace) assertThat(awaitItem().children).containsExactly(aSpace)
} }
} }
private fun createSpacePresenter( private fun createSpacePresenter(
inputs: SpaceEntryPoint.Inputs = SpaceEntryPoint.Inputs(A_ROOM_ID),
client: MatrixClient = FakeMatrixClient(), client: MatrixClient = FakeMatrixClient(),
spaceRoomList: SpaceRoomList = FakeSpaceRoomList(),
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
): SpacePresenter { ): SpacePresenter {
return SpacePresenter( return SpacePresenter(
inputs = inputs,
client = client, client = client,
spaceRoomList = spaceRoomList,
seenInvitesStore = seenInvitesStore, seenInvitesStore = seenInvitesStore,
) )
} }

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.api.spaces package io.element.android.libraries.matrix.api.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.util.Optional import java.util.Optional
@ -17,9 +18,13 @@ interface SpaceRoomList {
data class Idle(val hasMoreToLoad: Boolean) : PaginationStatus data class Idle(val hasMoreToLoad: Boolean) : PaginationStatus
} }
val roomId: RoomId
val currentSpaceFlow: StateFlow<Optional<SpaceRoom>> val currentSpaceFlow: StateFlow<Optional<SpaceRoom>>
val spaceRoomsFlow: Flow<List<SpaceRoom>> val spaceRoomsFlow: Flow<List<SpaceRoom>>
val paginationStatusFlow: StateFlow<PaginationStatus> val paginationStatusFlow: StateFlow<PaginationStatus>
suspend fun paginate(): Result<Unit> suspend fun paginate(): Result<Unit>
fun destroy()
} }

View file

@ -8,26 +8,31 @@
package io.element.android.libraries.matrix.impl.spaces package io.element.android.libraries.matrix.impl.spaces
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
import java.util.Optional import java.util.Optional
import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList
class RustSpaceRoomList( class RustSpaceRoomList(
override val roomId: RoomId,
private val innerProvider: suspend () -> InnerSpaceRoomList, private val innerProvider: suspend () -> InnerSpaceRoomList,
sessionCoroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
spaceRoomMapper: SpaceRoomMapper, spaceRoomMapper: SpaceRoomMapper,
) : SpaceRoomList { ) : SpaceRoomList {
private val inner = CompletableDeferred<InnerSpaceRoomList>() private val innerCompletable = CompletableDeferred<InnerSpaceRoomList>()
override val currentSpaceFlow = MutableStateFlow<Optional<SpaceRoom>>(Optional.empty()) override val currentSpaceFlow = MutableStateFlow<Optional<SpaceRoom>>(Optional.empty())
@ -41,37 +46,45 @@ class RustSpaceRoomList(
) )
init { init {
sessionCoroutineScope.launch { coroutineScope.launch {
inner.complete(innerProvider()) val inner = innerProvider()
} innerCompletable.complete(inner)
sessionCoroutineScope.launch {
inner.await().paginationStateFlow() inner.paginationStateFlow()
.onEach { paginationStatus -> .onEach { paginationStatus ->
paginationStatusFlow.emit(paginationStatus.into()) paginationStatusFlow.emit(paginationStatus.into())
} }
.collect() .launchIn(this)
}
sessionCoroutineScope.launch { inner.spaceListUpdateFlow()
inner.await().spaceListUpdateFlow()
.onEach { updates -> .onEach { updates ->
spaceListUpdateProcessor.postUpdates(updates) spaceListUpdateProcessor.postUpdates(updates)
} }
.collect() .launchIn(this)
}
sessionCoroutineScope.launch { inner.spaceUpdateFlow()
inner.await().spaceUpdateFlow()
.map { space -> space.map(spaceRoomMapper::map) } .map { space -> space.map(spaceRoomMapper::map) }
.onEach { space -> .onEach { space ->
currentSpaceFlow.emit(space) currentSpaceFlow.emit(space)
} }
.collect() .launchIn(this)
} }
} }
override suspend fun paginate(): Result<Unit> { override suspend fun paginate(): Result<Unit> {
return runCatchingExceptions { return runCatchingExceptions {
inner.await().paginate() innerCompletable.await().paginate()
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun destroy() {
Timber.d("Destroying SpaceRoomList $roomId")
coroutineScope.cancel()
try {
innerCompletable.getCompleted().destroy()
} catch (_: Exception) {
// Ignore, we just want to make sure it's completed
} }
} }

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.impl.spaces package io.element.android.libraries.matrix.impl.spaces
import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoom
@ -54,9 +55,11 @@ class RustSpaceService(
} }
override fun spaceRoomList(id: RoomId): SpaceRoomList { override fun spaceRoomList(id: RoomId): SpaceRoomList {
val childCoroutineScope = sessionCoroutineScope.childScope(sessionDispatcher, "SpaceRoomListScope-$this")
return RustSpaceRoomList( return RustSpaceRoomList(
roomId = id,
innerProvider = { innerSpaceService.spaceRoomList(id.value) }, innerProvider = { innerSpaceService.spaceRoomList(id.value) },
sessionCoroutineScope = sessionCoroutineScope, coroutineScope = childCoroutineScope,
spaceRoomMapper = spaceRoomMapper, spaceRoomMapper = spaceRoomMapper,
) )
} }

View file

@ -11,9 +11,11 @@ package io.element.android.libraries.matrix.impl.spaces
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSpaceRoom import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSpaceRoom
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSpaceRoomList import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSpaceRoomList
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -84,13 +86,15 @@ class RustSpaceRoomListTest {
} }
private fun TestScope.createRustSpaceRoomList( private fun TestScope.createRustSpaceRoomList(
roomId: RoomId = A_ROOM_ID,
innerSpaceRoomList: InnerSpaceRoomList = FakeFfiSpaceRoomList(), innerSpaceRoomList: InnerSpaceRoomList = FakeFfiSpaceRoomList(),
innerProvider: suspend () -> InnerSpaceRoomList = { innerSpaceRoomList }, innerProvider: suspend () -> InnerSpaceRoomList = { innerSpaceRoomList },
spaceRoomMapper: SpaceRoomMapper = SpaceRoomMapper(), spaceRoomMapper: SpaceRoomMapper = SpaceRoomMapper(),
): RustSpaceRoomList { ): RustSpaceRoomList {
return RustSpaceRoomList( return RustSpaceRoomList(
roomId = roomId,
innerProvider = innerProvider, innerProvider = innerProvider,
sessionCoroutineScope = backgroundScope, coroutineScope = backgroundScope,
spaceRoomMapper = spaceRoomMapper, spaceRoomMapper = spaceRoomMapper,
) )
} }

View file

@ -7,8 +7,10 @@
package io.element.android.libraries.matrix.test.spaces package io.element.android.libraries.matrix.test.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -18,6 +20,7 @@ import kotlinx.coroutines.flow.asStateFlow
import java.util.Optional import java.util.Optional
class FakeSpaceRoomList( class FakeSpaceRoomList(
override val roomId: RoomId = A_ROOM_ID,
initialSpaceFlowValue: SpaceRoom? = null, initialSpaceFlowValue: SpaceRoom? = null,
initialSpaceRoomsValue: List<SpaceRoom> = emptyList(), initialSpaceRoomsValue: List<SpaceRoom> = emptyList(),
initialSpaceRoomList: SpaceRoomList.PaginationStatus = SpaceRoomList.PaginationStatus.Loading, initialSpaceRoomList: SpaceRoomList.PaginationStatus = SpaceRoomList.PaginationStatus.Loading,
@ -47,4 +50,8 @@ class FakeSpaceRoomList(
override suspend fun paginate(): Result<Unit> = simulateLongTask { override suspend fun paginate(): Result<Unit> = simulateLongTask {
paginateResult() paginateResult()
} }
override fun destroy() {
// No op
}
} }