Merge pull request #3312 from vector-im/feature/bca/spaces_polish

Spaces various fixes and polish
This commit is contained in:
Benoit Marty 2021-05-12 10:09:36 +02:00 committed by GitHub
commit 56d0699bc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 736 additions and 185 deletions

View File

@ -33,7 +33,7 @@ interface Space {
fun spaceSummary(): RoomSummary? fun spaceSummary(): RoomSummary?
suspend fun addChildren(roomId: String, suspend fun addChildren(roomId: String,
viaServers: List<String>, viaServers: List<String>?,
order: String?, order: String?,
autoJoin: Boolean = false, autoJoin: Boolean = false,
suggested: Boolean? = false) suggested: Boolean? = false)

View File

@ -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.events.model.Event
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE 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.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter
import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
internal class PermalinkFactory @Inject constructor( internal class PermalinkFactory @Inject constructor(
@UserId @UserId
private val userId: String, private val userId: String,
// Use a provider to fix circular Dagger dependency private val viaParameterFinder: ViaParameterFinder
private val roomGetterProvider: Provider<RoomGetter>
) { ) {
fun createPermalink(event: Event): String? { fun createPermalink(event: Event): String? {
@ -50,12 +44,12 @@ internal class PermalinkFactory @Inject constructor(
return if (roomId.isEmpty()) { return if (roomId.isEmpty()) {
null null
} else { } 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 { 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? { fun getLinkedId(url: String): String? {
@ -66,25 +60,6 @@ internal class PermalinkFactory @Inject constructor(
} else null } 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 * 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 { private fun unescape(id: String): String {
return id.replace("%2F", "/") return id.replace("%2F", "/")
} }
/**
* Get a set of userIds of joined members of a room
*/
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
return roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
.orEmpty()
.toSet()
}
} }

View File

@ -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<RoomGetter>
) {
fun computeViaParams(roomId: String, max: Int): List<String> {
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<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(max)
}
/**
* Get a set of userIds of joined members of a room
*/
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
return roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
.orEmpty()
.toSet()
}
}

View File

@ -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.session.space.Space
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM 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.state.SendStateTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.search.SearchTask 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 roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService, private val roomPushRuleService: RoomPushRuleService,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) : private val searchTask: SearchTask) :
Room, Room,
TimelineService by timelineService, TimelineService by timelineService,
@ -154,6 +156,6 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
override fun asSpace(): Space? { override fun asSpace(): Space? {
if (roomSummary()?.roomType != RoomType.SPACE) return null if (roomSummary()?.roomType != RoomType.SPACE) return null
return DefaultSpace(this, roomSummaryDataSource) return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
} }
} }

View File

@ -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.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.internal.session.SessionScope 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.alias.DefaultAliasService
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService 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 membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
private val searchTask: SearchTask) : private val searchTask: SearchTask) :
RoomFactory { RoomFactory {
@ -83,7 +85,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomMembersService = membershipServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
sendStateTask = sendStateTask, sendStateTask = sendStateTask,
searchTask = searchTask searchTask = searchTask,
viaParameterFinder = viaParameterFinder
) )
} }
} }

View File

@ -364,6 +364,8 @@ internal class RoomSummaryUpdater @Inject constructor(
realm.where(RoomSummaryEntity::class.java) realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN)) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE) .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) .contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
.findAll().forEach { .findAll().forEach {
highlightCount += it.highlightCount highlightCount += it.highlightCount

View File

@ -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.room.model.RoomSummary
import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent 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 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
internal class DefaultSpace( internal class DefaultSpace(
private val room: Room, private val room: Room,
private val spaceSummaryDataSource: RoomSummaryDataSource private val spaceSummaryDataSource: RoomSummaryDataSource,
private val viaParameterFinder: ViaParameterFinder
) : Space { ) : Space {
override fun asRoom(): Room { override fun asRoom(): Room {
@ -46,15 +48,17 @@ internal class DefaultSpace(
} }
override suspend fun addChildren(roomId: String, override suspend fun addChildren(roomId: String,
viaServers: List<String>, viaServers: List<String>?,
order: String?, order: String?,
autoJoin: Boolean, autoJoin: Boolean,
suggested: Boolean?) { suggested: Boolean?) {
// Find best via
room.sendStateEvent( room.sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD, eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId, stateKey = roomId,
body = SpaceChildContent( body = SpaceChildContent(
via = viaServers, via = viaServers ?: viaParameterFinder.computeViaParams(roomId, 3),
autoJoin = autoJoin, autoJoin = autoJoin,
order = order, order = order,
suggested = suggested suggested = suggested

View File

@ -0,0 +1,88 @@
/*
* 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.view.isVisible
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 with a rounded corner background and an optional icon
*/
@EpoxyModelClass(layout = R.layout.item_generic_pill_footer)
abstract class GenericPillItem : VectorEpoxyModel<GenericPillItem.Holder>() {
@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
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))
} else {
ImageViewCompat.setImageTintList(holder.imageView, null)
}
holder.view.setOnClickListener {
itemClickAction?.perform?.run()
}
}
class Holder : VectorEpoxyHolder() {
val imageView by bind<ImageView>(R.id.itemGenericPillImage)
val textView by bind<TextView>(R.id.itemGenericPillText)
}
}

View File

@ -18,6 +18,7 @@
package im.vector.app.features.grouplist package im.vector.app.features.grouplist
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -35,8 +36,9 @@ import im.vector.app.features.themes.ThemeUtils
abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Holder>() { abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Holder>() {
@EpoxyAttribute var selected: Boolean = false @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 countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)
@EpoxyAttribute var showSeparator: Boolean = false
override fun getViewType(): Int { override fun getViewType(): Int {
// mm.. it's reusing the same layout for basic space item // mm.. it's reusing the same layout for basic space item
@ -56,6 +58,7 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
holder.leaveView.isVisible = false holder.leaveView.isVisible = false
holder.counterBadgeView.render(countState) holder.counterBadgeView.render(countState)
holder.bottomSeparator.isVisible = showSeparator
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
@ -64,5 +67,6 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout) val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
val leaveView by bind<ImageView>(R.id.groupTmpLeave) val leaveView by bind<ImageView>(R.id.groupTmpLeave)
val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge) val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge)
val bottomSeparator by bind<View>(R.id.groupBottomSeparator)
} }
} }

View File

@ -202,7 +202,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
setState { setState {
copy( copy(
notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites, notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites,
notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight, notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight || (dmInvites + roomsInvite) > 0,
notificationCountPeople = dmRooms.totalCount + dmInvites, notificationCountPeople = dmRooms.totalCount + dmInvites,
notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0, notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0,
notificationCountRooms = otherRooms.totalCount + roomsInvite, notificationCountRooms = otherRooms.totalCount + roomsInvite,

View File

@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.VectorPreferences
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
@ -52,6 +53,7 @@ data class CountInfo(
class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState, class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState,
session: Session, session: Session,
private val vectorPreferences: VectorPreferences,
appStateHandler: AppStateHandler) appStateHandler: AppStateHandler)
: VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) { : VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
@ -126,12 +128,24 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
} }
is RoomGroupingMethod.BySpace -> { is RoomGroupingMethod.BySpace -> {
val selectedSpace = appStateHandler.safeActiveSpaceId() val selectedSpace = appStateHandler.safeActiveSpaceId()
val counts = session.getNotificationCountForRooms(
val inviteCount = session.getRoomSummaries(roomSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}).size
val totalCount = session.getNotificationCountForRooms(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) 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() val rootCounts = session.spaceService().getRootSpaceSummaries()
.filter { .filter {
// filter out current selection // filter out current selection

View File

@ -832,7 +832,7 @@ class RoomDetailViewModel @AssistedInject constructor(
session.spaceService().getSpace(spaceId) session.spaceService().getSpace(spaceId)
?.addChildren( ?.addChildren(
state.roomId, state.roomId,
listOf(session.sessionParams.homeServerHost ?: ""), null,
null, null,
true true
) )
@ -849,7 +849,7 @@ class RoomDetailViewModel @AssistedInject constructor(
session.spaceService().getSpace(slashCommandResult.spaceId) session.spaceService().getSpace(slashCommandResult.spaceId)
?.addChildren( ?.addChildren(
room.roomId, room.roomId,
listOf(session.sessionParams.homeServerHost ?: ""), null,
null, null,
false false
) )

View File

@ -69,12 +69,12 @@ class RoomListViewModel @Inject constructor(
* Filter the rooms if they are part of the current space (children and grand children). * 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 * 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. * Special case when we don't want to discriminate rooms when current space is null.
* In this case return all. * In this case return all.
*/ */
NOT_IF_ALL, ALL_IF_SPACE_NULL,
/** Do not filter based on space*/ /** Do not filter based on space*/
NONE NONE
} }
@ -131,7 +131,8 @@ class RoomListViewModel @Inject constructor(
}, },
{ {
updatableQuery = it updatableQuery = it
} },
vectorPreferences.labsSpacesOnlyOrphansInHome()
).buildSections(initialState.displayMode) ).buildSections(initialState.displayMode)
} else { } else {
GroupRoomListSectionBuilder( GroupRoomListSectionBuilder(

View File

@ -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.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult 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.model.Membership
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.asObservable
class SpaceRoomListSectionBuilder( class SpaceRoomListSectionBuilder(
@ -50,7 +51,8 @@ class SpaceRoomListSectionBuilder(
val viewModelScope: CoroutineScope, val viewModelScope: CoroutineScope,
private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>, private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>,
val onDisposable: (Disposable) -> Unit, val onDisposable: (Disposable) -> Unit,
val onUdpatable: (UpdatableLivePageResult) -> Unit val onUdpatable: (UpdatableLivePageResult) -> Unit,
val onlyOrphansInHome: Boolean = false
) : RoomListSectionBuilder { ) : RoomListSectionBuilder {
val pagedListConfig = PagedList.Config.Builder() val pagedListConfig = PagedList.Config.Builder()
@ -87,22 +89,31 @@ class SpaceRoomListSectionBuilder(
} }
RoomListDisplayMode.NOTIFICATIONS -> { RoomListDisplayMode.NOTIFICATIONS -> {
addSection( addSection(
sections, sections = sections,
activeSpaceAwareQueries, activeSpaceUpdaters = activeSpaceAwareQueries,
R.string.invitations_header, nameRes = R.string.invitations_header,
true, notifyOfLocalEcho = true,
RoomListViewModel.SpaceFilterStrategy.NORMAL spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
},
countRoomAsNotif = true
) { ) {
it.memberships = listOf(Membership.INVITE) it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ALL it.roomCategoryFilter = RoomCategoryFilter.ALL
} }
addSection( addSection(
sections, sections = sections,
activeSpaceAwareQueries, activeSpaceUpdaters = activeSpaceAwareQueries,
R.string.bottom_action_rooms, nameRes = R.string.bottom_action_rooms,
false, notifyOfLocalEcho = false,
RoomListViewModel.SpaceFilterStrategy.NORMAL spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
@ -126,10 +137,12 @@ class SpaceRoomListSectionBuilder(
private fun buildRoomsSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) { private fun buildRoomsSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
addSection( addSection(
sections, activeSpaceAwareQueries, sections = sections,
R.string.invitations_header, activeSpaceUpdaters = activeSpaceAwareQueries,
true, nameRes = R.string.invitations_header,
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL notifyOfLocalEcho = true,
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
countRoomAsNotif = true
) { ) {
it.memberships = listOf(Membership.INVITE) it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
@ -140,7 +153,7 @@ class SpaceRoomListSectionBuilder(
activeSpaceAwareQueries, activeSpaceAwareQueries,
R.string.bottom_action_favourites, R.string.bottom_action_favourites,
false, false,
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
@ -148,11 +161,15 @@ class SpaceRoomListSectionBuilder(
} }
addSection( addSection(
sections, sections = sections,
activeSpaceAwareQueries, activeSpaceUpdaters = activeSpaceAwareQueries,
R.string.bottom_action_rooms, nameRes = R.string.bottom_action_rooms,
false, notifyOfLocalEcho = false,
RoomListViewModel.SpaceFilterStrategy.NORMAL spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
@ -160,11 +177,15 @@ class SpaceRoomListSectionBuilder(
} }
addSection( addSection(
sections, sections = sections,
activeSpaceAwareQueries, activeSpaceUpdaters = activeSpaceAwareQueries,
R.string.low_priority_header, nameRes = R.string.low_priority_header,
false, notifyOfLocalEcho = false,
RoomListViewModel.SpaceFilterStrategy.NORMAL spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
@ -172,11 +193,15 @@ class SpaceRoomListSectionBuilder(
} }
addSection( addSection(
sections, sections = sections,
activeSpaceAwareQueries, activeSpaceUpdaters = activeSpaceAwareQueries,
R.string.system_alerts_header, nameRes = R.string.system_alerts_header,
false, notifyOfLocalEcho = false,
RoomListViewModel.SpaceFilterStrategy.NORMAL spaceFilterStrategy = if (onlyOrphansInHome) {
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
} else {
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
}
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
@ -228,11 +253,12 @@ class SpaceRoomListSectionBuilder(
} }
private fun buildDmSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) { private fun buildDmSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
addSection(sections, addSection(sections = sections,
activeSpaceAwareQueries, activeSpaceUpdaters = activeSpaceAwareQueries,
R.string.invitations_header, nameRes = R.string.invitations_header,
true, notifyOfLocalEcho = true,
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
countRoomAsNotif = true
) { ) {
it.memberships = listOf(Membership.INVITE) it.memberships = listOf(Membership.INVITE)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
@ -242,7 +268,7 @@ class SpaceRoomListSectionBuilder(
activeSpaceAwareQueries, activeSpaceAwareQueries,
R.string.bottom_action_favourites, R.string.bottom_action_favourites,
false, false,
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
@ -253,7 +279,7 @@ class SpaceRoomListSectionBuilder(
activeSpaceAwareQueries, activeSpaceAwareQueries,
R.string.bottom_action_people_x, R.string.bottom_action_people_x,
false, false,
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
) { ) {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
@ -266,6 +292,7 @@ class SpaceRoomListSectionBuilder(
@StringRes nameRes: Int, @StringRes nameRes: Int,
notifyOfLocalEcho: Boolean = false, notifyOfLocalEcho: Boolean = false,
spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE, spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
countRoomAsNotif: Boolean = false,
query: (RoomSummaryQueryParams.Builder) -> Unit) { query: (RoomSummaryQueryParams.Builder) -> Unit) {
withQueryParams( withQueryParams(
{ query.invoke(it) }, { query.invoke(it) },
@ -277,7 +304,7 @@ class SpaceRoomListSectionBuilder(
pagedListConfig pagedListConfig
).also { ).also {
when (spaceFilterStrategy) { when (spaceFilterStrategy) {
RoomListViewModel.SpaceFilterStrategy.NORMAL -> { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater { activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) { override fun updateForSpaceId(roomId: String?) {
it.updateQuery { it.updateQuery {
@ -288,7 +315,7 @@ class SpaceRoomListSectionBuilder(
} }
}) })
} }
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL -> { RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater { activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) { override fun updateForSpaceId(roomId: String?) {
if (roomId != null) { if (roomId != null) {
@ -320,9 +347,15 @@ class SpaceRoomListSectionBuilder(
.subscribe { .subscribe {
sections.find { it.sectionName == name } sections.find { it.sectionName == name }
?.notificationCount ?.notificationCount
?.postValue(session.getNotificationCountForRooms( ?.postValue(
if (countRoomAsNotif) {
RoomAggregateNotificationCount(it.size, it.size)
} else {
session.getNotificationCountForRooms(
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
)) )
}
)
}.also { }.also {
onDisposable.invoke(it) onDisposable.invoke(it)
} }
@ -349,14 +382,16 @@ class SpaceRoomListSectionBuilder(
internal fun RoomSummaryQueryParams.process(spaceFilter: RoomListViewModel.SpaceFilterStrategy, currentSpace: String?): RoomSummaryQueryParams { internal fun RoomSummaryQueryParams.process(spaceFilter: RoomListViewModel.SpaceFilterStrategy, currentSpace: String?): RoomSummaryQueryParams {
return when (spaceFilter) { return when (spaceFilter) {
RoomListViewModel.SpaceFilterStrategy.NORMAL -> { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
copy( copy(
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace)
) )
} }
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL -> { RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
if (currentSpace == null) { if (currentSpace == null) {
this copy(
activeSpaceFilter = ActiveSpaceFilter.None
)
} else { } else {
copy( copy(
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace)

View File

@ -236,10 +236,9 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
if (initialState.parentSpaceId != null) { if (initialState.parentSpaceId != null) {
// add it as a child // add it as a child
try { try {
val via = session.sessionParams.homeServerHost?.let { listOf(it) }.orEmpty()
session.spaceService() session.spaceService()
.getSpace(initialState.parentSpaceId) .getSpace(initialState.parentSpaceId)
?.addChildren(roomId, viaServers = via, order = null) ?.addChildren(roomId, viaServers = null, order = null)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.w(failure, "Failed to add as a child") Timber.w(failure, "Failed to add as a child")
} }

View File

@ -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_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_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_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" 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) 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 * Photo / video picker
*/ */

View File

@ -17,6 +17,9 @@
package im.vector.app.features.settings package im.vector.app.features.settings
import im.vector.app.R 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 import javax.inject.Inject
class VectorSettingsLabsFragment @Inject constructor( class VectorSettingsLabsFragment @Inject constructor(
@ -27,6 +30,11 @@ class VectorSettingsLabsFragment @Inject constructor(
override val preferenceXmlRes = R.xml.vector_settings_labs override val preferenceXmlRes = R.xml.vector_settings_labs
override fun bindPref() { override fun bindPref() {
// Nothing to do findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_LABS_SPACES_HOME_AS_ORPHAN)!!.let { pref ->
pref.setOnPreferenceChangeListener { _, _ ->
MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = false))
true
}
}
} }
} }

View File

@ -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.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.Membership 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.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.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -43,6 +43,8 @@ class SpaceSummaryController @Inject constructor(
var callback: Callback? = null var callback: Callback? = null
private var viewState: SpaceListViewState? = null private var viewState: SpaceListViewState? = null
private val subSpaceComparator: Comparator<SpaceChildInfo> = compareBy<SpaceChildInfo> { it.order }.thenBy { it.childRoomId }
init { init {
requestModelBuild() requestModelBuild()
} }
@ -132,13 +134,13 @@ class SpaceSummaryController @Inject constructor(
} }
rootSpaces rootSpaces
?.sortedBy { it.displayName } ?.sortedBy { it.roomId }
?.forEach { groupSummary -> ?.forEach { groupSummary ->
val isSelected = selected is RoomGroupingMethod.BySpace && groupSummary.roomId == selected.space()?.roomId val isSelected = selected is RoomGroupingMethod.BySpace && groupSummary.roomId == selected.space()?.roomId
// does it have children? // does it have children?
val subSpaces = groupSummary.spaceChildren?.filter { childInfo -> val subSpaces = groupSummary.spaceChildren?.filter { childInfo ->
summaries?.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 summaries?.indexOfFirst { it.roomId == childInfo.childRoomId } != -1
} }?.sortedWith(subSpaceComparator)
val hasChildren = (subSpaces?.size ?: 0) > 0 val hasChildren = (subSpaces?.size ?: 0) > 0
val expanded = expandedStates[groupSummary.roomId] == true val expanded = expandedStates[groupSummary.roomId] == true
@ -163,24 +165,7 @@ class SpaceSummaryController @Inject constructor(
if (hasChildren && expanded) { if (hasChildren && expanded) {
// it's expanded // it's expanded
subSpaces?.forEach { child -> subSpaces?.forEach { child ->
summaries?.firstOrNull { it.roomId == child.childRoomId }?.let { childSum -> buildSubSpace(summaries, expandedStates, selected, child, 1, 3)
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
)
)
}
}
} }
} }
} }
@ -191,6 +176,45 @@ class SpaceSummaryController @Inject constructor(
} }
} }
private fun buildSubSpace(summaries: List<RoomSummary>?,
expandedStates: Map<String, Boolean>,
selected: RoomGroupingMethod,
info: SpaceChildInfo, currentDepth: Int, maxDepth: Int) {
if (currentDepth >= maxDepth) return
val childSummary = summaries?.firstOrNull { it.roomId == info.childRoomId } ?: return
// does it have children?
val subSpaces = childSummary.spaceChildren?.filter { childInfo ->
summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1
}?.sortedWith(subSpaceComparator)
val expanded = expandedStates[childSummary.roomId] == true
val isSelected = selected is RoomGroupingMethod.BySpace && childSummary.roomId == selected.space()?.roomId
subSpaceSummaryItem {
avatarRenderer(avatarRenderer)
id(childSummary.roomId)
hasChildren(!subSpaces.isNullOrEmpty())
selected(isSelected)
expanded(expanded)
onMore { callback?.onSpaceSettings(childSummary) }
matrixItem(childSummary.toMatrixItem())
listener { callback?.onSpaceSelected(childSummary) }
toggleExpand { callback?.onToggleExpand(childSummary) }
indent(currentDepth)
countState(
UnreadCounterBadgeView.State(
childSummary.notificationCount,
childSummary.highlightCount > 0
)
)
}
if (expanded) {
subSpaces?.forEach {
buildSubSpace(summaries, expandedStates, selected, it, currentDepth + 1, maxDepth)
}
}
}
interface Callback { interface Callback {
fun onSpaceSelected(spaceSummary: RoomSummary?) fun onSpaceSelected(spaceSummary: RoomSummary?)
fun onSpaceInviteSelected(spaceSummary: RoomSummary) fun onSpaceInviteSelected(spaceSummary: RoomSummary)

View File

@ -16,6 +16,7 @@
package im.vector.app.features.spaces package im.vector.app.features.spaces
import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.Space import android.widget.Space
import android.widget.TextView import android.widget.TextView
@ -37,17 +38,18 @@ import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_space) @EpoxyModelClass(layout = R.layout.item_space)
abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() { abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: (() -> Unit)? = null
@EpoxyAttribute var onMore: (() -> Unit)? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onMore: (() -> Unit)? = null
@EpoxyAttribute var toggleExpand: (() -> Unit)? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var toggleExpand: (() -> Unit)? = null
@EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute var expanded: Boolean = false
@EpoxyAttribute var hasChildren: Boolean = false @EpoxyAttribute var hasChildren: Boolean = false
@EpoxyAttribute var indent: Int = 0 @EpoxyAttribute var indent: Int = 0
@EpoxyAttribute var countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) @EpoxyAttribute var countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)
@EpoxyAttribute var description: String? = null @EpoxyAttribute var description: String? = null
@EpoxyAttribute var showSeparator: Boolean = false
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
@ -83,6 +85,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
} }
holder.indentSpace.isVisible = indent > 0 holder.indentSpace.isVisible = indent > 0
holder.separator.isVisible = showSeparator
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
holder.counterBadgeView.render(countState) holder.counterBadgeView.render(countState)
@ -102,5 +105,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
val collapseIndicator by bind<ImageView>(R.id.groupChildrenCollapse) val collapseIndicator by bind<ImageView>(R.id.groupChildrenCollapse)
val indentSpace by bind<Space>(R.id.indent) val indentSpace by bind<Space>(R.id.indent)
val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge) val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge)
val separator by bind<View>(R.id.groupBottomSeparator)
} }
} }

View File

@ -28,8 +28,7 @@ import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod import im.vector.app.RoomGroupingMethod
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.ui.UiStateRepository
import im.vector.app.group import im.vector.app.group
import im.vector.app.space import im.vector.app.space
import io.reactivex.Observable 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.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary 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.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.session.user.model.User
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.asObservable
@ -54,8 +54,7 @@ import java.util.concurrent.TimeUnit
class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider, private val vectorPreferences: VectorPreferences
private val uiStateRepository: UiStateRepository
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) { ) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -108,21 +107,34 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
} }
}.disposeOnClear() }.disposeOnClear()
// XXX there should be a way to refactor this and share it
session.getPagedRoomSummariesLive( session.getPagedRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) this.memberships = listOf(Membership.JOIN)
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf {
vectorPreferences.labsSpacesOnlyOrphansInHome()
} ?: ActiveSpaceFilter.None
}, sortOrder = RoomSortOrder.NONE }, sortOrder = RoomSortOrder.NONE
).asObservable() ).asObservable()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
.subscribe { .subscribe {
val counts = session.getNotificationCountForRooms( val inviteCount = session.getRoomSummaries(roomSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}).size
val totalCount = session.getNotificationCountForRooms(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) 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 { setState {
copy( copy(
homeAggregateCount = counts homeAggregateCount = counts

View File

@ -0,0 +1,102 @@
/*
* 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.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<SubSpaceSummaryItem.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: (() -> 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
}
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 {
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<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
val moreView by bind<ImageView>(R.id.groupTmpLeave)
val collapseIndicator by bind<ImageView>(R.id.groupChildrenCollapse)
val indentSpace by bind<Space>(R.id.indent)
val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge)
}
}

View File

@ -18,13 +18,21 @@ package im.vector.app.features.spaces.explore
import android.view.View import android.view.View
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.errorWithRetryItem
import im.vector.app.core.epoxy.loadingItem 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.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem 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.AvatarRenderer
import im.vector.app.features.home.room.list.spaceChildInfoItem 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.RoomType
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
@ -32,13 +40,16 @@ import javax.inject.Inject
class SpaceDirectoryController @Inject constructor( class SpaceDirectoryController @Inject constructor(
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider private val stringProvider: StringProvider,
private val colorProvider: ColorProvider,
private val errorFormatter: ErrorFormatter
) : TypedEpoxyController<SpaceDirectoryState>() { ) : TypedEpoxyController<SpaceDirectoryState>() {
interface InteractionListener { interface InteractionListener {
fun onButtonClick(spaceChildInfo: SpaceChildInfo) fun onButtonClick(spaceChildInfo: SpaceChildInfo)
fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo) fun onSpaceChildClick(spaceChildInfo: SpaceChildInfo)
fun onRoomClick(spaceChildInfo: SpaceChildInfo) fun onRoomClick(spaceChildInfo: SpaceChildInfo)
fun retry()
} }
var listener: InteractionListener? = null var listener: InteractionListener? = null
@ -50,6 +61,33 @@ class SpaceDirectoryController @Inject constructor(
loadingItem { loadingItem {
id("loading") 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 { } else {
val flattenChildInfo = results?.invoke() val flattenChildInfo = results?.invoke()
?.filter { ?.filter {

View File

@ -94,6 +94,9 @@ class SpaceDirectoryFragment @Inject constructor(
return true return true
} }
override fun retry() {
viewModel.handle(SpaceDirectoryViewAction.Retry)
}
// override fun navigateToRoom(roomId: String) { // override fun navigateToRoom(roomId: String) {
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId)) // viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
// } // }

View File

@ -25,4 +25,5 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction {
data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction()
data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction() data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction()
object HandleBack : SpaceDirectoryViewAction() object HandleBack : SpaceDirectoryViewAction()
object Retry : SpaceDirectoryViewAction()
} }

View File

@ -39,7 +39,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
class SpaceDirectoryViewModel @AssistedInject constructor( class SpaceDirectoryViewModel @AssistedInject constructor(
@Assisted initialState: SpaceDirectoryState, @Assisted val initialState: SpaceDirectoryState,
private val session: Session private val session: Session
) : VectorViewModel<SpaceDirectoryState, SpaceDirectoryViewAction, SpaceDirectoryViewEvents>(initialState) { ) : VectorViewModel<SpaceDirectoryState, SpaceDirectoryViewAction, SpaceDirectoryViewEvents>(initialState) {
@ -63,11 +63,21 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
val spaceSum = session.getRoomSummary(initialState.spaceId) val spaceSum = session.getRoomSummary(initialState.spaceId)
setState { setState {
copy( copy(
childList = spaceSum?.spaceChildren ?: emptyList(), childList = spaceSum?.spaceChildren ?: emptyList()
spaceSummaryApiResult = Loading()
) )
} }
refreshFromApi()
observeJoinedRooms()
observeMembershipChanges()
}
private fun refreshFromApi() {
setState {
copy(
spaceSummaryApiResult = Loading()
)
}
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val query = session.spaceService().querySpaceChildren(initialState.spaceId) val query = session.spaceService().querySpaceChildren(initialState.spaceId)
@ -84,8 +94,6 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
} }
} }
} }
observeJoinedRooms()
observeMembershipChanges()
} }
private fun observeJoinedRooms() { private fun observeJoinedRooms() {
@ -146,6 +154,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it))
} }
} }
SpaceDirectoryViewAction.Retry -> {
refreshFromApi()
}
} }
} }

View File

@ -19,6 +19,8 @@ package im.vector.app.features.spaces.manage
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController 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.DebouncedClickListener
import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
@ -56,6 +58,8 @@ class AddRoomListController @Inject constructor(
var listener: Listener? = null var listener: Listener? = null
var ignoreRooms: List<String>? = null var ignoreRooms: List<String>? = null
var subHeaderText: CharSequence? = null
var initialLoadOccurred = false var initialLoadOccurred = false
fun boundaryChange(boundary: ResultBoundaries) { fun boundaryChange(boundary: ResultBoundaries) {
@ -100,6 +104,15 @@ class AddRoomListController @Inject constructor(
expanded(true) expanded(true)
} }
) )
if (subHeaderText != null) {
add(
GenericPillItem_().apply {
id("sub_header")
text(subHeaderText)
imageRes(R.drawable.ic_info)
}
)
}
} }
super.addModels(filteredModel) super.addModels(filteredModel)
if (!initialLoadOccurred) { if (!initialLoadOccurred) {

View File

@ -16,9 +16,6 @@
package im.vector.app.features.spaces.manage 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -26,12 +23,8 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyViewHolder
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel 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.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceAddRoomsBinding import im.vector.app.databinding.FragmentSpaceAddRoomsBinding
import im.vector.app.features.home.room.list.RoomCategoryItem_
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -88,6 +80,7 @@ class SpaceAddRoomFragment @Inject constructor(
} }
.disposeOnDestroyView() .disposeOnDestroyView()
spaceEpoxyController.subHeaderText = getString(R.string.spaces_feeling_experimental_subspace)
viewModel.selectionListLiveData.observe(viewLifecycleOwner) { viewModel.selectionListLiveData.observe(viewLifecycleOwner) {
spaceEpoxyController.selectedItems = it spaceEpoxyController.selectedItems = it
roomEpoxyController.selectedItems = it roomEpoxyController.selectedItems = it
@ -183,36 +176,6 @@ class SpaceAddRoomFragment @Inject constructor(
} }
views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) 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) views.roomList.setHasFixedSize(true)
concatAdapter.addAdapter(roomEpoxyController.adapter) concatAdapter.addAdapter(roomEpoxyController.adapter)

View File

@ -174,7 +174,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
try { try {
session.spaceService().getSpace(initialState.spaceId)!!.addChildren( session.spaceService().getSpace(initialState.spaceId)!!.addChildren(
roomId = roomId, roomId = roomId,
viaServers = listOf(session.sessionParams.homeServerHost ?: ""), viaServers = null,
order = null order = null
) )
completed.add(roomId) completed.add(roomId)

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M14.6666,7.9999C14.6666,11.6818 11.6818,14.6666 7.9999,14.6666C4.318,14.6666 1.3333,11.6818 1.3333,7.9999C1.3333,4.318 4.318,1.3333 7.9999,1.3333C11.6818,1.3333 14.6666,4.318 14.6666,7.9999ZM6.3333,7.9999C6.3333,8.4353 6.6115,8.8057 6.9999,8.943V10.9999C6.9999,11.5522 7.4476,11.9999 7.9999,11.9999H8.9999C9.5522,11.9999 9.9999,11.5522 9.9999,10.9999C9.9999,10.4476 9.5522,9.9999 8.9999,9.9999V7.9999C8.9999,7.4476 8.5522,6.9999 7.9999,6.9999H7.3333C6.781,6.9999 6.3333,7.4476 6.3333,7.9999ZM7.9999,6.6666C8.5522,6.6666 8.9999,6.2189 8.9999,5.6666C8.9999,5.1143 8.5522,4.6666 7.9999,4.6666C7.4476,4.6666 6.9999,5.1143 6.9999,5.6666C6.9999,6.2189 7.4476,6.6666 7.9999,6.6666Z"
android:fillColor="#737D8C"
android:fillType="evenOdd"/>
</vector>

View File

@ -10,7 +10,7 @@
android:id="@+id/roomList" android:id="@+id/roomList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?riotx_header_panel_background" android:background="?riotx_background"
android:overScrollMode="always" android:overScrollMode="always"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_room_to_add_in_space" /> tools:listitem="@layout/item_room_to_add_in_space" />

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@drawable/rounded_rect_shape_8"
android:backgroundTint="?riotx_reaction_background_off"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/itemGenericPillImage"
android:layout_width="20dp"
android:layout_height="20dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_info"
app:tint="?riotx_text_secondary" />
<TextView
android:id="@+id/itemGenericPillText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="start"
tools:text="@tools:sample/lorem" />
</LinearLayout>

View File

@ -0,0 +1,132 @@
<?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/itemGroupLayout"
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">
<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/groupAvatarImageView"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_gravity="center"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:contentDescription="@string/avatar"
android:duplicateParentState="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/indent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/groupCounterBadge"
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="@android:color/white"
android:textSize="10sp"
android:visibility="gone"
app:layout_constraintCircle="@+id/groupAvatarImageView"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="14dp"
tools:background="@drawable/bg_unread_highlight"
tools:text="147"
tools:visibility="visible" />
<TextView
android:id="@+id/groupNameView"
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="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/groupChildrenCollapse"
app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/groupChildrenCollapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:importantForAccessibility="no"
android:src="@drawable/ic_expand_less_white"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/groupTmpLeave"
app:layout_constraintTop_toTopOf="parent"
app:tint="?riotx_text_primary"
tools:ignore="MissingPrefix"
tools:src="@drawable/ic_expand_more_white"
tools:visibility="visible" />
<ImageView
android:id="@+id/groupTmpLeave"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="4dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:importantForAccessibility="no"
android:padding="4dp"
android:src="@drawable/ic_more_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron"
app:layout_constraintTop_toTopOf="parent"
app:tint="?riotx_text_secondary" />
<ImageView
android:id="@+id/groupAvatarChevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_arrow_right"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?riotx_text_primary"
tools:ignore="MissingPrefix" />
<!-- <View-->
<!-- android:id="@+id/groupBottomSeparator"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="1dp"-->
<!-- android:background="?riotx_header_panel_border_mobile"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent" />-->
</im.vector.app.core.platform.CheckableConstraintLayout>

View File

@ -3362,4 +3362,10 @@
<string name="space_mark_as_suggested">Mark as suggested</string> <string name="space_mark_as_suggested">Mark as suggested</string>
<string name="space_mark_as_not_suggested">Mark as not suggested</string> <string name="space_mark_as_not_suggested">Mark as not suggested</string>
<string name="space_manage_rooms_and_spaces">Manage rooms and spaces</string> <string name="space_manage_rooms_and_spaces">Manage rooms and spaces</string>
<string name="labs_space_show_orphan_in_home">Experimental Space - Only show orphans in Home</string>
<string name="spaces_feeling_experimental_subspace">Feeling experimental?\nYou can add existing spaces to a space.</string>
<string name="spaces_no_server_support_title">It looks like your homeserver does not support Spaces yet</string>
<string name="spaces_no_server_support_description">Please contact your homserver admin for further information</string>
</resources> </resources>

View File

@ -52,4 +52,9 @@
android:summary="@string/labs_use_restricted_join_rule_desc"/> android:summary="@string/labs_use_restricted_join_rule_desc"/>
<!--</im.vector.app.core.preference.VectorPreferenceCategory>--> <!--</im.vector.app.core.preference.VectorPreferenceCategory>-->
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_SPACES_HOME_AS_ORPHAN"
android:title="@string/labs_space_show_orphan_in_home"/>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>