diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt index 3c963a0bf5..558ae8454d 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt @@ -10,6 +10,7 @@ package io.element.android.features.space.impl.leave import io.element.android.libraries.matrix.api.core.RoomId sealed interface LeaveSpaceEvents { + data object Retry : LeaveSpaceEvents data object SelectAllRooms : LeaveSpaceEvents data object DeselectAllRooms : LeaveSpaceEvents data class ToggleRoomSelection(val roomId: RoomId) : LeaveSpaceEvents diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt index 2754364676..ffaca68a67 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt @@ -8,10 +8,11 @@ package io.element.android.features.space.impl.leave import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.spaces.LeaveSpaceHandle 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.toImmutableList -import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -42,43 +42,55 @@ class LeaveSpacePresenter( fun create(leaveSpaceHandle: LeaveSpaceHandle): LeaveSpacePresenter } + data class LeaveSpaceRooms( + val current: LeaveSpaceRoom?, + val others: List, + ) + @Composable override fun present(): LeaveSpaceState { val coroutineScope = rememberCoroutineScope() - var currentSpace: LeaveSpaceRoom? by remember { mutableStateOf(null) } + var retryCount by remember { mutableIntStateOf(0) } val leaveSpaceAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - val selectedRoomIds = remember { - mutableStateOf>(persistentSetOf()) + var selectedRoomIds by remember { + mutableStateOf>(setOf()) } - val leaveSpaceRooms by produceState(AsyncData.Loading()) { + var leaveSpaceRooms by remember { + mutableStateOf>(AsyncData.Loading()) + } + LaunchedEffect(retryCount) { val rooms = leaveSpaceHandle.rooms() val (currentRoom, otherRooms) = rooms.getOrNull() .orEmpty() .partition { it.spaceRoom.roomId == leaveSpaceHandle.id } - currentSpace = currentRoom.firstOrNull() // By default select all rooms that can be left - selectedRoomIds.value = otherRooms + selectedRoomIds = otherRooms .filter { it.isLastAdmin.not() } .map { it.spaceRoom.roomId } - .toPersistentSet() - value = rooms.fold( - onSuccess = { AsyncData.Success(otherRooms) }, + leaveSpaceRooms = rooms.fold( + onSuccess = { + AsyncData.Success( + LeaveSpaceRooms( + current = currentRoom.firstOrNull(), + others = otherRooms.toImmutableList(), + ) + ) + }, onFailure = { AsyncData.Failure(it) } ) } - val selectableSpaceRooms by produceState( - initialValue = AsyncData.Loading(), - key1 = leaveSpaceRooms, - key2 = selectedRoomIds.value, - ) { - value = leaveSpaceRooms.map { list -> - list.orEmpty().map { room -> + var selectableSpaceRooms by remember { + mutableStateOf>>(AsyncData.Loading()) + } + LaunchedEffect(selectedRoomIds, leaveSpaceRooms) { + selectableSpaceRooms = leaveSpaceRooms.map { + it?.others.orEmpty().map { room -> SelectableSpaceRoom( spaceRoom = room.spaceRoom, isLastAdmin = room.isLastAdmin, - isSelected = selectedRoomIds.value.contains(room.spaceRoom.roomId), + isSelected = selectedRoomIds.contains(room.spaceRoom.roomId), ) }.toImmutableList() } @@ -86,28 +98,29 @@ class LeaveSpacePresenter( fun handleEvents(event: LeaveSpaceEvents) { when (event) { + LeaveSpaceEvents.Retry -> { + leaveSpaceRooms = AsyncData.Loading() + retryCount += 1 + } LeaveSpaceEvents.DeselectAllRooms -> { - selectedRoomIds.value = persistentSetOf() + selectedRoomIds = persistentSetOf() } LeaveSpaceEvents.SelectAllRooms -> { - selectedRoomIds.value = selectableSpaceRooms.dataOrNull() + selectedRoomIds = selectableSpaceRooms.dataOrNull() .orEmpty() .filter { it.isLastAdmin.not() } .map { it.spaceRoom.roomId } - .toPersistentSet() } is LeaveSpaceEvents.ToggleRoomSelection -> { - val currentSet = selectedRoomIds.value - selectedRoomIds.value = if (currentSet.contains(event.roomId)) { - currentSet - event.roomId + selectedRoomIds = if (selectedRoomIds.contains(event.roomId)) { + selectedRoomIds - event.roomId } else { - currentSet + event.roomId + selectedRoomIds + event.roomId } - .toPersistentSet() } LeaveSpaceEvents.LeaveSpace -> coroutineScope.leaveSpace( leaveSpaceAction = leaveSpaceAction, - selectedRoomIds = selectedRoomIds.value, + selectedRoomIds = selectedRoomIds, ) LeaveSpaceEvents.CloseError -> { leaveSpaceAction.value = AsyncAction.Uninitialized @@ -116,8 +129,8 @@ class LeaveSpacePresenter( } return LeaveSpaceState( - spaceName = currentSpace?.spaceRoom?.name, - isLastAdmin = currentSpace?.isLastAdmin == true, + spaceName = leaveSpaceRooms.dataOrNull()?.current?.spaceRoom?.name, + isLastAdmin = leaveSpaceRooms.dataOrNull()?.current?.isLastAdmin == true, selectableSpaceRooms = selectableSpaceRooms, leaveSpaceAction = leaveSpaceAction.value, eventSink = ::handleEvents, @@ -126,7 +139,7 @@ class LeaveSpacePresenter( private fun CoroutineScope.leaveSpace( leaveSpaceAction: MutableState>, - selectedRoomIds: Set, + selectedRoomIds: Collection, ) = launch { runUpdatingState(leaveSpaceAction) { leaveSpaceHandle.leave(selectedRoomIds.toList()) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt index c28b1661c7..ebf4bd178a 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt @@ -111,7 +111,9 @@ fun LeaveSpaceView( is AsyncData.Failure -> item { AsyncFailure( throwable = state.selectableSpaceRooms.error, - onRetry = null, + onRetry = { + state.eventSink(LeaveSpaceEvents.Retry) + }, ) } is AsyncData.Loading, diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt index b5d6f90923..5bdd100c93 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt @@ -60,9 +60,15 @@ class LeaveSpacePresenterTest { val state = awaitItem() assertThat(state.selectableSpaceRooms.isLoading()).isTrue() assertThat(state.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized) - skipItems(2) + skipItems(3) val stateError = awaitItem() 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 { - skipItems(3) + skipItems(4) val state = awaitItem() state.eventSink(LeaveSpaceEvents.LeaveSpace) val stateLeaving = awaitItem()