Skip to content

Commit 0c63d0c

Browse files
authored
Merge pull request #5432 from element-hq/feature/bma/leaveSpace
Leave space: use SDK API.
2 parents 0bec8f0 + 7299590 commit 0c63d0c

File tree

48 files changed

+599
-207
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+599
-207
lines changed

features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package io.element.android.features.space.impl.leave
1010
import io.element.android.libraries.matrix.api.core.RoomId
1111

1212
sealed interface LeaveSpaceEvents {
13+
data object Retry : LeaveSpaceEvents
1314
data object SelectAllRooms : LeaveSpaceEvents
1415
data object DeselectAllRooms : LeaveSpaceEvents
1516
data class ToggleRoomSelection(val roomId: RoomId) : LeaveSpaceEvents

features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,39 @@ package io.element.android.features.space.impl.leave
99

1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.ui.Modifier
12+
import com.bumble.appyx.core.lifecycle.subscribe
1213
import com.bumble.appyx.core.modality.BuildContext
1314
import com.bumble.appyx.core.node.Node
1415
import com.bumble.appyx.core.plugin.Plugin
1516
import dev.zacsweers.metro.Assisted
1617
import dev.zacsweers.metro.AssistedInject
1718
import io.element.android.annotations.ContributesNode
19+
import io.element.android.features.space.api.SpaceEntryPoint
1820
import io.element.android.features.space.impl.di.SpaceFlowScope
21+
import io.element.android.libraries.architecture.inputs
22+
import io.element.android.libraries.matrix.api.MatrixClient
1923

2024
@ContributesNode(SpaceFlowScope::class)
2125
@AssistedInject
2226
class LeaveSpaceNode(
2327
@Assisted buildContext: BuildContext,
2428
@Assisted plugins: List<Plugin>,
25-
private val presenter: LeaveSpacePresenter,
29+
matrixClient: MatrixClient,
30+
presenterFactory: LeaveSpacePresenter.Factory,
2631
) : Node(buildContext, plugins = plugins) {
32+
private val inputs: SpaceEntryPoint.Inputs = inputs()
33+
private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(inputs.roomId)
34+
private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle)
35+
36+
override fun onBuilt() {
37+
super.onBuilt()
38+
lifecycle.subscribe(
39+
onDestroy = {
40+
leaveSpaceHandle.close()
41+
}
42+
)
43+
}
44+
2745
@Composable
2846
override fun View(modifier: Modifier) {
2947
val state = presenter.present()

features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,92 +8,119 @@
88
package io.element.android.features.space.impl.leave
99

1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.LaunchedEffect
1112
import androidx.compose.runtime.MutableState
12-
import androidx.compose.runtime.collectAsState
1313
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableIntStateOf
1415
import androidx.compose.runtime.mutableStateOf
15-
import androidx.compose.runtime.produceState
1616
import androidx.compose.runtime.remember
1717
import androidx.compose.runtime.rememberCoroutineScope
18-
import dev.zacsweers.metro.Inject
18+
import androidx.compose.runtime.setValue
19+
import dev.zacsweers.metro.Assisted
20+
import dev.zacsweers.metro.AssistedFactory
21+
import dev.zacsweers.metro.AssistedInject
1922
import io.element.android.libraries.architecture.AsyncAction
2023
import io.element.android.libraries.architecture.AsyncData
2124
import io.element.android.libraries.architecture.Presenter
25+
import io.element.android.libraries.architecture.map
2226
import io.element.android.libraries.architecture.runUpdatingState
2327
import io.element.android.libraries.matrix.api.core.RoomId
24-
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
25-
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
28+
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
29+
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceRoom
2630
import kotlinx.collections.immutable.ImmutableList
27-
import kotlinx.collections.immutable.ImmutableSet
2831
import kotlinx.collections.immutable.persistentSetOf
29-
import kotlinx.collections.immutable.toPersistentList
30-
import kotlinx.collections.immutable.toPersistentSet
32+
import kotlinx.collections.immutable.toImmutableList
3133
import kotlinx.coroutines.CoroutineScope
3234
import kotlinx.coroutines.launch
33-
import kotlin.jvm.optionals.getOrNull
3435

35-
@Inject
36+
@AssistedInject
3637
class LeaveSpacePresenter(
37-
private val spaceRoomList: SpaceRoomList,
38+
@Assisted private val leaveSpaceHandle: LeaveSpaceHandle,
3839
) : Presenter<LeaveSpaceState> {
40+
@AssistedFactory
41+
fun interface Factory {
42+
fun create(leaveSpaceHandle: LeaveSpaceHandle): LeaveSpacePresenter
43+
}
44+
45+
data class LeaveSpaceRooms(
46+
val current: LeaveSpaceRoom?,
47+
val others: List<LeaveSpaceRoom>,
48+
)
49+
3950
@Composable
4051
override fun present(): LeaveSpaceState {
4152
val coroutineScope = rememberCoroutineScope()
42-
val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
53+
var retryCount by remember { mutableIntStateOf(0) }
4354
val leaveSpaceAction = remember {
4455
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
4556
}
46-
val selectedRoomIds = remember {
47-
mutableStateOf<ImmutableSet<RoomId>>(persistentSetOf())
57+
var selectedRoomIds by remember {
58+
mutableStateOf<Collection<RoomId>>(setOf())
4859
}
49-
val joinedSpaceRooms by produceState(emptyList()) {
50-
// TODO Get the joined room from the SDK, should also have the isLastAdmin boolean
51-
val rooms = emptyList<SpaceRoom>()
52-
// By default select all rooms
53-
selectedRoomIds.value = rooms.map { it.roomId }.toPersistentSet()
54-
value = rooms
60+
var leaveSpaceRooms by remember {
61+
mutableStateOf<AsyncData<LeaveSpaceRooms>>(AsyncData.Loading())
5562
}
56-
val selectableSpaceRooms by produceState<AsyncData<ImmutableList<SelectableSpaceRoom>>>(
57-
initialValue = AsyncData.Uninitialized,
58-
key1 = joinedSpaceRooms,
59-
key2 = selectedRoomIds.value,
60-
) {
61-
value = AsyncData.Success(
62-
joinedSpaceRooms.map {
63-
SelectableSpaceRoom(
64-
spaceRoom = it,
65-
// TODO Get this value from the SDK
66-
isLastAdmin = false,
67-
isSelected = selectedRoomIds.value.contains(it.roomId),
63+
LaunchedEffect(retryCount) {
64+
val rooms = leaveSpaceHandle.rooms()
65+
val (currentRoom, otherRooms) = rooms.getOrNull()
66+
.orEmpty()
67+
.partition { it.spaceRoom.roomId == leaveSpaceHandle.id }
68+
// By default select all rooms that can be left
69+
selectedRoomIds = otherRooms
70+
.filter { it.isLastAdmin.not() }
71+
.map { it.spaceRoom.roomId }
72+
leaveSpaceRooms = rooms.fold(
73+
onSuccess = {
74+
AsyncData.Success(
75+
LeaveSpaceRooms(
76+
current = currentRoom.firstOrNull(),
77+
others = otherRooms.toImmutableList(),
78+
)
6879
)
69-
}.toPersistentList()
80+
},
81+
onFailure = { AsyncData.Failure(it) }
7082
)
7183
}
84+
var selectableSpaceRooms by remember {
85+
mutableStateOf<AsyncData<ImmutableList<SelectableSpaceRoom>>>(AsyncData.Loading())
86+
}
87+
LaunchedEffect(selectedRoomIds, leaveSpaceRooms) {
88+
selectableSpaceRooms = leaveSpaceRooms.map {
89+
it?.others.orEmpty().map { room ->
90+
SelectableSpaceRoom(
91+
spaceRoom = room.spaceRoom,
92+
isLastAdmin = room.isLastAdmin,
93+
isSelected = selectedRoomIds.contains(room.spaceRoom.roomId),
94+
)
95+
}.toImmutableList()
96+
}
97+
}
7298

7399
fun handleEvents(event: LeaveSpaceEvents) {
74100
when (event) {
101+
LeaveSpaceEvents.Retry -> {
102+
leaveSpaceRooms = AsyncData.Loading()
103+
retryCount += 1
104+
}
75105
LeaveSpaceEvents.DeselectAllRooms -> {
76-
selectedRoomIds.value = persistentSetOf()
106+
selectedRoomIds = persistentSetOf()
77107
}
78108
LeaveSpaceEvents.SelectAllRooms -> {
79-
selectedRoomIds.value = selectableSpaceRooms.dataOrNull()
109+
selectedRoomIds = selectableSpaceRooms.dataOrNull()
80110
.orEmpty()
81111
.filter { it.isLastAdmin.not() }
82112
.map { it.spaceRoom.roomId }
83-
.toPersistentSet()
84113
}
85114
is LeaveSpaceEvents.ToggleRoomSelection -> {
86-
val currentSet = selectedRoomIds.value
87-
selectedRoomIds.value = if (currentSet.contains(event.roomId)) {
88-
currentSet - event.roomId
115+
selectedRoomIds = if (selectedRoomIds.contains(event.roomId)) {
116+
selectedRoomIds - event.roomId
89117
} else {
90-
currentSet + event.roomId
118+
selectedRoomIds + event.roomId
91119
}
92-
.toPersistentSet()
93120
}
94121
LeaveSpaceEvents.LeaveSpace -> coroutineScope.leaveSpace(
95122
leaveSpaceAction = leaveSpaceAction,
96-
selectedRoomIds = selectedRoomIds.value,
123+
selectedRoomIds = selectedRoomIds,
97124
)
98125
LeaveSpaceEvents.CloseError -> {
99126
leaveSpaceAction.value = AsyncAction.Uninitialized
@@ -102,7 +129,8 @@ class LeaveSpacePresenter(
102129
}
103130

104131
return LeaveSpaceState(
105-
spaceName = currentSpace.getOrNull()?.name,
132+
spaceName = leaveSpaceRooms.dataOrNull()?.current?.spaceRoom?.name,
133+
isLastAdmin = leaveSpaceRooms.dataOrNull()?.current?.isLastAdmin == true,
106134
selectableSpaceRooms = selectableSpaceRooms,
107135
leaveSpaceAction = leaveSpaceAction.value,
108136
eventSink = ::handleEvents,
@@ -111,11 +139,10 @@ class LeaveSpacePresenter(
111139

112140
private fun CoroutineScope.leaveSpace(
113141
leaveSpaceAction: MutableState<AsyncAction<Unit>>,
114-
@Suppress("unused") selectedRoomIds: Set<RoomId>,
142+
selectedRoomIds: Collection<RoomId>,
115143
) = launch {
116144
runUpdatingState(leaveSpaceAction) {
117-
// TODO SDK API call to leave all the rooms and space
118-
Result.failure(Exception("Not implemented"))
145+
leaveSpaceHandle.leave(selectedRoomIds.toList())
119146
}
120147
}
121148
}

features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import kotlinx.collections.immutable.ImmutableList
1313

1414
data class LeaveSpaceState(
1515
val spaceName: String?,
16+
val isLastAdmin: Boolean,
1617
val selectableSpaceRooms: AsyncData<ImmutableList<SelectableSpaceRoom>>,
1718
val leaveSpaceAction: AsyncAction<Unit>,
1819
val eventSink: (LeaveSpaceEvents) -> Unit,
@@ -25,7 +26,12 @@ data class LeaveSpaceState(
2526
/**
2627
* True if we should show the quick action to select/deselect all rooms.
2728
*/
28-
val showQuickAction = selectableRooms.isNotEmpty()
29+
val showQuickAction = isLastAdmin.not() && selectableRooms.isNotEmpty()
30+
31+
/**
32+
* True if we should show the leave button.
33+
*/
34+
val showLeaveButton = isLastAdmin.not() && selectableSpaceRooms is AsyncData.Success
2935

3036
/**
3137
* True if there all the selectable rooms are selected.

features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,20 @@ class LeaveSpaceStateProvider : PreviewParameterProvider<LeaveSpaceState> {
105105
aLeaveSpaceState(
106106
selectableSpaceRooms = AsyncData.Failure(Exception("An error")),
107107
),
108+
aLeaveSpaceState(
109+
isLastAdmin = true,
110+
),
108111
)
109112
}
110113

111114
fun aLeaveSpaceState(
112115
spaceName: String? = "Space name",
116+
isLastAdmin: Boolean = false,
113117
selectableSpaceRooms: AsyncData<ImmutableList<SelectableSpaceRoom>> = AsyncData.Uninitialized,
114118
leaveSpaceAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
115119
) = LeaveSpaceState(
116120
spaceName = spaceName,
121+
isLastAdmin = isLastAdmin,
117122
selectableSpaceRooms = selectableSpaceRooms,
118123
leaveSpaceAction = leaveSpaceAction,
119124
eventSink = { }

0 commit comments

Comments
 (0)