From 1ec08bec0792e18deb864bcf82500293982d7f68 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 08:18:37 +0200 Subject: [PATCH 1/8] Fix spaces add room via server --- .../android/sdk/api/session/space/Space.kt | 2 +- .../session/permalinks/PermalinkFactory.kt | 42 +---------- .../session/permalinks/ViaParameterFinder.kt | 69 +++++++++++++++++++ .../sdk/internal/session/room/DefaultRoom.kt | 4 +- .../sdk/internal/session/room/RoomFactory.kt | 5 +- .../internal/session/space/DefaultSpace.kt | 10 ++- .../home/room/detail/RoomDetailViewModel.kt | 4 +- .../createroom/CreateRoomViewModel.kt | 3 +- .../spaces/manage/SpaceAddRoomsViewModel.kt | 2 +- 9 files changed, 91 insertions(+), 50 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 21daf0b37d..db25762c2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -33,7 +33,7 @@ interface Space { fun spaceSummary(): RoomSummary? suspend fun addChildren(roomId: String, - viaServers: List, + viaServers: List?, order: String?, autoJoin: Boolean = false, suggested: Boolean? = false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 71a6e224bf..970752449a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -18,19 +18,13 @@ package org.matrix.android.sdk.internal.session.permalinks import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE -import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams -import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.room.RoomGetter -import java.net.URLEncoder import javax.inject.Inject -import javax.inject.Provider internal class PermalinkFactory @Inject constructor( @UserId private val userId: String, - // Use a provider to fix circular Dagger dependency - private val roomGetterProvider: Provider + private val viaParameterFinder: ViaParameterFinder ) { fun createPermalink(event: Event): String? { @@ -50,12 +44,12 @@ internal class PermalinkFactory @Inject constructor( return if (roomId.isEmpty()) { null } else { - MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId) + MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId) } } fun createPermalink(roomId: String, eventId: String): String { - return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId) + return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + viaParameterFinder.computeViaParams(userId, roomId) } fun getLinkedId(url: String): String? { @@ -66,25 +60,6 @@ internal class PermalinkFactory @Inject constructor( } else null } - /** - * Compute the via parameters. - * Take up to 3 homeserver domains, taking the most representative one regarding room members and including the - * current user one. - */ - private fun computeViaParams(userId: String, roomId: String): String { - val userHomeserver = userId.substringAfter(":") - return getUserIdsOfJoinedMembers(roomId) - .map { it.substringAfter(":") } - .groupBy { it } - .mapValues { it.value.size } - .toMutableMap() - // Ensure the user homeserver will be included - .apply { this[userHomeserver] = Int.MAX_VALUE } - .let { map -> map.keys.sortedByDescending { map[it] } } - .take(3) - .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } - } - /** * Escape '/' in id, because it is used as a separator * @@ -104,15 +79,4 @@ internal class PermalinkFactory @Inject constructor( private fun unescape(id: String): String { return id.replace("%2F", "/") } - - /** - * Get a set of userIds of joined members of a room - */ - private fun getUserIdsOfJoinedMembers(roomId: String): Set { - return roomGetterProvider.get().getRoom(roomId) - ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) - ?.map { it.userId } - .orEmpty() - .toSet() - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt new file mode 100644 index 0000000000..0da60e9ba2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.permalinks + +import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.RoomGetter +import java.net.URLEncoder +import javax.inject.Inject +import javax.inject.Provider + +internal class ViaParameterFinder @Inject constructor( + @UserId private val userId: String, + private val roomGetterProvider: Provider +) { + + fun computeViaParams(roomId: String, max: Int): List { + return computeViaParams(userId, roomId, max) + } + + /** + * Compute the via parameters. + * Take up to 3 homeserver domains, taking the most representative one regarding room members and including the + * current user one. + */ + fun computeViaParams(userId: String, roomId: String): String { + return computeViaParams(userId, roomId, 3) + .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } + } + + fun computeViaParams(userId: String, roomId: String, max: Int): List { + val userHomeserver = userId.substringAfter(":") + return getUserIdsOfJoinedMembers(roomId) + .map { it.substringAfter(":") } + .groupBy { it } + .mapValues { it.value.size } + .toMutableMap() + // Ensure the user homeserver will be included + .apply { this[userHomeserver] = Int.MAX_VALUE } + .let { map -> map.keys.sortedByDescending { map[it] } } + .take(max) + } + + /** + * Get a set of userIds of joined members of a room + */ + private fun getUserIdsOfJoinedMembers(roomId: String): Set { + return roomGetterProvider.get().getRoom(roomId) + ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) + ?.map { it.userId } + .orEmpty() + .toSet() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index c6059f84ea..a5e066dae8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.search.SearchTask @@ -66,6 +67,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val roomMembersService: MembershipService, private val roomPushRuleService: RoomPushRuleService, private val sendStateTask: SendStateTask, + private val viaParameterFinder: ViaParameterFinder, private val searchTask: SearchTask) : Room, TimelineService by timelineService, @@ -154,6 +156,6 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, override fun asSpace(): Space? { if (roomSummary()?.roomType != RoomType.SPACE) return null - return DefaultSpace(this, roomSummaryDataSource) + return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 90640b4700..3f743c2922 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService @@ -60,6 +61,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, private val sendStateTask: SendStateTask, + private val viaParameterFinder: ViaParameterFinder, private val searchTask: SearchTask) : RoomFactory { @@ -83,7 +85,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomMembersService = membershipServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId), sendStateTask = sendStateTask, - searchTask = searchTask + searchTask = searchTask, + viaParameterFinder = viaParameterFinder ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index d6df991245..70c52bf4ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -24,11 +24,13 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.model.SpaceChildContent +import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource internal class DefaultSpace( private val room: Room, - private val spaceSummaryDataSource: RoomSummaryDataSource + private val spaceSummaryDataSource: RoomSummaryDataSource, + private val viaParameterFinder: ViaParameterFinder ) : Space { override fun asRoom(): Room { @@ -46,15 +48,17 @@ internal class DefaultSpace( } override suspend fun addChildren(roomId: String, - viaServers: List, + viaServers: List?, order: String?, autoJoin: Boolean, suggested: Boolean?) { + // Find best via + room.sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( - via = viaServers, + via = viaServers ?: viaParameterFinder.computeViaParams(roomId, 3), autoJoin = autoJoin, order = order, suggested = suggested diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 52779c863d..cd7f6a5730 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -832,7 +832,7 @@ class RoomDetailViewModel @AssistedInject constructor( session.spaceService().getSpace(spaceId) ?.addChildren( state.roomId, - listOf(session.sessionParams.homeServerHost ?: ""), + null, null, true ) @@ -849,7 +849,7 @@ class RoomDetailViewModel @AssistedInject constructor( session.spaceService().getSpace(slashCommandResult.spaceId) ?.addChildren( room.roomId, - listOf(session.sessionParams.homeServerHost ?: ""), + null, null, false ) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 4eb05fb697..8c8d17dd46 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -236,10 +236,9 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init if (initialState.parentSpaceId != null) { // add it as a child try { - val via = session.sessionParams.homeServerHost?.let { listOf(it) }.orEmpty() session.spaceService() .getSpace(initialState.parentSpaceId) - ?.addChildren(roomId, viaServers = via, order = null) + ?.addChildren(roomId, viaServers = null, order = null) } catch (failure: Throwable) { Timber.w(failure, "Failed to add as a child") } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index ce677e39c7..55e65eb171 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -174,7 +174,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( try { session.spaceService().getSpace(initialState.spaceId)!!.addChildren( roomId = roomId, - viaServers = listOf(session.sessionParams.homeServerHost ?: ""), + viaServers = null, order = null ) completed.add(roomId) From 24a32f2b7e33d6f22c50f9735de9f8a9a4839454 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 10:07:34 +0200 Subject: [PATCH 2/8] Subspace more hierarchy depth + fix bad notification count --- .../grouplist/HomeSpaceSummaryItem.kt | 6 +- .../features/spaces/SpaceSummaryController.kt | 60 +++++--- .../app/features/spaces/SpaceSummaryItem.kt | 12 +- .../features/spaces/SubSpaceSummaryItem.kt | 106 ++++++++++++++ vector/src/main/res/layout/item_sub_space.xml | 132 ++++++++++++++++++ 5 files changed, 292 insertions(+), 24 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt create mode 100644 vector/src/main/res/layout/item_sub_space.xml diff --git a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt index 553f82e98f..ddda38aa9c 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt @@ -18,6 +18,7 @@ package im.vector.app.features.grouplist import android.content.res.ColorStateList +import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat @@ -35,8 +36,9 @@ import im.vector.app.features.themes.ThemeUtils abstract class HomeSpaceSummaryItem : VectorEpoxyModel() { @EpoxyAttribute var selected: Boolean = false - @EpoxyAttribute var listener: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: (() -> Unit)? = null @EpoxyAttribute var countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + @EpoxyAttribute var showSeparator: Boolean = false override fun getViewType(): Int { // mm.. it's reusing the same layout for basic space item @@ -56,6 +58,7 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel(R.id.itemGroupLayout) val leaveView by bind(R.id.groupTmpLeave) val counterBadgeView by bind(R.id.groupCounterBadge) + val bottomSeparator by bind(R.id.groupBottomSeparator) } } 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 4b78a999bb..6bd56c9d81 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 @@ -31,8 +31,8 @@ import im.vector.app.space import org.matrix.android.sdk.api.session.group.model.GroupSummary 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 import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -163,24 +163,7 @@ class SpaceSummaryController @Inject constructor( if (hasChildren && expanded) { // it's expanded subSpaces?.forEach { child -> - summaries?.firstOrNull { it.roomId == child.childRoomId }?.let { childSum -> - val isChildSelected = selected is RoomGroupingMethod.BySpace && childSum.roomId == selected.space()?.roomId - spaceSummaryItem { - avatarRenderer(avatarRenderer) - id(child.childRoomId) - hasChildren(false) - selected(isChildSelected) - matrixItem(MatrixItem.RoomItem(child.childRoomId, child.name, child.avatarUrl)) - listener { callback?.onSpaceSelected(childSum) } - indent(1) - countState( - UnreadCounterBadgeView.State( - groupSummary.notificationCount, - groupSummary.highlightCount > 0 - ) - ) - } - } + buildSubSpace(summaries, expandedStates, selected, child, 1, 3) } } } @@ -191,6 +174,45 @@ class SpaceSummaryController @Inject constructor( } } + private fun buildSubSpace(summaries: List?, + expandedStates: Map, + selected: RoomGroupingMethod, + childSum: SpaceChildInfo, currentDepth: Int, maxDepth: Int) { + if (currentDepth >= maxDepth) return + val childSum = summaries?.firstOrNull { it.roomId == childSum.childRoomId } ?: return + // does it have children? + val subSpaces = childSum.spaceChildren?.filter { childInfo -> + summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 + } + val expanded = expandedStates[childSum.roomId] == true + val isSelected = selected is RoomGroupingMethod.BySpace && childSum.roomId == selected.space()?.roomId + + subSpaceSummaryItem { + avatarRenderer(avatarRenderer) + id(childSum.roomId) + hasChildren(!subSpaces.isNullOrEmpty()) + selected(isSelected) + expanded(expanded) + onMore { callback?.onSpaceSettings(childSum) } + matrixItem(childSum.toMatrixItem()) + listener { callback?.onSpaceSelected(childSum) } + toggleExpand { callback?.onToggleExpand(childSum) } + indent(currentDepth) + countState( + UnreadCounterBadgeView.State( + childSum.notificationCount, + childSum.highlightCount > 0 + ) + ) + } + + if (expanded) { + subSpaces?.forEach { + buildSubSpace(summaries, expandedStates, selected, it, currentDepth + 1, maxDepth) + } + } + } + interface Callback { fun onSpaceSelected(spaceSummary: RoomSummary?) fun onSpaceInviteSelected(spaceSummary: RoomSummary) 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 013f7d6db5..c029f4b25e 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 @@ -16,6 +16,7 @@ package im.vector.app.features.spaces +import android.view.View import android.widget.ImageView import android.widget.Space import android.widget.TextView @@ -37,17 +38,18 @@ import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass(layout = R.layout.item_space) abstract class SpaceSummaryItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute var selected: Boolean = false - @EpoxyAttribute var listener: (() -> Unit)? = null - @EpoxyAttribute var onMore: (() -> Unit)? = null - @EpoxyAttribute var toggleExpand: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: (() -> Unit)? = 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) @EpoxyAttribute var description: String? = null + @EpoxyAttribute var showSeparator: Boolean = false override fun bind(holder: Holder) { super.bind(holder) @@ -83,6 +85,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { } holder.indentSpace.isVisible = indent > 0 + holder.separator.isVisible = showSeparator avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) holder.counterBadgeView.render(countState) @@ -102,5 +105,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { val collapseIndicator by bind(R.id.groupChildrenCollapse) val indentSpace by bind(R.id.indent) val counterBadgeView by bind(R.id.groupCounterBadge) + val separator by bind(R.id.groupBottomSeparator) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt new file mode 100644 index 0000000000..b98f1ca7af --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt @@ -0,0 +1,106 @@ +/* + * 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.isGone +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.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.platform.CheckableConstraintLayout +import im.vector.app.core.utils.DebouncedClickListener +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(layout = R.layout.item_sub_space) +abstract class SubSpaceSummaryItem : VectorEpoxyModel() { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var selected: Boolean = false + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: (() -> Unit)? = 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.rootView.setOnClickListener { listener?.invoke() } + holder.groupNameView.text = matrixItem.displayName + holder.rootView.isChecked = selected + if (onMore != null) { + holder.moreView.isVisible = true + holder.moreView.setOnClickListener( + DebouncedClickListener({ _ -> + onMore?.invoke() + }) + ) + } else { + holder.moreView.isVisible = false + } + + if (hasChildren) { + 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 + holder.indentSpace.updateLayoutParams { + width = indent * 30 + } + + avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) + holder.counterBadgeView.render(countState) + } + + override fun unbind(holder: Holder) { + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val avatarImageView by bind(R.id.groupAvatarImageView) + val groupNameView by bind(R.id.groupNameView) + 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) + val counterBadgeView by bind(R.id.groupCounterBadge) + } +} diff --git a/vector/src/main/res/layout/item_sub_space.xml b/vector/src/main/res/layout/item_sub_space.xml new file mode 100644 index 0000000000..e01b9a4cc7 --- /dev/null +++ b/vector/src/main/res/layout/item_sub_space.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 14144dc99d222a3e190fd1d7caf2b0ad0e8c6bd9 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 10:45:07 +0200 Subject: [PATCH 3/8] Default all in home + lab flag --- .../home/room/list/RoomListViewModel.kt | 7 ++-- .../room/list/SpaceRoomListSectionBuilder.kt | 42 +++++++++++-------- .../features/settings/VectorPreferences.kt | 5 +++ .../settings/VectorSettingsLabsFragment.kt | 10 ++++- vector/src/main/res/values/strings.xml | 3 ++ .../src/main/res/xml/vector_settings_labs.xml | 5 +++ 6 files changed, 51 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index bc24705e13..246d5052cf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -69,12 +69,12 @@ class RoomListViewModel @Inject constructor( * Filter the rooms if they are part of the current space (children and grand children). * If current space is null, will return orphan rooms only */ - NORMAL, + ORPHANS_IF_SPACE_NULL, /** * Special case when we don't want to discriminate rooms when current space is null. * In this case return all. */ - NOT_IF_ALL, + ALL_IF_SPACE_NULL, /** Do not filter based on space*/ NONE } @@ -131,7 +131,8 @@ class RoomListViewModel @Inject constructor( }, { updatableQuery = it - } + }, + vectorPreferences.labsSpacesOnlyOrphansInHome() ).buildSections(initialState.displayMode) } else { GroupRoomListSectionBuilder( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index 351c72ab48..852efb9193 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -50,7 +50,8 @@ class SpaceRoomListSectionBuilder( val viewModelScope: CoroutineScope, private val suggestedRoomJoiningState: LiveData>>, val onDisposable: (Disposable) -> Unit, - val onUdpatable: (UpdatableLivePageResult) -> Unit + val onUdpatable: (UpdatableLivePageResult) -> Unit, + val onlyOrphansInHome: Boolean = false ) : RoomListSectionBuilder { val pagedListConfig = PagedList.Config.Builder() @@ -91,7 +92,8 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.invitations_header, true, - RoomListViewModel.SpaceFilterStrategy.NORMAL + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } + ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ALL @@ -102,7 +104,8 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.bottom_action_rooms, false, - RoomListViewModel.SpaceFilterStrategy.NORMAL + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } + ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS @@ -129,7 +132,7 @@ class SpaceRoomListSectionBuilder( sections, activeSpaceAwareQueries, R.string.invitations_header, true, - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -140,7 +143,7 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.bottom_action_favourites, false, - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -152,7 +155,8 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.bottom_action_rooms, false, - RoomListViewModel.SpaceFilterStrategy.NORMAL + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } + ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -164,7 +168,8 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.low_priority_header, false, - RoomListViewModel.SpaceFilterStrategy.NORMAL + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } + ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -176,7 +181,8 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.system_alerts_header, false, - RoomListViewModel.SpaceFilterStrategy.NORMAL + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } + ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -232,7 +238,7 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.invitations_header, true, - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -242,7 +248,7 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.bottom_action_favourites, false, - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -253,7 +259,7 @@ class SpaceRoomListSectionBuilder( activeSpaceAwareQueries, R.string.bottom_action_people_x, false, - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -277,7 +283,7 @@ class SpaceRoomListSectionBuilder( pagedListConfig ).also { when (spaceFilterStrategy) { - RoomListViewModel.SpaceFilterStrategy.NORMAL -> { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> { activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater { override fun updateForSpaceId(roomId: String?) { it.updateQuery { @@ -288,7 +294,7 @@ class SpaceRoomListSectionBuilder( } }) } - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL -> { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater { override fun updateForSpaceId(roomId: String?) { if (roomId != null) { @@ -349,21 +355,23 @@ class SpaceRoomListSectionBuilder( internal fun RoomSummaryQueryParams.process(spaceFilter: RoomListViewModel.SpaceFilterStrategy, currentSpace: String?): RoomSummaryQueryParams { return when (spaceFilter) { - RoomListViewModel.SpaceFilterStrategy.NORMAL -> { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> { copy( activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) ) } - RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL -> { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { if (currentSpace == null) { - this + copy( + activeSpaceFilter = ActiveSpaceFilter.None + ) } else { copy( activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) ) } } - RoomListViewModel.SpaceFilterStrategy.NONE -> this + RoomListViewModel.SpaceFilterStrategy.NONE -> this } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index b44d44aba0..9fc496a914 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -151,6 +151,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS" const val SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE = "SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE" + const val SETTINGS_LABS_SPACES_HOME_AS_ORPHAN = "SETTINGS_LABS_SPACES_HOME_AS_ORPHAN" private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY" private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY" @@ -956,6 +957,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE, false) } + fun labsSpacesOnlyOrphansInHome(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_SPACES_HOME_AS_ORPHAN, false) + } + /* * Photo / video picker */ diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 6a4ea83d68..ae6b24e4e4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -17,6 +17,9 @@ package im.vector.app.features.settings import im.vector.app.R +import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.features.MainActivity +import im.vector.app.features.MainActivityArgs import javax.inject.Inject class VectorSettingsLabsFragment @Inject constructor( @@ -27,6 +30,11 @@ class VectorSettingsLabsFragment @Inject constructor( override val preferenceXmlRes = R.xml.vector_settings_labs override fun bindPref() { - // Nothing to do + findPreference(VectorPreferences.SETTINGS_LABS_SPACES_HOME_AS_ORPHAN)!!.let { pref -> + pref.setOnPreferenceChangeListener { _, _ -> + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = false)) + true + } + } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index cea541410b..9d2763e243 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3362,4 +3362,7 @@ Mark as suggested Mark as not suggested Manage rooms and spaces + + + Experimental Space - Only show orphans in Home diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 607f01db35..1e0f736db2 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -52,4 +52,9 @@ android:summary="@string/labs_use_restricted_join_rule_desc"/> + + \ No newline at end of file From f9820cde58715013cb0a2fdcb25f8caccd667a0c Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 11:36:17 +0200 Subject: [PATCH 4/8] Fixes on unread badges --- .../room/summary/RoomSummaryUpdater.kt | 2 + .../app/features/home/HomeDetailViewModel.kt | 2 +- .../home/UnreadMessagesSharedViewModel.kt | 18 ++++++- .../room/list/SpaceRoomListSectionBuilder.kt | 50 ++++++++++++------- .../features/spaces/SpacesListViewModel.kt | 28 ++++++++--- 5 files changed, 70 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 63fcb557a8..cac0d715f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -364,6 +364,8 @@ internal class RoomSummaryUpdater @Inject constructor( realm.where(RoomSummaryEntity::class.java) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN)) .notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE) + // also we do not count DM in here, because home space will already show them + .equalTo(RoomSummaryEntityFields.IS_DIRECT, false) .contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId) .findAll().forEach { highlightCount += it.highlightCount diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index be5c72330e..836c63e85b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -202,7 +202,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho setState { copy( notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites, - notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight, + notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight || (dmInvites + roomsInvite) > 0, notificationCountPeople = dmRooms.totalCount + dmInvites, notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0, notificationCountRooms = otherRooms.totalCount + roomsInvite, diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 43c878624b..79450321ba 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.settings.VectorPreferences import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -52,6 +53,7 @@ data class CountInfo( class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState, session: Session, + private val vectorPreferences: VectorPreferences, appStateHandler: AppStateHandler) : VectorViewModel(initialState) { @@ -126,12 +128,24 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia } is RoomGroupingMethod.BySpace -> { val selectedSpace = appStateHandler.safeActiveSpaceId() - val counts = session.getNotificationCountForRooms( + + val inviteCount = session.getRoomSummaries(roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + }).size + + val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { + vectorPreferences.labsSpacesOnlyOrphansInHome() + } ?: ActiveSpaceFilter.None } ) + + val counts = RoomAggregateNotificationCount( + totalCount.notificationCount + inviteCount, + totalCount.highlightCount + inviteCount + ) val rootCounts = session.spaceService().getRootSpaceSummaries() .filter { // filter out current selection diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index 852efb9193..2cd0199062 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.rx.asObservable class SpaceRoomListSectionBuilder( @@ -88,12 +89,13 @@ class SpaceRoomListSectionBuilder( } RoomListDisplayMode.NOTIFICATIONS -> { addSection( - sections, - activeSpaceAwareQueries, - R.string.invitations_header, - true, - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } - ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } + ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true ) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ALL @@ -129,10 +131,12 @@ class SpaceRoomListSectionBuilder( private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { addSection( - sections, activeSpaceAwareQueries, - R.string.invitations_header, - true, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true ) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -234,11 +238,12 @@ class SpaceRoomListSectionBuilder( } private fun buildDmSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { - addSection(sections, - activeSpaceAwareQueries, - R.string.invitations_header, - true, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + addSection(sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true ) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -272,6 +277,7 @@ class SpaceRoomListSectionBuilder( @StringRes nameRes: Int, notifyOfLocalEcho: Boolean = false, spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE, + countRoomAsNotif: Boolean = false, query: (RoomSummaryQueryParams.Builder) -> Unit) { withQueryParams( { query.invoke(it) }, @@ -313,7 +319,7 @@ class SpaceRoomListSectionBuilder( } }) } - RoomListViewModel.SpaceFilterStrategy.NONE -> { + RoomListViewModel.SpaceFilterStrategy.NONE -> { // we ignore current space for this one } } @@ -326,9 +332,15 @@ class SpaceRoomListSectionBuilder( .subscribe { sections.find { it.sectionName == name } ?.notificationCount - ?.postValue(session.getNotificationCountForRooms( - roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) - )) + ?.postValue( + if (countRoomAsNotif) { + RoomAggregateNotificationCount(it.size, it.size) + } else { + session.getNotificationCountForRooms( + roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) + ) + } + ) }.also { onDisposable.invoke(it) } 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 9b2cffc59c..eec604d318 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 @@ -28,8 +28,7 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.resources.StringProvider -import im.vector.app.features.ui.UiStateRepository +import im.vector.app.features.settings.VectorPreferences import im.vector.app.group import im.vector.app.space import io.reactivex.Observable @@ -45,6 +44,7 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder 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.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.rx.asObservable @@ -54,8 +54,7 @@ import java.util.concurrent.TimeUnit class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, private val appStateHandler: AppStateHandler, private val session: Session, - private val stringProvider: StringProvider, - private val uiStateRepository: UiStateRepository + private val vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { @AssistedFactory @@ -108,21 +107,34 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } }.disposeOnClear() + // XXX there should be a way to refactor this and share it session.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { + vectorPreferences.labsSpacesOnlyOrphansInHome() + } ?: ActiveSpaceFilter.None }, sortOrder = RoomSortOrder.NONE ).asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) .subscribe { - val counts = session.getNotificationCountForRooms( + val inviteCount = session.getRoomSummaries(roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + }).size + + val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { + vectorPreferences.labsSpacesOnlyOrphansInHome() + } ?: ActiveSpaceFilter.None } ) + val counts = RoomAggregateNotificationCount( + totalCount.notificationCount + inviteCount, + totalCount.highlightCount + inviteCount + ) setState { copy( homeAggregateCount = counts @@ -217,7 +229,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .rx() .liveUser(session.myUserId) .map { - it.getOrNull() + it.getOrNull() }, session .rx() From 91d8ee2a81348e5a25b9e9bd0a8cdaaea57a1f5f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 14:45:30 +0200 Subject: [PATCH 5/8] Space panel room ordering --- .../vector/app/features/spaces/SpaceSummaryController.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 6bd56c9d81..bd190519aa 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 @@ -43,6 +43,8 @@ class SpaceSummaryController @Inject constructor( var callback: Callback? = null private var viewState: SpaceListViewState? = null + private val subSpaceComparator: Comparator = compareBy { it.order }.thenBy { it.childRoomId } + init { requestModelBuild() } @@ -132,13 +134,13 @@ class SpaceSummaryController @Inject constructor( } rootSpaces - ?.sortedBy { it.displayName } + ?.sortedBy { it.roomId } ?.forEach { groupSummary -> val isSelected = selected is RoomGroupingMethod.BySpace && groupSummary.roomId == selected.space()?.roomId // does it have children? val subSpaces = groupSummary.spaceChildren?.filter { childInfo -> summaries?.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 - } + }?.sortedWith(subSpaceComparator) val hasChildren = (subSpaces?.size ?: 0) > 0 val expanded = expandedStates[groupSummary.roomId] == true @@ -183,7 +185,7 @@ class SpaceSummaryController @Inject constructor( // does it have children? val subSpaces = childSum.spaceChildren?.filter { childInfo -> summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 - } + }?.sortedWith(subSpaceComparator) val expanded = expandedStates[childSum.roomId] == true val isSelected = selected is RoomGroupingMethod.BySpace && childSum.roomId == selected.space()?.roomId From eb9fadaebfd137db84068f8b9fa5ca3350427eef Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 17:34:26 +0200 Subject: [PATCH 6/8] user error when no space support on HS remove buggy separators on add room; design update --- .../app/core/ui/list/GenericPillItem.kt | 85 +++++++++++++++++++ .../explore/SpaceDirectoryController.kt | 40 ++++++++- .../spaces/explore/SpaceDirectoryFragment.kt | 3 + .../explore/SpaceDirectoryViewAction.kt | 1 + .../spaces/explore/SpaceDirectoryViewModel.kt | 25 ++++-- .../spaces/manage/AddRoomListController.kt | 13 +++ .../spaces/manage/SpaceAddRoomFragment.kt | 39 +-------- vector/src/main/res/drawable/ic_info.xml | 10 +++ .../res/layout/fragment_space_add_rooms.xml | 2 +- .../res/layout/item_generic_pill_footer.xml | 27 ++++++ vector/src/main/res/values/strings.xml | 3 + 11 files changed, 201 insertions(+), 47 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt create mode 100644 vector/src/main/res/drawable/ic_info.xml create mode 100644 vector/src/main/res/layout/item_generic_pill_footer.xml diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt new file mode 100644 index 0000000000..5750fd93b4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2019 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.core.ui.list + +import android.content.res.ColorStateList +import android.view.Gravity +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.core.widget.ImageViewCompat +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.features.themes.ThemeUtils + +/** + * A generic list item. + * Displays an item with a title, and optional description. + * Can display an accessory on the right, that can be an image or an indeterminate progress. + * If provided with an action, will display a button at the bottom of the list item. + */ +@EpoxyModelClass(layout = R.layout.item_generic_pill_footer) +abstract class GenericPillItem : VectorEpoxyModel() { + + @EpoxyAttribute + var text: CharSequence? = null + + @EpoxyAttribute + var style: ItemStyle = ItemStyle.NORMAL_TEXT + + @EpoxyAttribute + var itemClickAction: GenericItem.Action? = null + + @EpoxyAttribute + var centered: Boolean = false + + @EpoxyAttribute + @DrawableRes + var imageRes: Int? = null + + @EpoxyAttribute + var tintIcon: Boolean = true + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.textView.setTextOrHide(text) + holder.textView.typeface = style.toTypeFace() + holder.textView.textSize = style.toTextSize() + holder.textView.gravity = if (centered) Gravity.CENTER_HORIZONTAL else Gravity.START + + imageRes?.let { holder.imageView.setImageResource(it) } + if (tintIcon) { + val iconTintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) + ImageViewCompat.setImageTintList(holder.imageView, ColorStateList.valueOf(iconTintColor)) + } else { + ImageViewCompat.setImageTintList(holder.imageView, null) + } + + holder.view.setOnClickListener { + itemClickAction?.perform?.run() + } + } + + class Holder : VectorEpoxyHolder() { + val imageView by bind(R.id.itemGenericPillImage) + val textView by bind(R.id.itemGenericPillText) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index f8e45dcd5e..28d410529e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -18,13 +18,21 @@ package im.vector.app.features.spaces.explore import android.view.View import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import im.vector.app.R +import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.core.ui.list.genericPillItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.spaceChildInfoItem +import me.gujun.android.span.span +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError.Companion.M_UNRECOGNIZED import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.util.MatrixItem @@ -32,13 +40,16 @@ import javax.inject.Inject class SpaceDirectoryController @Inject constructor( private val avatarRenderer: AvatarRenderer, - private val stringProvider: StringProvider + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val errorFormatter: ErrorFormatter ) : TypedEpoxyController() { interface InteractionListener { fun onButtonClick(spaceChildInfo: SpaceChildInfo) fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo) fun onRoomClick(spaceChildInfo: SpaceChildInfo) + fun retry() } var listener: InteractionListener? = null @@ -50,6 +61,33 @@ class SpaceDirectoryController @Inject constructor( loadingItem { id("loading") } + } else if (results is Fail) { + val failure = results.error + if (failure is Failure.ServerError && failure.error.code == M_UNRECOGNIZED) { + genericPillItem { + id("HS no Support") + imageRes(R.drawable.error) + tintIcon(false) + text( + span { + span(stringProvider.getString(R.string.spaces_no_server_support_title)) { + textStyle = "bold" + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary) + } + +"\n\n" + span(stringProvider.getString(R.string.spaces_no_server_support_description)) { + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + } + } + ) + } + } else { + errorWithRetryItem { + id("api_err") + text(errorFormatter.toHumanReadable(failure)) + listener { listener?.retry() } + } + } } else { val flattenChildInfo = results?.invoke() ?.filter { diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 41ab8d9006..fa44f4595e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -94,6 +94,9 @@ class SpaceDirectoryFragment @Inject constructor( return true } + override fun retry() { + viewModel.handle(SpaceDirectoryViewAction.Retry) + } // override fun navigateToRoom(roomId: String) { // viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId)) // } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt index a7e1482e24..83cdf2916d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt @@ -25,4 +25,5 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction { data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction() object HandleBack : SpaceDirectoryViewAction() + object Retry : SpaceDirectoryViewAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 9d74849386..0c23752936 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -39,7 +39,7 @@ import org.matrix.android.sdk.rx.rx import timber.log.Timber class SpaceDirectoryViewModel @AssistedInject constructor( - @Assisted initialState: SpaceDirectoryState, + @Assisted val initialState: SpaceDirectoryState, private val session: Session ) : VectorViewModel(initialState) { @@ -63,11 +63,21 @@ class SpaceDirectoryViewModel @AssistedInject constructor( val spaceSum = session.getRoomSummary(initialState.spaceId) setState { copy( - childList = spaceSum?.spaceChildren ?: emptyList(), - spaceSummaryApiResult = Loading() + childList = spaceSum?.spaceChildren ?: emptyList() ) } + refreshFromApi() + observeJoinedRooms() + observeMembershipChanges() + } + + private fun refreshFromApi() { + setState { + copy( + spaceSummaryApiResult = Loading() + ) + } viewModelScope.launch(Dispatchers.IO) { try { val query = session.spaceService().querySpaceChildren(initialState.spaceId) @@ -84,8 +94,6 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } } } - observeJoinedRooms() - observeMembershipChanges() } private fun observeJoinedRooms() { @@ -139,13 +147,16 @@ class SpaceDirectoryViewModel @AssistedInject constructor( is SpaceDirectoryViewAction.NavigateToRoom -> { _viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId)) } - is SpaceDirectoryViewAction.ShowDetails -> { + is SpaceDirectoryViewAction.ShowDetails -> { // This is temporary for now to at least display something for the space beta // It's not ideal as it's doing some peeking that is not needed. session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let { - _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) + _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) } } + SpaceDirectoryViewAction.Retry -> { + refreshFromApi() + } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt index 46ca2a6606..dffb09529b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt @@ -19,6 +19,8 @@ package im.vector.app.features.spaces.manage import androidx.recyclerview.widget.DiffUtil import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.R +import im.vector.app.core.ui.list.GenericPillItem_ import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.AvatarRenderer @@ -56,6 +58,8 @@ class AddRoomListController @Inject constructor( var listener: Listener? = null var ignoreRooms: List? = null + var subHeaderText: CharSequence? = null + var initialLoadOccurred = false fun boundaryChange(boundary: ResultBoundaries) { @@ -100,6 +104,15 @@ class AddRoomListController @Inject constructor( expanded(true) } ) + if (subHeaderText != null) { + add( + GenericPillItem_().apply { + id("sub_header") + text(subHeaderText) + imageRes(R.drawable.ic_info) + } + ) + } } super.addModels(filteredModel) if (!initialLoadOccurred) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index 9a25e79f85..203098d32b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -16,9 +16,6 @@ package im.vector.app.features.spaces.manage -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.Drawable import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -26,12 +23,8 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.ConcatAdapter -import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.airbnb.epoxy.EpoxyViewHolder import com.airbnb.mvrx.Loading import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel @@ -41,7 +34,6 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceAddRoomsBinding -import im.vector.app.features.home.room.list.RoomCategoryItem_ import io.reactivex.rxkotlin.subscribeBy import org.matrix.android.sdk.api.session.room.model.RoomSummary import java.util.concurrent.TimeUnit @@ -88,6 +80,7 @@ class SpaceAddRoomFragment @Inject constructor( } .disposeOnDestroyView() + spaceEpoxyController.subHeaderText = getString(R.string.spaces_feeling_experimental_subspace) viewModel.selectionListLiveData.observe(viewLifecycleOwner) { spaceEpoxyController.selectedItems = it roomEpoxyController.selectedItems = it @@ -183,36 +176,6 @@ class SpaceAddRoomFragment @Inject constructor( } views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - views.roomList.addItemDecoration( - - object : DividerItemDecoration(context, VERTICAL) { - val decorationDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.divider_horizontal) - - override fun getDrawable(): Drawable? { - return decorationDrawable - } - - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - val position = parent.getChildAdapterPosition(view) - val vh = parent.findViewHolderForAdapterPosition(position) - val nextIsSectionOrFinal = parent.findViewHolderForAdapterPosition(position + 1)?.let { - (it as? EpoxyViewHolder)?.model is RoomCategoryItem_ - } ?: true - if (vh == null - || (vh as? EpoxyViewHolder)?.model is RoomCategoryItem_ - || nextIsSectionOrFinal - ) { - outRect.setEmpty() - } else { - super.getItemOffsets(outRect, view, parent, state) - } - } - - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - } - } - ) views.roomList.setHasFixedSize(true) concatAdapter.addAdapter(roomEpoxyController.adapter) diff --git a/vector/src/main/res/drawable/ic_info.xml b/vector/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000000..afca98641a --- /dev/null +++ b/vector/src/main/res/drawable/ic_info.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_space_add_rooms.xml b/vector/src/main/res/layout/fragment_space_add_rooms.xml index 6619d06cca..84774d9cf9 100644 --- a/vector/src/main/res/layout/fragment_space_add_rooms.xml +++ b/vector/src/main/res/layout/fragment_space_add_rooms.xml @@ -10,7 +10,7 @@ android:id="@+id/roomList" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?riotx_header_panel_background" + android:background="?riotx_background" android:overScrollMode="always" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:listitem="@layout/item_room_to_add_in_space" /> diff --git a/vector/src/main/res/layout/item_generic_pill_footer.xml b/vector/src/main/res/layout/item_generic_pill_footer.xml new file mode 100644 index 0000000000..160fbc5210 --- /dev/null +++ b/vector/src/main/res/layout/item_generic_pill_footer.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 9d2763e243..cdd33b9a92 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3365,4 +3365,7 @@ Experimental Space - Only show orphans in Home + Feeling experimental?\nYou can add existing spaces to a space. + It looks like your homeserver does not support Spaces yet + Please contact your homserver admin for further information From 0de56e29933f94740e3cd9e6034fb5a7d10be360 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 11 May 2021 19:11:15 +0200 Subject: [PATCH 7/8] Code Review --- .../app/core/ui/list/GenericPillItem.kt | 13 ++-- .../room/list/SpaceRoomListSectionBuilder.kt | 71 +++++++++++-------- .../features/spaces/SpaceSummaryController.kt | 24 +++---- .../features/spaces/SubSpaceSummaryItem.kt | 36 +++++----- 4 files changed, 79 insertions(+), 65 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt index 5750fd93b4..ce9e35c007 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt @@ -20,6 +20,7 @@ import android.view.Gravity import android.widget.ImageView import android.widget.TextView import androidx.annotation.DrawableRes +import androidx.core.view.isVisible import androidx.core.widget.ImageViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -30,10 +31,7 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.themes.ThemeUtils /** - * A generic list item. - * Displays an item with a title, and optional description. - * Can display an accessory on the right, that can be an image or an indeterminate progress. - * If provided with an action, will display a button at the bottom of the list item. + * A generic list item with a rounded corner background and an optional icon */ @EpoxyModelClass(layout = R.layout.item_generic_pill_footer) abstract class GenericPillItem : VectorEpoxyModel() { @@ -65,7 +63,12 @@ abstract class GenericPillItem : VectorEpoxyModel() { holder.textView.textSize = style.toTextSize() holder.textView.gravity = if (centered) Gravity.CENTER_HORIZONTAL else Gravity.START - imageRes?.let { holder.imageView.setImageResource(it) } + if (imageRes != null) { + holder.imageView.setImageResource(imageRes!!) + holder.imageView.isVisible = true + } else { + holder.imageView.isVisible = false + } if (tintIcon) { val iconTintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) ImageViewCompat.setImageTintList(holder.imageView, ColorStateList.valueOf(iconTintColor)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index 2cd0199062..266adf6b0c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -93,8 +93,11 @@ class SpaceRoomListSectionBuilder( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.invitations_header, notifyOfLocalEcho = true, - spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } - ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + }, countRoomAsNotif = true ) { it.memberships = listOf(Membership.INVITE) @@ -102,12 +105,15 @@ class SpaceRoomListSectionBuilder( } addSection( - sections, - activeSpaceAwareQueries, - R.string.bottom_action_rooms, - false, - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } - ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.bottom_action_rooms, + notifyOfLocalEcho = false, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + } ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS @@ -155,12 +161,15 @@ class SpaceRoomListSectionBuilder( } addSection( - sections, - activeSpaceAwareQueries, - R.string.bottom_action_rooms, - false, - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } - ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.bottom_action_rooms, + notifyOfLocalEcho = false, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + } ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -168,12 +177,15 @@ class SpaceRoomListSectionBuilder( } addSection( - sections, - activeSpaceAwareQueries, - R.string.low_priority_header, - false, - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } - ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.low_priority_header, + notifyOfLocalEcho = false, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + } ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -181,12 +193,15 @@ class SpaceRoomListSectionBuilder( } addSection( - sections, - activeSpaceAwareQueries, - R.string.system_alerts_header, - false, - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL.takeIf { onlyOrphansInHome } - ?: RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.system_alerts_header, + notifyOfLocalEcho = false, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + } ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -372,7 +387,7 @@ class SpaceRoomListSectionBuilder( activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) ) } - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { if (currentSpace == null) { copy( activeSpaceFilter = ActiveSpaceFilter.None @@ -383,7 +398,7 @@ class SpaceRoomListSectionBuilder( ) } } - RoomListViewModel.SpaceFilterStrategy.NONE -> this + RoomListViewModel.SpaceFilterStrategy.NONE -> this } } } 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 bd190519aa..33e4b23047 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 @@ -179,31 +179,31 @@ class SpaceSummaryController @Inject constructor( private fun buildSubSpace(summaries: List?, expandedStates: Map, selected: RoomGroupingMethod, - childSum: SpaceChildInfo, currentDepth: Int, maxDepth: Int) { + info: SpaceChildInfo, currentDepth: Int, maxDepth: Int) { if (currentDepth >= maxDepth) return - val childSum = summaries?.firstOrNull { it.roomId == childSum.childRoomId } ?: return + val childSummary = summaries?.firstOrNull { it.roomId == info.childRoomId } ?: return // does it have children? - val subSpaces = childSum.spaceChildren?.filter { childInfo -> + val subSpaces = childSummary.spaceChildren?.filter { childInfo -> summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 }?.sortedWith(subSpaceComparator) - val expanded = expandedStates[childSum.roomId] == true - val isSelected = selected is RoomGroupingMethod.BySpace && childSum.roomId == selected.space()?.roomId + val expanded = expandedStates[childSummary.roomId] == true + val isSelected = selected is RoomGroupingMethod.BySpace && childSummary.roomId == selected.space()?.roomId subSpaceSummaryItem { avatarRenderer(avatarRenderer) - id(childSum.roomId) + id(childSummary.roomId) hasChildren(!subSpaces.isNullOrEmpty()) selected(isSelected) expanded(expanded) - onMore { callback?.onSpaceSettings(childSum) } - matrixItem(childSum.toMatrixItem()) - listener { callback?.onSpaceSelected(childSum) } - toggleExpand { callback?.onToggleExpand(childSum) } + onMore { callback?.onSpaceSettings(childSummary) } + matrixItem(childSummary.toMatrixItem()) + listener { callback?.onSpaceSelected(childSummary) } + toggleExpand { callback?.onToggleExpand(childSummary) } indent(currentDepth) countState( UnreadCounterBadgeView.State( - childSum.notificationCount, - childSum.highlightCount > 0 + childSummary.notificationCount, + childSummary.highlightCount > 0 ) ) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt index b98f1ca7af..db58353e5c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt @@ -20,7 +20,6 @@ import android.widget.ImageView import android.widget.Space import android.widget.TextView import androidx.core.content.ContextCompat -import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute @@ -37,16 +36,16 @@ import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass(layout = R.layout.item_sub_space) abstract class SubSpaceSummaryItem : VectorEpoxyModel() { - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: (() -> Unit)? = null - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: (() -> Unit)? = null - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: (() -> Unit)? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: (() -> Unit)? = 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) + @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) override fun bind(holder: Holder) { super.bind(holder) @@ -64,21 +63,18 @@ abstract class SubSpaceSummaryItem : VectorEpoxyModel - toggleExpand?.invoke() - }) - ) - } else { - holder.collapseIndicator.isGone = 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() + }) + ) + + holder.collapseIndicator.isVisible = hasChildren holder.indentSpace.isVisible = indent > 0 holder.indentSpace.updateLayoutParams { From 1799dcbd0c63af71c489b0514a90ecf467c02f48 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 12 May 2021 09:20:55 +0200 Subject: [PATCH 8/8] Fix lint --- vector/src/main/res/layout/item_generic_pill_footer.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_generic_pill_footer.xml b/vector/src/main/res/layout/item_generic_pill_footer.xml index 160fbc5210..a0b19db32f 100644 --- a/vector/src/main/res/layout/item_generic_pill_footer.xml +++ b/vector/src/main/res/layout/item_generic_pill_footer.xml @@ -1,5 +1,6 @@ + app:tint="?riotx_text_secondary" />