Improve LeaveSpacePresenter and add a retry mechanism if loading the rooms fails.
This commit is contained in:
parent
2c3e4a45e4
commit
b8e046cf4d
4 changed files with 57 additions and 35 deletions
|
|
@ -10,6 +10,7 @@ package io.element.android.features.space.impl.leave
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
sealed interface LeaveSpaceEvents {
|
sealed interface LeaveSpaceEvents {
|
||||||
|
data object Retry : LeaveSpaceEvents
|
||||||
data object SelectAllRooms : LeaveSpaceEvents
|
data object SelectAllRooms : LeaveSpaceEvents
|
||||||
data object DeselectAllRooms : LeaveSpaceEvents
|
data object DeselectAllRooms : LeaveSpaceEvents
|
||||||
data class ToggleRoomSelection(val roomId: RoomId) : LeaveSpaceEvents
|
data class ToggleRoomSelection(val roomId: RoomId) : LeaveSpaceEvents
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@
|
||||||
package io.element.android.features.space.impl.leave
|
package io.element.android.features.space.impl.leave
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
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 androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -26,10 +27,9 @@ import io.element.android.libraries.architecture.runUpdatingState
|
||||||
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.LeaveSpaceHandle
|
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
|
||||||
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceRoom
|
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceRoom
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toPersistentSet
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -42,43 +42,55 @@ class LeaveSpacePresenter(
|
||||||
fun create(leaveSpaceHandle: LeaveSpaceHandle): LeaveSpacePresenter
|
fun create(leaveSpaceHandle: LeaveSpaceHandle): LeaveSpacePresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class LeaveSpaceRooms(
|
||||||
|
val current: LeaveSpaceRoom?,
|
||||||
|
val others: List<LeaveSpaceRoom>,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): LeaveSpaceState {
|
override fun present(): LeaveSpaceState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var currentSpace: LeaveSpaceRoom? by remember { mutableStateOf(null) }
|
var retryCount by remember { mutableIntStateOf(0) }
|
||||||
val leaveSpaceAction = remember {
|
val leaveSpaceAction = remember {
|
||||||
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
val selectedRoomIds = remember {
|
var selectedRoomIds by remember {
|
||||||
mutableStateOf<ImmutableSet<RoomId>>(persistentSetOf())
|
mutableStateOf<Collection<RoomId>>(setOf())
|
||||||
}
|
}
|
||||||
val leaveSpaceRooms by produceState(AsyncData.Loading()) {
|
var leaveSpaceRooms by remember {
|
||||||
|
mutableStateOf<AsyncData<LeaveSpaceRooms>>(AsyncData.Loading())
|
||||||
|
}
|
||||||
|
LaunchedEffect(retryCount) {
|
||||||
val rooms = leaveSpaceHandle.rooms()
|
val rooms = leaveSpaceHandle.rooms()
|
||||||
val (currentRoom, otherRooms) = rooms.getOrNull()
|
val (currentRoom, otherRooms) = rooms.getOrNull()
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.partition { it.spaceRoom.roomId == leaveSpaceHandle.id }
|
.partition { it.spaceRoom.roomId == leaveSpaceHandle.id }
|
||||||
currentSpace = currentRoom.firstOrNull()
|
|
||||||
// By default select all rooms that can be left
|
// By default select all rooms that can be left
|
||||||
selectedRoomIds.value = otherRooms
|
selectedRoomIds = otherRooms
|
||||||
.filter { it.isLastAdmin.not() }
|
.filter { it.isLastAdmin.not() }
|
||||||
.map { it.spaceRoom.roomId }
|
.map { it.spaceRoom.roomId }
|
||||||
.toPersistentSet()
|
leaveSpaceRooms = rooms.fold(
|
||||||
value = rooms.fold(
|
onSuccess = {
|
||||||
onSuccess = { AsyncData.Success(otherRooms) },
|
AsyncData.Success(
|
||||||
|
LeaveSpaceRooms(
|
||||||
|
current = currentRoom.firstOrNull(),
|
||||||
|
others = otherRooms.toImmutableList(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
onFailure = { AsyncData.Failure(it) }
|
onFailure = { AsyncData.Failure(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val selectableSpaceRooms by produceState(
|
var selectableSpaceRooms by remember {
|
||||||
initialValue = AsyncData.Loading(),
|
mutableStateOf<AsyncData<ImmutableList<SelectableSpaceRoom>>>(AsyncData.Loading())
|
||||||
key1 = leaveSpaceRooms,
|
}
|
||||||
key2 = selectedRoomIds.value,
|
LaunchedEffect(selectedRoomIds, leaveSpaceRooms) {
|
||||||
) {
|
selectableSpaceRooms = leaveSpaceRooms.map {
|
||||||
value = leaveSpaceRooms.map { list ->
|
it?.others.orEmpty().map { room ->
|
||||||
list.orEmpty().map { room ->
|
|
||||||
SelectableSpaceRoom(
|
SelectableSpaceRoom(
|
||||||
spaceRoom = room.spaceRoom,
|
spaceRoom = room.spaceRoom,
|
||||||
isLastAdmin = room.isLastAdmin,
|
isLastAdmin = room.isLastAdmin,
|
||||||
isSelected = selectedRoomIds.value.contains(room.spaceRoom.roomId),
|
isSelected = selectedRoomIds.contains(room.spaceRoom.roomId),
|
||||||
)
|
)
|
||||||
}.toImmutableList()
|
}.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
@ -86,28 +98,29 @@ class LeaveSpacePresenter(
|
||||||
|
|
||||||
fun handleEvents(event: LeaveSpaceEvents) {
|
fun handleEvents(event: LeaveSpaceEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
LeaveSpaceEvents.Retry -> {
|
||||||
|
leaveSpaceRooms = AsyncData.Loading()
|
||||||
|
retryCount += 1
|
||||||
|
}
|
||||||
LeaveSpaceEvents.DeselectAllRooms -> {
|
LeaveSpaceEvents.DeselectAllRooms -> {
|
||||||
selectedRoomIds.value = persistentSetOf()
|
selectedRoomIds = persistentSetOf()
|
||||||
}
|
}
|
||||||
LeaveSpaceEvents.SelectAllRooms -> {
|
LeaveSpaceEvents.SelectAllRooms -> {
|
||||||
selectedRoomIds.value = selectableSpaceRooms.dataOrNull()
|
selectedRoomIds = selectableSpaceRooms.dataOrNull()
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.filter { it.isLastAdmin.not() }
|
.filter { it.isLastAdmin.not() }
|
||||||
.map { it.spaceRoom.roomId }
|
.map { it.spaceRoom.roomId }
|
||||||
.toPersistentSet()
|
|
||||||
}
|
}
|
||||||
is LeaveSpaceEvents.ToggleRoomSelection -> {
|
is LeaveSpaceEvents.ToggleRoomSelection -> {
|
||||||
val currentSet = selectedRoomIds.value
|
selectedRoomIds = if (selectedRoomIds.contains(event.roomId)) {
|
||||||
selectedRoomIds.value = if (currentSet.contains(event.roomId)) {
|
selectedRoomIds - event.roomId
|
||||||
currentSet - event.roomId
|
|
||||||
} else {
|
} else {
|
||||||
currentSet + event.roomId
|
selectedRoomIds + event.roomId
|
||||||
}
|
}
|
||||||
.toPersistentSet()
|
|
||||||
}
|
}
|
||||||
LeaveSpaceEvents.LeaveSpace -> coroutineScope.leaveSpace(
|
LeaveSpaceEvents.LeaveSpace -> coroutineScope.leaveSpace(
|
||||||
leaveSpaceAction = leaveSpaceAction,
|
leaveSpaceAction = leaveSpaceAction,
|
||||||
selectedRoomIds = selectedRoomIds.value,
|
selectedRoomIds = selectedRoomIds,
|
||||||
)
|
)
|
||||||
LeaveSpaceEvents.CloseError -> {
|
LeaveSpaceEvents.CloseError -> {
|
||||||
leaveSpaceAction.value = AsyncAction.Uninitialized
|
leaveSpaceAction.value = AsyncAction.Uninitialized
|
||||||
|
|
@ -116,8 +129,8 @@ class LeaveSpacePresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
return LeaveSpaceState(
|
return LeaveSpaceState(
|
||||||
spaceName = currentSpace?.spaceRoom?.name,
|
spaceName = leaveSpaceRooms.dataOrNull()?.current?.spaceRoom?.name,
|
||||||
isLastAdmin = currentSpace?.isLastAdmin == true,
|
isLastAdmin = leaveSpaceRooms.dataOrNull()?.current?.isLastAdmin == true,
|
||||||
selectableSpaceRooms = selectableSpaceRooms,
|
selectableSpaceRooms = selectableSpaceRooms,
|
||||||
leaveSpaceAction = leaveSpaceAction.value,
|
leaveSpaceAction = leaveSpaceAction.value,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
|
|
@ -126,7 +139,7 @@ class LeaveSpacePresenter(
|
||||||
|
|
||||||
private fun CoroutineScope.leaveSpace(
|
private fun CoroutineScope.leaveSpace(
|
||||||
leaveSpaceAction: MutableState<AsyncAction<Unit>>,
|
leaveSpaceAction: MutableState<AsyncAction<Unit>>,
|
||||||
selectedRoomIds: Set<RoomId>,
|
selectedRoomIds: Collection<RoomId>,
|
||||||
) = launch {
|
) = launch {
|
||||||
runUpdatingState(leaveSpaceAction) {
|
runUpdatingState(leaveSpaceAction) {
|
||||||
leaveSpaceHandle.leave(selectedRoomIds.toList())
|
leaveSpaceHandle.leave(selectedRoomIds.toList())
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,9 @@ fun LeaveSpaceView(
|
||||||
is AsyncData.Failure -> item {
|
is AsyncData.Failure -> item {
|
||||||
AsyncFailure(
|
AsyncFailure(
|
||||||
throwable = state.selectableSpaceRooms.error,
|
throwable = state.selectableSpaceRooms.error,
|
||||||
onRetry = null,
|
onRetry = {
|
||||||
|
state.eventSink(LeaveSpaceEvents.Retry)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AsyncData.Loading,
|
is AsyncData.Loading,
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,15 @@ class LeaveSpacePresenterTest {
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.selectableSpaceRooms.isLoading()).isTrue()
|
assertThat(state.selectableSpaceRooms.isLoading()).isTrue()
|
||||||
assertThat(state.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized)
|
assertThat(state.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
skipItems(2)
|
skipItems(3)
|
||||||
val stateError = awaitItem()
|
val stateError = awaitItem()
|
||||||
assertThat(stateError.selectableSpaceRooms.isFailure()).isTrue()
|
assertThat(stateError.selectableSpaceRooms.isFailure()).isTrue()
|
||||||
|
// Retry
|
||||||
|
stateError.eventSink(LeaveSpaceEvents.Retry)
|
||||||
|
skipItems(1)
|
||||||
|
val stateLoadingAgain = awaitItem()
|
||||||
|
assertThat(stateLoadingAgain.selectableSpaceRooms.isLoading()).isTrue()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +172,7 @@ class LeaveSpacePresenterTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(3)
|
skipItems(4)
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
state.eventSink(LeaveSpaceEvents.LeaveSpace)
|
state.eventSink(LeaveSpaceEvents.LeaveSpace)
|
||||||
val stateLeaving = awaitItem()
|
val stateLeaving = awaitItem()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue