diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt index 7c4435bf59..2141a6861d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt @@ -22,6 +22,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.grouplist.newHomeSpaceSummaryItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import org.matrix.android.sdk.api.extensions.orFalse 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.session.room.model.SpaceChildInfo @@ -50,7 +51,8 @@ class NewSpaceSummaryController @Inject constructor( nonNullViewState.spaces, nonNullViewState.selectedSpace, nonNullViewState.rootSpacesOrdered, - nonNullViewState.homeAggregateCount + nonNullViewState.homeAggregateCount, + nonNullViewState.expandedStates, ) } @@ -58,19 +60,49 @@ class NewSpaceSummaryController @Inject constructor( spaceSummaries: List?, selectedSpace: RoomSummary?, rootSpaces: List?, - homeCount: RoomAggregateNotificationCount + homeCount: RoomAggregateNotificationCount, + expandedStates: Map, ) { + println(homeCount) val host = this newSpaceListHeaderItem { id("space_list_header") } - if (selectedSpace != null) { - addSubSpaces(selectedSpace, spaceSummaries, homeCount) - } else { - addHomeItem(true, homeCount) - addRootSpaces(rootSpaces) - } + addHomeItem(false, homeCount) + + rootSpaces + ?.filter { it.membership != Membership.INVITE } + ?.forEach { spaceSummary -> + + val subSpaces = spaceSummary.spaceChildren?.filter { childInfo -> + spaceSummaries?.any { it.roomId == childInfo.childRoomId }.orFalse() + } + val hasChildren = (subSpaces?.size ?: 0) > 0 + val isSelected = spaceSummary.roomId == selectedSpace?.roomId + val expanded = expandedStates[spaceSummary.roomId] == true + + newSpaceSummaryItem { + avatarRenderer(host.avatarRenderer) + id(spaceSummary.roomId) + matrixItem(spaceSummary.toMatrixItem()) + onSpaceSelectedListener { host.callback?.onSpaceSelected(spaceSummary) } + countState(UnreadCounterBadgeView.State(spaceSummary.notificationCount, spaceSummary.highlightCount > 0)) + + expanded(expanded) + hasChildren(hasChildren) + toggleExpand { host.callback?.onToggleExpand(spaceSummary) } + selected(isSelected) + onMore { host.callback?.onSpaceSettings(spaceSummary) } + } + + if (hasChildren && expanded) { + // it's expanded + subSpaces?.forEach { child -> + buildSubSpace(spaceSummary.roomId, spaceSummaries, expandedStates, selectedSpace, child, 1, 3) + } + } + } newSpaceAddItem { id("create") @@ -78,6 +110,51 @@ class NewSpaceSummaryController @Inject constructor( } } + private fun buildSubSpace( + idPrefix: String, + summaries: List?, + expandedStates: Map, + selectedSpace: RoomSummary?, + info: SpaceChildInfo, currentDepth: Int, maxDepth: Int + ) { + val host = this + if (currentDepth >= maxDepth) return + val childSummary = summaries?.firstOrNull { it.roomId == info.childRoomId } ?: return + // does it have children? + val subSpaces = childSummary.spaceChildren?.filter { childInfo -> + summaries.any { it.roomId == childInfo.childRoomId } + }?.sortedWith(subSpaceComparator) + val expanded = expandedStates[childSummary.roomId] == true + val isSelected = childSummary.roomId == selectedSpace?.roomId + + val id = "$idPrefix:${childSummary.roomId}" + + newSubSpaceSummaryItem { + avatarRenderer(host.avatarRenderer) + id(id) + hasChildren(!subSpaces.isNullOrEmpty()) + selected(isSelected) + expanded(expanded) + onMore { host.callback?.onSpaceSettings(childSummary) } + matrixItem(childSummary.toMatrixItem()) + listener { host.callback?.onSpaceSelected(childSummary) } + toggleExpand { host.callback?.onToggleExpand(childSummary) } + indent(currentDepth) + countState( + UnreadCounterBadgeView.State( + childSummary.notificationCount, + childSummary.highlightCount > 0 + ) + ) + } + + if (expanded) { + subSpaces?.forEach { + buildSubSpace(id, summaries, expandedStates, selectedSpace, it, currentDepth + 1, maxDepth) + } + } + } + private fun addHomeItem(selected: Boolean, homeCount: RoomAggregateNotificationCount) { val host = this newHomeSpaceSummaryItem { @@ -108,7 +185,7 @@ class NewSpaceSummaryController @Inject constructor( id(subSpaceSummary.roomId) matrixItem(subSpaceSummary.toMatrixItem()) selected(false) - listener { host.callback?.onSpaceSelected(subSpaceSummary) } + onSpaceSelectedListener { host.callback?.onSpaceSelected(subSpaceSummary) } countState( UnreadCounterBadgeView.State( subSpaceSummary.notificationCount, @@ -124,25 +201,11 @@ class NewSpaceSummaryController @Inject constructor( } } - private fun addRootSpaces(rootSpaces: List?) { - val host = this - rootSpaces - ?.filter { it.membership != Membership.INVITE } - ?.forEach { roomSummary -> - newSpaceSummaryItem { - avatarRenderer(host.avatarRenderer) - id(roomSummary.roomId) - matrixItem(roomSummary.toMatrixItem()) - listener { host.callback?.onSpaceSelected(roomSummary) } - countState(UnreadCounterBadgeView.State(roomSummary.notificationCount, roomSummary.highlightCount > 0)) - } - } - } - interface Callback { fun onSpaceSelected(spaceSummary: RoomSummary?) fun onSpaceInviteSelected(spaceSummary: RoomSummary) fun onSpaceSettings(spaceSummary: RoomSummary) + fun onToggleExpand(spaceSummary: RoomSummary) fun onAddSpaceSelected() fun sendFeedBack() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt index 778b9c933e..a3a88b0eb7 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt @@ -18,6 +18,7 @@ package im.vector.app.features.spaces import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -36,14 +37,24 @@ abstract class NewSpaceSummaryItem : VectorEpoxyModel(R.id.root) + val root by bind(R.id.root) val avatar by bind(R.id.avatar) val name by bind(R.id.name) val unreadCounter by bind(R.id.unread_counter) + val chevron by bind(R.id.chevron) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSubSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSubSpaceSummaryItem.kt new file mode 100644 index 0000000000..e424a93cdd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSubSpaceSummaryItem.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.isVisible +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.platform.CheckableConstraintLayout +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class NewSubSpaceSummaryItem : VectorEpoxyModel(R.layout.item_new_sub_space) { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var selected: Boolean = false + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: ClickListener? = null + @EpoxyAttribute var expanded: Boolean = false + @EpoxyAttribute var hasChildren: Boolean = false + @EpoxyAttribute var indent: Int = 0 + @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + + override fun bind(holder: Holder) { + super.bind(holder) + holder.root.onClick(listener) + holder.name.text = matrixItem.displayName + holder.root.isChecked = selected + holder.root.setOnLongClickListener { onMore?.invoke(holder.root).let { true } } + + holder.chevron.setImageDrawable( + ContextCompat.getDrawable( + holder.view.context, + if (expanded) R.drawable.ic_expand_more else R.drawable.ic_arrow_right + ) + ) + holder.chevron.onClick(toggleExpand) + holder.chevron.isVisible = hasChildren + + holder.indent.isVisible = indent > 0 + holder.indent.updateLayoutParams { + width = indent * 30 + } + + avatarRenderer.render(matrixItem, holder.avatar) + holder.notificationBadge.render(countState) + } + + override fun unbind(holder: Holder) { + avatarRenderer.clear(holder.avatar) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val avatar by bind(R.id.avatar) + val name by bind(R.id.name) + val root by bind(R.id.root) + val chevron by bind(R.id.chevron) + val indent by bind(R.id.indent) + val notificationBadge by bind(R.id.notification_badge) + } +} diff --git a/vector/src/main/res/layout/item_new_space.xml b/vector/src/main/res/layout/item_new_space.xml index 367d69ce69..3d627fa782 100644 --- a/vector/src/main/res/layout/item_new_space.xml +++ b/vector/src/main/res/layout/item_new_space.xml @@ -34,9 +34,9 @@ android:maxLines="1" android:textColor="?vctr_content_primary" android:textStyle="bold" - app:layout_constraintStart_toEndOf="@id/avatar" - app:layout_constraintEnd_toStartOf="@id/unread_counter" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/unread_counter" + app:layout_constraintStart_toEndOf="@id/avatar" app:layout_constraintTop_toTopOf="parent" tools:text="Element Corp" /> @@ -53,25 +53,28 @@ android:paddingEnd="4dp" android:textColor="?colorOnError" android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/chevron" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" tools:background="@drawable/bg_unread_highlight" tools:text="147" tools:visibility="visible" /> + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_arrow_right" + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/item_new_sub_space.xml b/vector/src/main/res/layout/item_new_sub_space.xml new file mode 100644 index 0000000000..1c4afcea18 --- /dev/null +++ b/vector/src/main/res/layout/item_new_sub_space.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + +