From 16efec9d1e4f98c36eb8cbbf6caa1c07a9cf8905 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 22 Aug 2022 13:46:14 +0200 Subject: [PATCH 1/8] Adds accordion-style space sheet --- .../spaces/NewSpaceSummaryController.kt | 111 ++++++++++++++---- .../features/spaces/NewSpaceSummaryItem.kt | 20 +++- .../features/spaces/NewSubSpaceSummaryItem.kt | 89 ++++++++++++++ vector/src/main/res/layout/item_new_space.xml | 21 ++-- .../main/res/layout/item_new_sub_space.xml | 93 +++++++++++++++ 5 files changed, 297 insertions(+), 37 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/spaces/NewSubSpaceSummaryItem.kt create mode 100644 vector/src/main/res/layout/item_new_sub_space.xml 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<RoomSummary>?, selectedSpace: RoomSummary?, rootSpaces: List<RoomSummary>?, - homeCount: RoomAggregateNotificationCount + homeCount: RoomAggregateNotificationCount, + expandedStates: Map<String, Boolean>, ) { + 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<RoomSummary>?, + expandedStates: Map<String, Boolean>, + 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<RoomSummary>?) { - 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<NewSpaceSummaryItem.Holder @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 onSpaceSelectedListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: ClickListener? = null @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: ClickListener? = null + @EpoxyAttribute var expanded: Boolean = false + @EpoxyAttribute var hasChildren: Boolean = false + override fun bind(holder: Holder) { super.bind(holder) - holder.rootView.onClick(listener) + holder.root.onClick(onSpaceSelectedListener) + holder.root.setOnLongClickListener { onMore?.invoke(holder.root).let { true } } holder.name.text = matrixItem.displayName - holder.rootView.isChecked = selected + holder.root.isChecked = selected + + holder.chevron.setOnClickListener(toggleExpand) + holder.chevron.isVisible = hasChildren + holder.chevron.setImageResource(if (expanded) R.drawable.ic_expand_more else R.drawable.ic_arrow_right) avatarRenderer.render(matrixItem, holder.avatar) holder.unreadCounter.render(countState) @@ -55,9 +66,10 @@ abstract class NewSpaceSummaryItem : VectorEpoxyModel<NewSpaceSummaryItem.Holder } class Holder : VectorEpoxyHolder() { - val rootView by bind<CheckableConstraintLayout>(R.id.root) + val root by bind<CheckableConstraintLayout>(R.id.root) val avatar by bind<ImageView>(R.id.avatar) val name by bind<TextView>(R.id.name) val unreadCounter by bind<UnreadCounterBadgeView>(R.id.unread_counter) + val chevron by bind<ImageView>(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<NewSubSpaceSummaryItem.Holder>(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<ImageView>(R.id.avatar) + val name by bind<TextView>(R.id.name) + val root by bind<CheckableConstraintLayout>(R.id.root) + val chevron by bind<ImageView>(R.id.chevron) + val indent by bind<Space>(R.id.indent) + val notificationBadge by bind<UnreadCounterBadgeView>(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" /> <ImageView android:id="@+id/chevron" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="32dp" + android:layout_height="48dp" android:layout_marginEnd="21dp" + android:background="?selectableItemBackground" android:importantForAccessibility="no" - android:src="@drawable/ic_arrow_right" - android:visibility="visible" + android:scaleType="centerInside" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" app:tint="?vctr_content_secondary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_arrow_right" + tools:visibility="visible" /> </im.vector.app.core.platform.CheckableConstraintLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<im.vector.app.core.platform.CheckableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root" + android:layout_width="match_parent" + android:layout_height="40dp" + android:background="@drawable/bg_space_item" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground" + tools:viewBindingIgnore="true"> + + <Space + android:id="@+id/indent" + android:layout_width="20dp" + android:layout_height="match_parent" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> + + <ImageView + android:id="@+id/avatar" + android:layout_width="26dp" + android:layout_height="26dp" + android:layout_gravity="center" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:duplicateParentState="true" + android:importantForAccessibility="no" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/indent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@sample/space_avatars" /> + + <im.vector.app.features.home.room.list.UnreadCounterBadgeView + android:id="@+id/notification_badge" + style="@style/Widget.Vector.TextView.Micro" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" + android:gravity="center" + android:minWidth="16dp" + android:minHeight="16dp" + android:paddingStart="4dp" + android:paddingEnd="4dp" + android:textColor="?colorOnError" + android:visibility="gone" + app:layout_constraintCircle="@id/avatar" + app:layout_constraintCircleAngle="45" + app:layout_constraintCircleRadius="14dp" + tools:background="@drawable/bg_unread_highlight" + tools:text="147" + tools:visibility="visible" /> + + <TextView + android:id="@+id/name" + style="@style/Widget.Vector.TextView.Subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:layout_marginEnd="@dimen/layout_horizontal_margin" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?vctr_content_primary" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/chevron" + app:layout_constraintStart_toEndOf="@id/avatar" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@tools:sample/lorem/random" /> + + <ImageView + android:id="@+id/chevron" + android:layout_width="24dp" + android:layout_height="32dp" + android:layout_marginEnd="24dp" + android:background="?selectableItemBackground" + android:importantForAccessibility="no" + android:scaleType="centerInside" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:tint="?vctr_content_secondary" + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_arrow_right" + tools:visibility="visible" /> + +</im.vector.app.core.platform.CheckableConstraintLayout> From 4bbc04a838c20dfb281a9a75340782447a81bbe3 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 22 Aug 2022 14:09:54 +0200 Subject: [PATCH 2/8] Refactors space controller and items --- .../spaces/NewSpaceSummaryController.kt | 187 ++++++++---------- .../features/spaces/NewSpaceSummaryItem.kt | 15 +- .../features/spaces/NewSubSpaceSummaryItem.kt | 18 +- 3 files changed, 93 insertions(+), 127 deletions(-) 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 2141a6861d..b3131bff4d 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 @@ -63,96 +63,13 @@ class NewSpaceSummaryController @Inject constructor( homeCount: RoomAggregateNotificationCount, expandedStates: Map<String, Boolean>, ) { - println(homeCount) - val host = this newSpaceListHeaderItem { id("space_list_header") } - 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") - listener { host.callback?.onAddSpaceSelected() } - } - } - - private fun buildSubSpace( - idPrefix: String, - summaries: List<RoomSummary>?, - expandedStates: Map<String, Boolean>, - 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) - } - } + addHomeItem(selectedSpace == null, homeCount) + addSpaces(spaceSummaries, selectedSpace, rootSpaces, expandedStates) + addCreateItem() } private fun addHomeItem(selected: Boolean, homeCount: RoomAggregateNotificationCount) { @@ -166,38 +83,88 @@ class NewSpaceSummaryController @Inject constructor( } } - private fun addSubSpaces( - selectedSpace: RoomSummary, + private fun addSpaces( spaceSummaries: List<RoomSummary>?, - homeCount: RoomAggregateNotificationCount, + selectedSpace: RoomSummary?, + rootSpaces: List<RoomSummary>?, + expandedStates: Map<String, Boolean>, ) { val host = this - val spaceChildren = selectedSpace.spaceChildren - var subSpacesAdded = false - spaceChildren?.sortedWith(subSpaceComparator)?.forEach { spaceChild -> - val subSpaceSummary = spaceSummaries?.firstOrNull { it.roomId == spaceChild.childRoomId } ?: return@forEach + rootSpaces?.filter { it.membership != Membership.INVITE } + ?.forEach { spaceSummary -> + val subSpaces = spaceSummary.spaceChildren?.filter { spaceChild -> spaceSummaries.containsSpaceId(spaceChild.childRoomId) } + val hasChildren = (subSpaces?.size ?: 0) > 0 + val isSelected = spaceSummary.roomId == selectedSpace?.roomId + val expanded = expandedStates[spaceSummary.roomId] == true - if (subSpaceSummary.membership != Membership.INVITE) { - subSpacesAdded = true - newSpaceSummaryItem { - avatarRenderer(host.avatarRenderer) - id(subSpaceSummary.roomId) - matrixItem(subSpaceSummary.toMatrixItem()) - selected(false) - onSpaceSelectedListener { host.callback?.onSpaceSelected(subSpaceSummary) } - countState( - UnreadCounterBadgeView.State( - subSpaceSummary.notificationCount, - subSpaceSummary.highlightCount > 0 - ) - ) + newSpaceSummaryItem { + id(spaceSummary.roomId) + avatarRenderer(host.avatarRenderer) + countState(UnreadCounterBadgeView.State(spaceSummary.notificationCount, spaceSummary.highlightCount > 0)) + expanded(expanded) + hasChildren(hasChildren) + matrixItem(spaceSummary.toMatrixItem()) + onLongClickListener { host.callback?.onSpaceSettings(spaceSummary) } + onSpaceSelectedListener { host.callback?.onSpaceSelected(spaceSummary) } + onToggleExpandListener { host.callback?.onToggleExpand(spaceSummary) } + selected(isSelected) + } + + if (hasChildren && expanded) { + subSpaces?.forEach { child -> + addSubSpace(spaceSummary.roomId, spaceSummaries, expandedStates, selectedSpace, child, 1, 3) + } + } } - } + } + + private fun List<RoomSummary>?.containsSpaceId(spaceId: String) = this?.any { it.roomId == spaceId }.orFalse() + + private fun addSubSpace( + idPrefix: String, + spaceSummaries: List<RoomSummary>?, + expandedStates: Map<String, Boolean>, + selectedSpace: RoomSummary?, + info: SpaceChildInfo, + currentDepth: Int, + maxDepth: Int, + ) { + val host = this + if (currentDepth >= maxDepth) return + val childSummary = spaceSummaries?.firstOrNull { it.roomId == info.childRoomId } ?: return + val id = "$idPrefix:${childSummary.roomId}" + val countState = UnreadCounterBadgeView.State(childSummary.notificationCount, childSummary.highlightCount > 0) + val expanded = expandedStates[childSummary.roomId] == true + val isSelected = childSummary.roomId == selectedSpace?.roomId + val subSpaces = childSummary.spaceChildren?.filter { childSpace -> spaceSummaries.containsSpaceId(childSpace.childRoomId) }?.sortedWith(subSpaceComparator) + + newSubSpaceSummaryItem { + id(id) + avatarRenderer(host.avatarRenderer) + countState(countState) + expanded(expanded) + hasChildren(!subSpaces.isNullOrEmpty()) + indent(currentDepth) + matrixItem(childSummary.toMatrixItem()) + onLongClickListener { host.callback?.onSpaceSettings(childSummary) } + onSubSpaceSelectedListener { host.callback?.onSpaceSelected(childSummary) } + onToggleExpandListener { host.callback?.onToggleExpand(childSummary) } + selected(isSelected) } - if (!subSpacesAdded) { - addHomeItem(false, homeCount) + if (expanded) { + subSpaces?.forEach { + addSubSpace(id, spaceSummaries, expandedStates, selectedSpace, it, currentDepth + 1, maxDepth) + } + } + } + + private fun addCreateItem() { + val host = this + newSpaceAddItem { + id("create") + listener { host.callback?.onAddSpaceSelected() } } } 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 a3a88b0eb7..48a5a912a4 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 @@ -35,24 +35,23 @@ import org.matrix.android.sdk.api.util.MatrixItem abstract class NewSpaceSummaryItem : VectorEpoxyModel<NewSpaceSummaryItem.Holder>(R.layout.item_new_space) { @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute lateinit var matrixItem: MatrixItem - @EpoxyAttribute var selected: Boolean = false - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onSpaceSelectedListener: ClickListener? = null - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: ClickListener? = null @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) - - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: ClickListener? = null @EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute var hasChildren: Boolean = false + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onLongClickListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onSpaceSelectedListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onToggleExpandListener: ClickListener? = null + @EpoxyAttribute var selected: Boolean = false override fun bind(holder: Holder) { super.bind(holder) holder.root.onClick(onSpaceSelectedListener) - holder.root.setOnLongClickListener { onMore?.invoke(holder.root).let { true } } + holder.root.setOnLongClickListener { onLongClickListener?.invoke(holder.root).let { true } } holder.name.text = matrixItem.displayName holder.root.isChecked = selected - holder.chevron.setOnClickListener(toggleExpand) + holder.chevron.setOnClickListener(onToggleExpandListener) holder.chevron.isVisible = hasChildren holder.chevron.setImageResource(if (expanded) R.drawable.ic_expand_more else R.drawable.ic_arrow_right) 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 index e424a93cdd..8dd2aea9b3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/NewSubSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSubSpaceSummaryItem.kt @@ -38,22 +38,22 @@ import org.matrix.android.sdk.api.util.MatrixItem abstract class NewSubSpaceSummaryItem : VectorEpoxyModel<NewSubSpaceSummaryItem.Holder>(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 countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) @EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute var hasChildren: Boolean = false @EpoxyAttribute var indent: Int = 0 - @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onLongClickListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onSubSpaceSelectedListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onToggleExpandListener: ClickListener? = null + @EpoxyAttribute var selected: Boolean = false override fun bind(holder: Holder) { super.bind(holder) - holder.root.onClick(listener) + holder.root.onClick(onSubSpaceSelectedListener) holder.name.text = matrixItem.displayName holder.root.isChecked = selected - holder.root.setOnLongClickListener { onMore?.invoke(holder.root).let { true } } + holder.root.setOnLongClickListener { onLongClickListener?.invoke(holder.root).let { true } } holder.chevron.setImageDrawable( ContextCompat.getDrawable( @@ -61,7 +61,7 @@ abstract class NewSubSpaceSummaryItem : VectorEpoxyModel<NewSubSpaceSummaryItem. if (expanded) R.drawable.ic_expand_more else R.drawable.ic_arrow_right ) ) - holder.chevron.onClick(toggleExpand) + holder.chevron.onClick(onToggleExpandListener) holder.chevron.isVisible = hasChildren holder.indent.isVisible = indent > 0 From a7aa72fb6e0eaedb768c53cb07722cdc4401864f Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 22 Aug 2022 14:21:48 +0200 Subject: [PATCH 3/8] Adds changelog file --- changelog.d/6907.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6907.wip diff --git a/changelog.d/6907.wip b/changelog.d/6907.wip new file mode 100644 index 0000000000..a8d887c66b --- /dev/null +++ b/changelog.d/6907.wip @@ -0,0 +1 @@ +[New Layout] Changes space sheet to accordion-style with expandable subspaces From a1c79f8c132446871478d4d31ac3d64885a1067f Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 22 Aug 2022 14:31:29 +0200 Subject: [PATCH 4/8] Removes drag and drop repositioning for spaces --- .../app/features/spaces/SpaceListFragment.kt | 44 ------------------- 1 file changed, 44 deletions(-) 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 7b034356b4..9978e493c8 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 @@ -72,7 +72,6 @@ class SpaceListFragment @Inject constructor( private fun setupSpaceController() { if (vectorFeatures.isNewAppLayoutEnabled()) { - enableDragAndDropForNewSpaceController() newSpaceController.callback = this views.groupListView.configureWith(newSpaceController) } else { @@ -82,49 +81,6 @@ class SpaceListFragment @Inject constructor( } } - private fun enableDragAndDropForNewSpaceController() { - EpoxyTouchHelper.initDragging(newSpaceController) - .withRecyclerView(views.groupListView) - .forVerticalList() - .withTarget(NewSpaceSummaryItem::class.java) - .andCallbacks(object : EpoxyTouchHelper.DragCallbacks<NewSpaceSummaryItem>() { - var toPositionM: Int? = null - var fromPositionM: Int? = null - var initialElevation: Float? = null - - override fun onDragStarted(model: NewSpaceSummaryItem?, itemView: View?, adapterPosition: Int) { - toPositionM = null - fromPositionM = null - model?.matrixItem?.id?.let { - viewModel.handle(SpaceListAction.OnStartDragging(it, false)) - } - itemView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - initialElevation = itemView?.elevation - itemView?.elevation = 6f - } - - override fun onDragReleased(model: NewSpaceSummaryItem?, itemView: View?) { - if (toPositionM == null || fromPositionM == null) return - val movedSpaceId = model?.matrixItem?.id ?: return - viewModel.handle(SpaceListAction.MoveSpace(movedSpaceId, toPositionM!! - fromPositionM!!)) - } - - override fun clearView(model: NewSpaceSummaryItem?, itemView: View?) { - itemView?.elevation = initialElevation ?: 0f - } - - override fun onModelMoved(fromPosition: Int, toPosition: Int, modelBeingMoved: NewSpaceSummaryItem?, itemView: View?) { - if (fromPositionM == null) { - fromPositionM = fromPosition - } - if (toPositionM != toPosition) { - toPositionM = toPosition - itemView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - } - } - }) - } - private fun enableDragAndDropForSpaceController() { EpoxyTouchHelper.initDragging(spaceController) .withRecyclerView(views.groupListView) From 4c404ca4ce1b569b007ce1ddc02f172beb10382c Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 22 Aug 2022 14:34:44 +0200 Subject: [PATCH 5/8] Removes max depth on subspaces --- .../app/features/spaces/NewSpaceSummaryController.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 b3131bff4d..3169fefcb5 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 @@ -113,7 +113,7 @@ class NewSpaceSummaryController @Inject constructor( if (hasChildren && expanded) { subSpaces?.forEach { child -> - addSubSpace(spaceSummary.roomId, spaceSummaries, expandedStates, selectedSpace, child, 1, 3) + addSubSpace(spaceSummary.roomId, spaceSummaries, expandedStates, selectedSpace, child, 1) } } } @@ -127,11 +127,9 @@ class NewSpaceSummaryController @Inject constructor( expandedStates: Map<String, Boolean>, selectedSpace: RoomSummary?, info: SpaceChildInfo, - currentDepth: Int, - maxDepth: Int, + depth: Int, ) { val host = this - if (currentDepth >= maxDepth) return val childSummary = spaceSummaries?.firstOrNull { it.roomId == info.childRoomId } ?: return val id = "$idPrefix:${childSummary.roomId}" val countState = UnreadCounterBadgeView.State(childSummary.notificationCount, childSummary.highlightCount > 0) @@ -145,7 +143,7 @@ class NewSpaceSummaryController @Inject constructor( countState(countState) expanded(expanded) hasChildren(!subSpaces.isNullOrEmpty()) - indent(currentDepth) + indent(depth) matrixItem(childSummary.toMatrixItem()) onLongClickListener { host.callback?.onSpaceSettings(childSummary) } onSubSpaceSelectedListener { host.callback?.onSpaceSelected(childSummary) } @@ -155,7 +153,7 @@ class NewSpaceSummaryController @Inject constructor( if (expanded) { subSpaces?.forEach { - addSubSpace(id, spaceSummaries, expandedStates, selectedSpace, it, currentDepth + 1, maxDepth) + addSubSpace(id, spaceSummaries, expandedStates, selectedSpace, it, depth + 1) } } } From 8892fac062cacedc01d39f11dc98f6959cca743a Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 22 Aug 2022 14:41:48 +0200 Subject: [PATCH 6/8] Adds content description to space chevrons --- .../im/vector/app/features/spaces/NewSpaceSummaryController.kt | 3 ++- vector/src/main/res/layout/item_new_space.xml | 2 +- vector/src/main/res/layout/item_new_sub_space.xml | 2 +- vector/src/main/res/values/strings.xml | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) 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 3169fefcb5..2b45db2e4e 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 @@ -135,7 +135,8 @@ class NewSpaceSummaryController @Inject constructor( val countState = UnreadCounterBadgeView.State(childSummary.notificationCount, childSummary.highlightCount > 0) val expanded = expandedStates[childSummary.roomId] == true val isSelected = childSummary.roomId == selectedSpace?.roomId - val subSpaces = childSummary.spaceChildren?.filter { childSpace -> spaceSummaries.containsSpaceId(childSpace.childRoomId) }?.sortedWith(subSpaceComparator) + val subSpaces = childSummary.spaceChildren?.filter { childSpace -> spaceSummaries.containsSpaceId(childSpace.childRoomId) } + ?.sortedWith(subSpaceComparator) newSubSpaceSummaryItem { id(id) diff --git a/vector/src/main/res/layout/item_new_space.xml b/vector/src/main/res/layout/item_new_space.xml index 3d627fa782..fc023ebd6e 100644 --- a/vector/src/main/res/layout/item_new_space.xml +++ b/vector/src/main/res/layout/item_new_space.xml @@ -66,7 +66,7 @@ android:layout_height="48dp" android:layout_marginEnd="21dp" android:background="?selectableItemBackground" - android:importantForAccessibility="no" + android:contentDescription="@string/a11y_expand_space_children" android:scaleType="centerInside" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" diff --git a/vector/src/main/res/layout/item_new_sub_space.xml b/vector/src/main/res/layout/item_new_sub_space.xml index 1c4afcea18..014568e26d 100644 --- a/vector/src/main/res/layout/item_new_sub_space.xml +++ b/vector/src/main/res/layout/item_new_sub_space.xml @@ -79,7 +79,7 @@ android:layout_height="32dp" android:layout_marginEnd="24dp" android:background="?selectableItemBackground" - android:importantForAccessibility="no" + android:contentDescription="@string/a11y_expand_space_children" android:scaleType="centerInside" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 0b62c16f92..bc247c38aa 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -141,6 +141,7 @@ <string name="create_room">Create Room</string> <string name="change_space">Change Space</string> <string name="explore_rooms">Explore Rooms</string> + <string name="a11y_expand_space_children">Expand space children</string> <!-- Last seen time --> From e9120c8e33731fa0d2dad5311ff85e49eb83f7e0 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Wed, 24 Aug 2022 20:22:46 +0200 Subject: [PATCH 7/8] Changes content description of chevron based on expanded state --- .../im/vector/app/features/spaces/NewSpaceSummaryItem.kt | 7 ++++++- vector/src/main/res/layout/item_new_space.xml | 1 - vector/src/main/res/values/strings.xml | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) 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 48a5a912a4..f6a4781860 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 @@ -46,14 +46,19 @@ abstract class NewSpaceSummaryItem : VectorEpoxyModel<NewSpaceSummaryItem.Holder override fun bind(holder: Holder) { super.bind(holder) + val context = holder.root.context holder.root.onClick(onSpaceSelectedListener) - holder.root.setOnLongClickListener { onLongClickListener?.invoke(holder.root).let { true } } + holder.root.setOnLongClickListener { + onLongClickListener?.invoke(holder.root) + true + } holder.name.text = matrixItem.displayName holder.root.isChecked = selected holder.chevron.setOnClickListener(onToggleExpandListener) holder.chevron.isVisible = hasChildren holder.chevron.setImageResource(if (expanded) R.drawable.ic_expand_more else R.drawable.ic_arrow_right) + holder.chevron.contentDescription = context.getString(if (expanded) R.string.a11y_collapse_space_children else R.string.a11y_expand_space_children) avatarRenderer.render(matrixItem, holder.avatar) holder.unreadCounter.render(countState) diff --git a/vector/src/main/res/layout/item_new_space.xml b/vector/src/main/res/layout/item_new_space.xml index fc023ebd6e..b198818cdd 100644 --- a/vector/src/main/res/layout/item_new_space.xml +++ b/vector/src/main/res/layout/item_new_space.xml @@ -66,7 +66,6 @@ android:layout_height="48dp" android:layout_marginEnd="21dp" android:background="?selectableItemBackground" - android:contentDescription="@string/a11y_expand_space_children" android:scaleType="centerInside" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index bc247c38aa..c5326c3c2f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -142,6 +142,7 @@ <string name="change_space">Change Space</string> <string name="explore_rooms">Explore Rooms</string> <string name="a11y_expand_space_children">Expand space children</string> + <string name="a11y_collapse_space_children">Collapse space children</string> <!-- Last seen time --> From d8398c279c7c579340daa8f79893bba951d37061 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Thu, 25 Aug 2022 16:32:31 +0200 Subject: [PATCH 8/8] Adds content description to fix lint warning --- vector/src/main/res/layout/item_new_space.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/item_new_space.xml b/vector/src/main/res/layout/item_new_space.xml index b198818cdd..fc023ebd6e 100644 --- a/vector/src/main/res/layout/item_new_space.xml +++ b/vector/src/main/res/layout/item_new_space.xml @@ -66,6 +66,7 @@ android:layout_height="48dp" android:layout_marginEnd="21dp" android:background="?selectableItemBackground" + android:contentDescription="@string/a11y_expand_space_children" android:scaleType="centerInside" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent"