Iterate on manage space rooms, but not happy with the reset method.
This commit is contained in:
parent
ae4d635357
commit
e896c7604d
15 changed files with 170 additions and 117 deletions
|
|
@ -14,6 +14,7 @@ import android.os.Parcelable
|
|||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
|
|
@ -40,6 +41,7 @@ import io.element.android.libraries.di.RoomScope
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||
import io.element.android.libraries.matrix.api.spaces.loadAllIncrementally
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
|
|
@ -83,6 +85,9 @@ class SpaceFlowNode(
|
|||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
spaceRoomList.loadAllIncrementally(lifecycleScope)
|
||||
},
|
||||
onDestroy = {
|
||||
spaceRoomList.destroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.text.input.rememberTextFieldState
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -25,8 +24,10 @@ import io.element.android.libraries.architecture.AsyncAction
|
|||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
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.SpaceService
|
||||
import io.element.android.libraries.matrix.api.spaces.resetAndWaitForFullReload
|
||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -35,6 +36,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
class AddRoomToSpacePresenter(
|
||||
|
|
@ -45,7 +47,7 @@ class AddRoomToSpacePresenter(
|
|||
@Composable
|
||||
override fun present(): AddRoomToSpaceState {
|
||||
var selectedRooms: ImmutableList<SelectRoomInfo> by remember { mutableStateOf(persistentListOf()) }
|
||||
var searchQuery = rememberTextFieldState()
|
||||
val searchQuery = rememberTextFieldState()
|
||||
var isSearchActive by remember { mutableStateOf(false) }
|
||||
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
|
||||
|
|
@ -63,12 +65,12 @@ class AddRoomToSpacePresenter(
|
|||
val suggestions by dataSource.suggestions.collectAsState(initial = persistentListOf())
|
||||
|
||||
val filteredRooms by dataSource.roomInfoList.collectAsState(initial = persistentListOf())
|
||||
val searchResults by remember<State<SearchBarResultState<ImmutableList<SelectRoomInfo>>>> {
|
||||
val searchResults by remember {
|
||||
derivedStateOf {
|
||||
when {
|
||||
filteredRooms.isNotEmpty() -> SearchBarResultState.Results(filteredRooms)
|
||||
isSearchActive && searchQuery.text.isNotEmpty() -> SearchBarResultState.NoResultsFound()
|
||||
else -> SearchBarResultState.Initial()
|
||||
isSearchActive && searchQuery.text.isNotEmpty() -> SearchBarResultState.NoResultsFound<ImmutableList<SelectRoomInfo>>()
|
||||
else -> SearchBarResultState.Initial<ImmutableList<SelectRoomInfo>>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +93,11 @@ class AddRoomToSpacePresenter(
|
|||
AddRoomToSpaceEvent.Save -> {
|
||||
coroutineScope.addRoomsToSpace(
|
||||
selectedRooms = selectedRooms,
|
||||
addAction = saveAction,
|
||||
dataSource = dataSource,
|
||||
saveAction = saveAction,
|
||||
onPartialSuccess = { successfullyAdded ->
|
||||
selectedRooms = selectedRooms.filterNot { it.roomId in successfullyAdded }.toImmutableList()
|
||||
},
|
||||
)
|
||||
}
|
||||
AddRoomToSpaceEvent.ResetSaveAction -> {
|
||||
|
|
@ -113,21 +119,30 @@ class AddRoomToSpacePresenter(
|
|||
|
||||
private fun CoroutineScope.addRoomsToSpace(
|
||||
selectedRooms: ImmutableList<SelectRoomInfo>,
|
||||
addAction: MutableState<AsyncAction<Unit>>,
|
||||
dataSource: AddRoomToSpaceSearchDataSource,
|
||||
saveAction: MutableState<AsyncAction<Unit>>,
|
||||
onPartialSuccess: (Set<RoomId>) -> Unit,
|
||||
) = launch {
|
||||
addAction.runUpdatingState {
|
||||
val results = selectedRooms.map { selectedRoom ->
|
||||
saveAction.runUpdatingState {
|
||||
val spaceId = spaceRoomList.spaceId
|
||||
val successfullyAdded = mutableSetOf<RoomId>()
|
||||
val results = selectedRooms.map { room ->
|
||||
async {
|
||||
spaceService.addChildToSpace(
|
||||
spaceId = spaceRoomList.roomId,
|
||||
childId = selectedRoom.roomId,
|
||||
)
|
||||
spaceId = spaceId,
|
||||
childId = room.roomId,
|
||||
).onSuccess { successfullyAdded.add(room.roomId) }
|
||||
}
|
||||
}.awaitAll()
|
||||
val anyFailure = results.any { it.isFailure }
|
||||
if (anyFailure) {
|
||||
// On partial success, mark added rooms in data source and update selection
|
||||
dataSource.markAsAdded(successfullyAdded)
|
||||
onPartialSuccess(successfullyAdded)
|
||||
Result.failure(Exception("Failed to add some rooms"))
|
||||
} else {
|
||||
// On full success, refresh the space room list
|
||||
spaceRoomList.reset()
|
||||
Result.success(Unit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,20 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||
import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.recent.getRecentlyVisitedRoomInfoFlow
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||
import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -48,7 +49,7 @@ class AddRoomToSpaceSearchDataSource(
|
|||
roomListService: RoomListService,
|
||||
spaceRoomList: SpaceRoomList,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
coroutineDispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
|
@ -62,33 +63,49 @@ class AddRoomToSpaceSearchDataSource(
|
|||
coroutineScope = coroutineScope,
|
||||
)
|
||||
|
||||
private val spaceChildrenFlow = spaceRoomList.spaceRoomsFlow.map { spaceChildren ->
|
||||
spaceChildren.map { it.roomId }.toSet()
|
||||
private val spaceChildrenFlow = spaceRoomList.spaceRoomsFlow.map { rooms ->
|
||||
rooms.map { it.roomId }.toSet()
|
||||
}
|
||||
|
||||
private val filterRoomPredicate: (RoomInfo, Set<RoomId>) -> Boolean = { info, childIds ->
|
||||
// Track locally added rooms for partial failure cases
|
||||
private val addedRoomIds = MutableStateFlow<Set<RoomId>>(emptySet())
|
||||
|
||||
/**
|
||||
* Marks rooms as added to the space (for partial failure handling).
|
||||
* These rooms will be filtered out from search results and suggestions.
|
||||
*/
|
||||
fun markAsAdded(roomIds: Set<RoomId>) {
|
||||
addedRoomIds.value += roomIds
|
||||
}
|
||||
|
||||
private val filterRoomPredicate: (RoomInfo, Set<RoomId>, Set<RoomId>) -> Boolean = { info, childIds, addedIds ->
|
||||
!info.isSpace &&
|
||||
!info.isDm &&
|
||||
info.currentUserMembership == CurrentUserMembership.JOINED &&
|
||||
info.id !in childIds
|
||||
info.id !in childIds &&
|
||||
info.id !in addedIds
|
||||
}
|
||||
|
||||
val roomInfoList: Flow<ImmutableList<SelectRoomInfo>> = combine(
|
||||
roomList.filteredSummaries,
|
||||
spaceChildrenFlow,
|
||||
) { roomSummaries, childIds ->
|
||||
addedRoomIds,
|
||||
) { roomSummaries, childIds, addedIds ->
|
||||
roomSummaries
|
||||
.filter { filterRoomPredicate(it.info, childIds) }
|
||||
.map { it.toSelectRoomInfo() }
|
||||
.filter { filterRoomPredicate(it.info, childIds, addedIds) }
|
||||
.map { it.info.toSelectRoomInfo() }
|
||||
.toImmutableList()
|
||||
}.flowOn(coroutineDispatchers.computation)
|
||||
|
||||
val suggestions: Flow<ImmutableList<SelectRoomInfo>> = spaceChildrenFlow.map { childIds ->
|
||||
val suggestions: Flow<ImmutableList<SelectRoomInfo>> = combine(
|
||||
spaceChildrenFlow,
|
||||
addedRoomIds,
|
||||
) { childIds, addedIds ->
|
||||
matrixClient
|
||||
.getRecentlyVisitedRoomInfoFlow { filterRoomPredicate(it, childIds) }
|
||||
.getRecentlyVisitedRoomInfoFlow { filterRoomPredicate(it, childIds, addedIds) }
|
||||
.take(MAX_SUGGESTIONS_COUNT)
|
||||
.map { it.toSelectRoomInfo() }
|
||||
.toList()
|
||||
.map { it.toSelectRoomInfo() }
|
||||
.toImmutableList()
|
||||
}.flowOn(coroutineDispatchers.computation)
|
||||
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ fun AddRoomToSpaceView(
|
|||
onRoomsAdded: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun onRoomRemoved(roomInfo: SelectRoomInfo) {
|
||||
state.eventSink(AddRoomToSpaceEvent.ToggleRoom(roomInfo))
|
||||
fun onRoomToggled(room: SelectRoomInfo) {
|
||||
state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room))
|
||||
}
|
||||
|
||||
fun onBack() {
|
||||
|
|
@ -114,18 +114,18 @@ fun AddRoomToSpaceView(
|
|||
if (state.selectedRooms.isNotEmpty()) {
|
||||
SelectedRoomsRow(
|
||||
selectedRooms = state.selectedRooms,
|
||||
onRemoveRoom = ::onRoomRemoved,
|
||||
onRemoveRoom = ::onRoomToggled,
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
) { rooms ->
|
||||
LazyColumn {
|
||||
items(rooms, key = { it.roomId.value }) { roomInfo ->
|
||||
items(rooms, key = { it.roomId }) { roomInfo ->
|
||||
RoomListItem(
|
||||
roomInfo = roomInfo,
|
||||
isSelected = state.selectedRooms.any { it.roomId == roomInfo.roomId },
|
||||
onToggle = { state.eventSink(AddRoomToSpaceEvent.ToggleRoom(it)) }
|
||||
onToggle = ::onRoomToggled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ fun AddRoomToSpaceView(
|
|||
if (state.selectedRooms.isNotEmpty()) {
|
||||
SelectedRoomsRow(
|
||||
selectedRooms = state.selectedRooms,
|
||||
onRemoveRoom = ::onRoomRemoved,
|
||||
onRemoveRoom = ::onRoomToggled,
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@ fun AddRoomToSpaceView(
|
|||
RoomListItem(
|
||||
roomInfo = roomInfo,
|
||||
isSelected = state.selectedRooms.any { it.roomId == roomInfo.roomId },
|
||||
onToggle = { state.eventSink(AddRoomToSpaceEvent.ToggleRoom(it)) }
|
||||
onToggle = ::onRoomToggled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -205,8 +205,8 @@ private fun SelectedRoomsRow(
|
|||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(32.dp)
|
||||
) {
|
||||
items(selectedRooms, key = { it.roomId.value }) { roomInfo ->
|
||||
SelectedRoom(roomInfo = roomInfo, onRemoveRoom = onRemoveRoom)
|
||||
items(selectedRooms, key = { it.roomId }) { roomInfo ->
|
||||
SelectedRoom(roomInfo = roomInfo, onRemoveRoom = { onRemoveRoom(roomInfo) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class SpaceNode(
|
|||
private val callback: Callback = callback()
|
||||
|
||||
private fun onShareRoom(context: Context) = lifecycleScope.launch {
|
||||
matrixClient.getRoom(spaceRoomList.roomId)?.use { room ->
|
||||
matrixClient.getRoom(spaceRoomList.spaceId)?.use { room ->
|
||||
room.getPermalink()
|
||||
.onSuccess { permalink ->
|
||||
context.startSharePlainTextIntent(
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import kotlinx.collections.immutable.toImmutableSet
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -69,7 +70,6 @@ class SpacePresenter(
|
|||
@Composable
|
||||
override fun present(): SpaceState {
|
||||
LaunchedEffect(Unit) {
|
||||
paginate()
|
||||
spaceRoomList.spaceRoomsFlow.collect { children = it.toImmutableList() }
|
||||
}
|
||||
|
||||
|
|
@ -111,21 +111,18 @@ class SpacePresenter(
|
|||
var isManageMode by remember { mutableStateOf(false) }
|
||||
var selectedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
||||
var removeRoomsAction by remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
// Track locally removed rooms for partial failure cases
|
||||
var removedRoomIds by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
||||
|
||||
val filteredChildren by remember {
|
||||
derivedStateOf {
|
||||
children
|
||||
.filterNot { it.roomId in removedRoomIds }
|
||||
.let { list ->
|
||||
if (isManageMode) {
|
||||
// In manage mode, only show rooms (not spaces)
|
||||
list.filter { !it.isSpace }
|
||||
} else {
|
||||
list
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
val notRemoved = children.filterNot { it.roomId in removedRoomIds }
|
||||
if (isManageMode) {
|
||||
// In manage mode, only show rooms (not spaces)
|
||||
notRemoved.filter { !it.isSpace }.toImmutableList()
|
||||
} else {
|
||||
notRemoved.toImmutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +138,8 @@ class SpacePresenter(
|
|||
|
||||
fun handleEvent(event: SpaceEvents) {
|
||||
when (event) {
|
||||
SpaceEvents.LoadMore -> localCoroutineScope.paginate()
|
||||
// SpaceRoomList is loaded automatically as backend is really slow. Event is kept for future.
|
||||
SpaceEvents.LoadMore -> Unit
|
||||
is SpaceEvents.Join -> {
|
||||
sessionCoroutineScope.joinRoom(event.spaceRoom, joinActions, setJoinActions)
|
||||
}
|
||||
|
|
@ -186,7 +184,7 @@ class SpacePresenter(
|
|||
SpaceEvents.ConfirmRoomRemoval -> {
|
||||
localCoroutineScope.launch {
|
||||
removeRoomsAction = AsyncAction.Loading
|
||||
val spaceId = spaceRoomList.roomId
|
||||
val spaceId = spaceRoomList.spaceId
|
||||
val roomsToRemove = selectedRoomIds.toSet()
|
||||
val successfullyRemoved = mutableSetOf<RoomId>()
|
||||
val results = roomsToRemove.map { roomId ->
|
||||
|
|
@ -196,16 +194,18 @@ class SpacePresenter(
|
|||
}
|
||||
}
|
||||
results.awaitAll()
|
||||
if (successfullyRemoved.isNotEmpty()) {
|
||||
removedRoomIds = removedRoomIds + successfullyRemoved
|
||||
}
|
||||
val hasError = successfullyRemoved.size < roomsToRemove.size
|
||||
if (hasError) {
|
||||
// On partial success, update selection to only keep failed rooms
|
||||
selectedRoomIds = selectedRoomIds - successfullyRemoved
|
||||
removedRoomIds = removedRoomIds + successfullyRemoved
|
||||
removeRoomsAction = AsyncAction.Failure(Exception("Failed to remove some rooms"))
|
||||
} else {
|
||||
removeRoomsAction = AsyncAction.Success(Unit)
|
||||
isManageMode = false
|
||||
selectedRoomIds = emptySet()
|
||||
// Reset the space room list to see the updates.
|
||||
spaceRoomList.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,8 +246,4 @@ class SpacePresenter(
|
|||
setJoinActions(joinActions + mapOf(spaceRoom.roomId to AsyncAction.Failure(it)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.paginate() = launch {
|
||||
spaceRoomList.paginate()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ fun SpaceView(
|
|||
SpaceViewTopBar(
|
||||
spaceInfo = state.spaceInfo,
|
||||
canAccessSpaceSettings = state.canAccessSpaceSettings,
|
||||
canEditSpaceGraph = state.canEditSpaceGraph,
|
||||
showManageRoomsAction = state.showManageRoomsAction,
|
||||
onBackClick = onBackClick,
|
||||
onLeaveSpaceClick = onLeaveSpaceClick,
|
||||
|
|
@ -376,6 +377,7 @@ private fun LoadingMoreIndicator(
|
|||
private fun SpaceViewTopBar(
|
||||
spaceInfo: RoomInfo,
|
||||
canAccessSpaceSettings: Boolean,
|
||||
canEditSpaceGraph: Boolean,
|
||||
showManageRoomsAction: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
onLeaveSpaceClick: () -> Unit,
|
||||
|
|
@ -416,7 +418,7 @@ private fun SpaceViewTopBar(
|
|||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
if (showManageRoomsAction) {
|
||||
if (canEditSpaceGraph) {
|
||||
SpaceMenuItem(
|
||||
titleRes = CommonStrings.action_create_room,
|
||||
icon = CompoundIcons.Plus(),
|
||||
|
|
@ -433,14 +435,16 @@ private fun SpaceViewTopBar(
|
|||
onAddRoomClick()
|
||||
}
|
||||
)
|
||||
SpaceMenuItem(
|
||||
titleRes = CommonStrings.action_manage_rooms,
|
||||
icon = CompoundIcons.Edit(),
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onManageRoomsClick()
|
||||
}
|
||||
)
|
||||
if (showManageRoomsAction) {
|
||||
SpaceMenuItem(
|
||||
titleRes = CommonStrings.action_manage_rooms,
|
||||
icon = CompoundIcons.Edit(),
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onManageRoomsClick()
|
||||
}
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
}
|
||||
SpaceMenuItem(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue