From ea5e48b9404dac9a0f23627bcaf682a973303bb4 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 22 Mar 2021 11:40:28 +0100 Subject: [PATCH] 1 depth hierarchy support in space panel --- .../sdk/session/space/SpaceHierarchyTest.kt | 40 +++++++++ .../sdk/api/session/space/SpaceService.kt | 2 + .../room/summary/RoomSummaryDataSource.kt | 27 +++++- .../session/space/DefaultSpaceService.kt | 3 + .../app/features/spaces/SpaceListFragment.kt | 4 + .../features/spaces/SpaceSummaryController.kt | 90 ++++++++++++++----- .../app/features/spaces/SpaceSummaryItem.kt | 51 +++++++---- .../features/spaces/SpacesListViewModel.kt | 23 ++++- vector/src/main/res/layout/item_space.xml | 32 ++++--- 9 files changed, 217 insertions(+), 55 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index b512983ea6..b3396fed6f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -370,4 +370,44 @@ class SpaceHierarchyTest : InstrumentedTest { } return TestSpaceCreationResult(spaceId, roomIds) } + + @Test + fun testRootSpaces() { + val session = commonTestHelper.createAccount("John", SessionTestParams(true)) + + val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + )) + + val spaceBInfo = createPublicSpace(session, "SpaceB", listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + )) + + val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + )) + + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + + // add C as subspace of B + runBlocking { + val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) + spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + } + + // + A + // a1, a2 + // + B + // b1, b2, b3 + // + C + // + c1, c2 + + val rootSpaces = session.spaceService().getRootSpaceSummaries() + + assertEquals(2, rootSpaces.size, "Unexpected number of root spaces ${rootSpaces.map { it.name }}") + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 390f8c6718..604d06d9ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -90,4 +90,6 @@ interface SpaceService { * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering. */ suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List) + + fun getRootSpaceSummaries(): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index fd54ac63ee..c2d4e318cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -123,6 +123,23 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat return getRoomSummaries(spaceSummaryQueryParams) } + fun getRootSpaceSummaries(): List { + return getRoomSummaries(spaceSummaryQueryParams { + memberships = listOf(Membership.JOIN) + }) + .let { allJoinedSpace -> + val allFlattenChildren = arrayListOf() + allJoinedSpace.forEach { + flattenSubSpace(it, emptyList(), allFlattenChildren, listOf(Membership.JOIN), false) + } + val knownNonOrphan = allFlattenChildren.map { it.roomId }.distinct() + // keep only root rooms + allJoinedSpace.filter { candidate -> + !knownNonOrphan.contains(candidate.roomId) + } + } + } + fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List { return monarchy.fetchAllMappedSync( { breadcrumbsQuery(it, queryParams) }, @@ -341,8 +358,14 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } } - fun flattenSubSpace(current: RoomSummary, parenting: List, output: MutableList, memberShips: List) { - output.add(current) + fun flattenSubSpace(current: RoomSummary, + parenting: List, + output: MutableList, + memberShips: List, + includeCurrent: Boolean = true) { + if (includeCurrent) { + output.add(current) + } current.children?.sortedBy { it.order ?: it.name }?.forEach { if (it.roomType == RoomType.SPACE) { // Add recursive diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 02ebc266c0..e6d30e2712 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -85,6 +85,9 @@ internal class DefaultSpaceService @Inject constructor( return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) } + override fun getRootSpaceSummaries(): List { + return roomSummaryDataSource.getRootSpaceSummaries() + } override suspend fun peekSpace(spaceId: String): SpacePeekResult { return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index 011453e642..bc7be74442 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -84,6 +84,10 @@ class SpaceListFragment @Inject constructor( sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId)) } + override fun onToggleExpand(spaceSummary: RoomSummary) { + viewModel.handle(SpaceListAction.ToggleExpand(spaceSummary)) + } + override fun onAddSpaceSelected() { viewModel.handle(SpaceListAction.AddSpace) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index 7c9c3969b1..0417889eaf 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -27,6 +27,7 @@ import im.vector.app.features.grouplist.homeSpaceSummaryItem import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -48,10 +49,17 @@ class SpaceSummaryController @Inject constructor( override fun buildModels() { val nonNullViewState = viewState ?: return - buildGroupModels(nonNullViewState.asyncSpaces(), nonNullViewState.selectedSpace) + buildGroupModels( + nonNullViewState.asyncSpaces(), + nonNullViewState.selectedSpace, + nonNullViewState.rootSpaces, + nonNullViewState.expandedStates) } - private fun buildGroupModels(summaries: List?, selected: RoomSummary?) { + private fun buildGroupModels(summaries: List?, + selected: RoomSummary?, + rootSpaces: List?, + expandedStates: Map) { if (summaries.isNullOrEmpty()) { return } @@ -86,29 +94,66 @@ class SpaceSummaryController @Inject constructor( text(stringProvider.getString(R.string.spaces_header)) } - summaries - .filter { it.membership == Membership.JOIN } - .forEach { groupSummary -> - val isSelected = groupSummary.roomId == selected?.roomId - if (groupSummary.roomId == ALL_COMMUNITIES_GROUP_ID) { - homeSpaceSummaryItem { - id(groupSummary.roomId) - selected(isSelected) - listener { callback?.onSpaceSelected(groupSummary) } - } - } else { - spaceSummaryItem { - avatarRenderer(avatarRenderer) - id(groupSummary.roomId) - matrixItem(groupSummary.toMatrixItem()) - selected(isSelected) - onMore { callback?.onSpaceSettings(groupSummary) } - listener { callback?.onSpaceSelected(groupSummary) } - } + summaries.firstOrNull { it.roomId == ALL_COMMUNITIES_GROUP_ID } + ?.let { + homeSpaceSummaryItem { + id(it.roomId) + selected(it.roomId == selected?.roomId) + listener { callback?.onSpaceSelected(it) } } } - // Temporary item to create a new Space (will move with final design) +// summaries +// .filter { it.membership == Membership.JOIN } + rootSpaces + ?.forEach { groupSummary -> + val isSelected = groupSummary.roomId == selected?.roomId +// if (groupSummary.roomId == ALL_COMMUNITIES_GROUP_ID) { +// homeSpaceSummaryItem { +// id(groupSummary.roomId) +// selected(isSelected) +// listener { callback?.onSpaceSelected(groupSummary) } +// } +// } else { + // does it have children? + val subSpaces = groupSummary.children?.filter { childInfo -> + summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 + } + val hasChildren = (subSpaces?.size ?: 0) > 0 + val expanded = expandedStates[groupSummary.roomId] == true + + spaceSummaryItem { + avatarRenderer(avatarRenderer) + id(groupSummary.roomId) + hasChildren(hasChildren) + expanded(expanded) + matrixItem(groupSummary.toMatrixItem()) + selected(isSelected) + onMore { callback?.onSpaceSettings(groupSummary) } + listener { callback?.onSpaceSelected(groupSummary) } + toggleExpand { callback?.onToggleExpand(groupSummary) } + } + + if (hasChildren && expanded) { + // it's expanded + subSpaces?.forEach { child -> + summaries.firstOrNull { it.roomId == child.childRoomId }?.let { childSum -> + val isSelected = childSum.roomId == selected?.roomId + spaceSummaryItem { + avatarRenderer(avatarRenderer) + id(child.childRoomId) + hasChildren(false) + selected(isSelected) + matrixItem(MatrixItem.RoomItem(child.childRoomId, child.name, child.avatarUrl)) + listener { callback?.onSpaceSelected(childSum) } + indent(1) + } + } + } + } + } + +// Temporary item to create a new Space (will move with final design) genericButtonItem { id("create") @@ -121,6 +166,7 @@ class SpaceSummaryController @Inject constructor( interface Callback { fun onSpaceSelected(spaceSummary: RoomSummary) fun onSpaceSettings(spaceSummary: RoomSummary) + fun onToggleExpand(spaceSummary: RoomSummary) fun onAddSpaceSelected() } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index af151a1624..e3deca5367 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -17,6 +17,7 @@ package im.vector.app.features.spaces import android.widget.ImageView +import android.widget.Space import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.view.isGone @@ -40,7 +41,9 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { @EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute var onMore: (() -> Unit)? = null @EpoxyAttribute var toggleExpand: (() -> Unit)? = null - @EpoxyAttribute var expanded: Boolean? = null + @EpoxyAttribute var expanded: Boolean = false + @EpoxyAttribute var hasChildren: Boolean = false + @EpoxyAttribute var indent: Int = 0 override fun bind(holder: Holder) { super.bind(holder) @@ -57,25 +60,36 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { holder.moreView.isVisible = false } - when (expanded) { - null -> { - holder.collapseIndicator.isGone = true - } - else -> { - holder.collapseIndicator.isVisible = true - holder.collapseIndicator.setImageDrawable( - ContextCompat.getDrawable(holder.view.context, - if (expanded!!) R.drawable.ic_expand_less else R.drawable.ic_expand_more - ) - ) - holder.collapseIndicator.setOnClickListener( - DebouncedClickListener({ _ -> - toggleExpand?.invoke() - }) - ) - } + if (hasChildren) { +// holder.collapseIndicator.setOnClickListener( +// DebouncedClickListener({ _ -> +// toggleExpand?.invoke() +// }) +// ) +// when (expanded) { +// null -> { +// holder.collapseIndicator.isGone = true +// } +// else -> { + holder.collapseIndicator.isVisible = true + holder.collapseIndicator.setImageDrawable( + ContextCompat.getDrawable(holder.view.context, + if (expanded) R.drawable.ic_expand_less else R.drawable.ic_expand_more + ) + ) + holder.collapseIndicator.setOnClickListener( + DebouncedClickListener({ _ -> + toggleExpand?.invoke() + }) + ) +// } +// } + } else { + holder.collapseIndicator.isGone = true } + holder.indentSpace.isVisible = indent > 0 + avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) } @@ -90,5 +104,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { val rootView by bind(R.id.itemGroupLayout) val moreView by bind(R.id.groupTmpLeave) val collapseIndicator by bind(R.id.groupChildrenCollapse) + val indentSpace by bind(R.id.indent) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index fd7dbd006c..1f15d76a80 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -51,6 +51,7 @@ const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" sealed class SpaceListAction : VectorViewModelAction { data class SelectSpace(val spaceSummary: RoomSummary) : SpaceListAction() data class LeaveSpace(val spaceSummary: RoomSummary) : SpaceListAction() + data class ToggleExpand(val spaceSummary: RoomSummary) : SpaceListAction() object AddSpace : SpaceListAction() } @@ -65,7 +66,9 @@ sealed class SpaceListViewEvents : VectorViewEvents { data class SpaceListViewState( val asyncSpaces: Async> = Uninitialized, - val selectedSpace: RoomSummary? = null + val selectedSpace: RoomSummary? = null, + val rootSpaces: List? = null, + val expandedStates: Map = emptyMap() ) : MvRxState class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, @@ -132,6 +135,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp is SpaceListAction.SelectSpace -> handleSelectSpace(action) is SpaceListAction.LeaveSpace -> handleLeaveSpace(action) SpaceListAction.AddSpace -> handleAddSpace() + is SpaceListAction.ToggleExpand -> handleToggleExpand(action) } } @@ -159,6 +163,15 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } } + private fun handleToggleExpand(action: SpaceListAction.ToggleExpand) = withState { state -> + val updatedToggleStates = state.expandedStates.toMutableMap().apply { + this[action.spaceSummary.roomId] = !(this[action.spaceSummary.roomId] ?: false) + } + setState { + copy(expandedStates = updatedToggleStates) + } + } + private fun handleLeaveSpace(action: SpaceListAction.LeaveSpace) { viewModelScope.launch { awaitCallback { @@ -199,7 +212,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .rx() .liveSpaceSummaries(spaceSummaryQueryParams), BiFunction { allCommunityGroup, communityGroups -> - listOf(allCommunityGroup) + communityGroups + (listOf(allCommunityGroup) + communityGroups) } ) .execute { async -> @@ -209,7 +222,11 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } else { async()?.firstOrNull() } - copy(asyncSpaces = async, selectedSpace = newSelectedGroup) + copy( + asyncSpaces = async, + selectedSpace = newSelectedGroup, + rootSpaces = session.spaceService().getRootSpaceSummaries() + ) } } } diff --git a/vector/src/main/res/layout/item_space.xml b/vector/src/main/res/layout/item_space.xml index ce3b5c69bc..fc7b43216a 100644 --- a/vector/src/main/res/layout/item_space.xml +++ b/vector/src/main/res/layout/item_space.xml @@ -10,6 +10,16 @@ android:focusable="true" android:foreground="?attr/selectableItemBackground"> + + @@ -46,31 +56,33 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" + android:background="?selectableItemBackground" + android:clickable="true" android:importantForAccessibility="no" - tools:src="@drawable/ic_expand_more_white" android:src="@drawable/ic_expand_less_white" android:visibility="gone" - tools:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator" app:layout_constraintEnd_toStartOf="@+id/groupTmpLeave" app:layout_constraintTop_toTopOf="parent" app:tint="?riotx_text_primary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_expand_more_white" + tools:visibility="visible" /> + app:layout_constraintTop_toTopOf="parent" + app:tint="?riotx_text_secondary" />