Merge pull request #3401 from vector-im/feature/bca/spaces_fix_3386

Fixes #3386 show space description in explore header
This commit is contained in:
Benoit Marty 2021-05-28 15:27:49 +02:00 committed by GitHub
commit 5657da3493
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 521 additions and 205 deletions

View File

@ -54,7 +54,7 @@ interface PermalinkService {
*
* @return the permalink, or null in case of error
*/
fun createRoomPermalink(roomId: String): String?
fun createRoomPermalink(roomId: String, viaServers: List<String>? = null): String?
/**
* Creates a permalink for an event. If you have an event you can use [createPermalink]

View File

@ -32,5 +32,7 @@ data class SpaceChildInfo(
val parentRoomId: String?,
val suggested: Boolean?,
val canonicalAlias: String?,
val aliases: List<String>?
val aliases: List<String>?,
val worldReadable: Boolean
)

View File

@ -28,7 +28,8 @@ sealed class PeekResult {
val numJoinedMembers: Int?,
val roomType: String?,
val viaServers: List<String>,
val someMembers: List<MatrixItem.UserItem>?
val someMembers: List<MatrixItem.UserItem>?,
val isPublic: Boolean
) : PeekResult()
data class PeekingNotAllowed(

View File

@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
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.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
@ -38,6 +39,8 @@ sealed class MatrixItem(
init {
if (BuildConfig.DEBUG) checkId()
}
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
data class EventItem(override val id: String,
@ -47,6 +50,8 @@ sealed class MatrixItem(
init {
if (BuildConfig.DEBUG) checkId()
}
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
data class RoomItem(override val id: String,
@ -56,6 +61,19 @@ sealed class MatrixItem(
init {
if (BuildConfig.DEBUG) checkId()
}
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
data class SpaceItem(override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null)
: MatrixItem(id, displayName, avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
data class RoomAliasItem(override val id: String,
@ -68,6 +86,8 @@ sealed class MatrixItem(
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
override fun getBestName() = id
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
data class GroupItem(override val id: String,
@ -80,6 +100,8 @@ sealed class MatrixItem(
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
override fun getBestName() = id
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
}
open fun getBestName(): String {
@ -92,12 +114,15 @@ sealed class MatrixItem(
}
}
abstract fun updateAvatar(newAvatar: String?): MatrixItem
/**
* Return the prefix as defined in the matrix spec (and not extracted from the id)
*/
fun getIdPrefix() = when (this) {
is UserItem -> '@'
is EventItem -> '$'
is SpaceItem,
is RoomItem -> '!'
is RoomAliasItem -> '#'
is GroupItem -> '+'
@ -148,7 +173,11 @@ fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
MatrixItem.SpaceItem(roomId, displayName, avatarUrl)
} else {
MatrixItem.RoomItem(roomId, displayName, avatarUrl)
}
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
@ -159,4 +188,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias ?: "", avatarUrl)
fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)
} else {
MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl)
}

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
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.model.SpaceParentInfo
@ -92,7 +93,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
parentRoomId = roomSummaryEntity.roomId,
suggested = it.suggested,
canonicalAlias = it.childSummaryEntity?.canonicalAlias,
aliases = it.childSummaryEntity?.aliases?.toList()
aliases = it.childSummaryEntity?.aliases?.toList(),
worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC
)
},
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()

View File

@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor(
return permalinkFactory.createPermalink(id)
}
override fun createRoomPermalink(roomId: String): String? {
return permalinkFactory.createRoomPermalink(roomId)
override fun createRoomPermalink(roomId: String, viaServers: List<String>?): String? {
return permalinkFactory.createRoomPermalink(roomId, viaServers)
}
override fun createPermalink(roomId: String, eventId: String): String {

View File

@ -40,11 +40,18 @@ internal class PermalinkFactory @Inject constructor(
} else MATRIX_TO_URL_BASE + escape(id)
}
fun createRoomPermalink(roomId: String): String? {
fun createRoomPermalink(roomId: String, via: List<String>? = null): String? {
return if (roomId.isEmpty()) {
null
} else {
MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId)
buildString {
append(MATRIX_TO_URL_BASE)
append(escape(roomId))
append(
via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.asUrlViaParameters(it) }
?: viaParameterFinder.computeViaParams(userId, roomId)
)
}
}
}

View File

@ -39,8 +39,11 @@ internal class ViaParameterFinder @Inject constructor(
* 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") }
return asUrlViaParameters(computeViaParams(userId, roomId, 3))
}
fun asUrlViaParameters(viaList: List<String>): String {
return viaList.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
}
fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {

View File

@ -23,6 +23,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
@ -105,7 +107,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
numJoinedMembers = publicRepoResult.numJoinedMembers,
viaServers = serverList,
roomType = null, // would be nice to get that from directory...
someMembers = null
someMembers = null,
isPublic = true
)
}
@ -143,6 +146,11 @@ internal class DefaultPeekRoomTask @Inject constructor(
}
}
val historyVisibility =
stateEvents
.lastOrNull { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY && it.stateKey?.isNotEmpty() == true }
?.let { it.content?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility }
val roomType = stateEvents
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
?.content
@ -158,7 +166,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
numJoinedMembers = memberCount,
roomType = roomType,
viaServers = serverList,
someMembers = someMembers
someMembers = someMembers,
isPublic = historyVisibility == RoomHistoryVisibility.WORLD_READABLE
)
} catch (failure: Throwable) {
// Would be M_FORBIDDEN if cannot peek :/

View File

@ -147,7 +147,8 @@ internal class DefaultSpaceService @Inject constructor(
parentRoomId = childStateEv.roomId,
suggested = childStateEvContent.suggested,
canonicalAlias = childSummary.canonicalAlias,
aliases = childSummary.aliases
aliases = childSummary.aliases,
worldReadable = childSummary.worldReadable
)
}
}.orEmpty()

1
newsfragment/3401.bugfix Normal file
View File

@ -0,0 +1 @@
Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools)

1
newsfragment/3406.bugfix Normal file
View File

@ -0,0 +1 @@
Fix | Suggest Rooms, Show a detailed view of the room on click

View File

@ -71,7 +71,7 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder<FormEditableS
.into(holder.image)
}
matrixItem != null -> {
avatarRenderer?.renderSpace(matrixItem!!, holder.image)
avatarRenderer?.render(matrixItem!!, holder.image)
}
else -> {
avatarRenderer?.clear(holder.image)

View File

@ -66,24 +66,24 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
DrawableImageViewTarget(imageView))
}
@UiThread
fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
val placeholder = getSpacePlaceholderDrawable(matrixItem)
val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
glideRequests
.load(resolvedUrl)
.transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
.placeholder(placeholder)
.into(DrawableImageViewTarget(imageView))
}
fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
renderSpace(
matrixItem,
imageView,
GlideApp.with(imageView)
)
}
// fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
// renderSpace(
// matrixItem,
// imageView,
// GlideApp.with(imageView)
// )
// }
//
// @UiThread
// private fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
// val placeholder = getSpacePlaceholderDrawable(matrixItem)
// val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
// glideRequests
// .load(resolvedUrl)
// .transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
// .placeholder(placeholder)
// .into(DrawableImageViewTarget(imageView))
// }
fun clear(imageView: ImageView) {
// It can be called after recycler view is destroyed, just silently catch
@ -137,7 +137,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
target: Target<Drawable>) {
val placeholder = getPlaceholderDrawable(matrixItem)
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
.apply(RequestOptions.circleCropTransform())
.apply {
when (matrixItem) {
is MatrixItem.SpaceItem -> {
transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8))))
}
else -> {
apply(RequestOptions.circleCropTransform())
}
}
}
.placeholder(placeholder)
.into(target)
}
@ -197,17 +206,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
.beginConfig()
.bold()
.endConfig()
.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
}
@AnyThread
fun getSpacePlaceholderDrawable(matrixItem: MatrixItem): Drawable {
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
return TextDrawable.builder()
.beginConfig()
.bold()
.endConfig()
.buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8))
.let {
when (matrixItem) {
is MatrixItem.SpaceItem -> {
it.buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8))
}
else -> {
it.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
}
}
}
}
// PRIVATE API *********************************************************************************

View File

@ -30,4 +30,5 @@ sealed class RoomListAction : VectorViewModelAction {
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
data class LeaveRoom(val roomId: String) : RoomListAction()
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
data class ShowRoomDetails(val roomId: String, val viaServers: List<String>?) : RoomListAction()
}

View File

@ -108,10 +108,11 @@ class RoomListFragment @Inject constructor(
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomListViewModel.observeViewEvents {
when (it) {
is RoomListViewEvents.Loading -> showLoading(it.message)
is RoomListViewEvents.Failure -> showFailure(it.throwable)
is RoomListViewEvents.SelectRoom -> handleSelectRoom(it)
is RoomListViewEvents.Done -> Unit
is RoomListViewEvents.Loading -> showLoading(it.message)
is RoomListViewEvents.Failure -> showFailure(it.throwable)
is RoomListViewEvents.SelectRoom -> handleSelectRoom(it)
is RoomListViewEvents.Done -> Unit
is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
}.exhaustive
}
@ -155,6 +156,10 @@ class RoomListFragment @Inject constructor(
showErrorInSnackbar(throwable)
}
private fun handleShowMxToLink(link: String) {
navigator.openMatrixToBottomSheet(requireContext(), link)
}
override fun onDestroyView() {
adapterInfosList.onEach { it.contentEpoxyController.removeModelBuildListener(modelBuildListener) }
adapterInfosList.clear()
@ -474,6 +479,10 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers))
}
override fun onSuggestedRoomClicked(room: SpaceChildInfo) {
roomListViewModel.handle(RoomListAction.ShowRoomDetails(room.childRoomId, room.viaServers))
}
override fun onRejectRoomInvitation(room: RoomSummary) {
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
roomListViewModel.handle(RoomListAction.RejectInvitation(room))

View File

@ -26,4 +26,5 @@ interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListen
fun onRejectRoomInvitation(room: RoomSummary)
fun onAcceptRoomInvitation(room: RoomSummary)
fun onJoinSuggestedRoom(room: SpaceChildInfo)
fun onSuggestedRoomClicked(room: SpaceChildInfo)
}

View File

@ -29,4 +29,5 @@ sealed class RoomListViewEvents : VectorViewEvents {
data class SelectRoom(val roomSummary: RoomSummary) : RoomListViewEvents()
object Done : RoomListViewEvents()
data class NavigateToMxToBottomSheet(val link: String) : RoomListViewEvents()
}

View File

@ -161,6 +161,7 @@ class RoomListViewModel @Inject constructor(
is RoomListAction.ToggleTag -> handleToggleTag(action)
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action)
}.exhaustive
}
@ -289,6 +290,12 @@ class RoomListViewModel @Inject constructor(
}
}
private fun handleShowRoomDetails(action: RoomListAction.ShowRoomDetails) {
session.permalinkService().createRoomPermalink(action.roomId, action.viaServers)?.let {
_viewEvents.post(RoomListViewEvents.NavigateToMxToBottomSheet(it))
}
}
private fun handleToggleTag(action: RoomListAction.ToggleTag) {
session.getRoom(action.roomId)?.let { room ->
viewModelScope.launch(Dispatchers.IO) {

View File

@ -16,7 +16,6 @@
package im.vector.app.features.home.room.list
import android.view.View
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import im.vector.app.R
@ -56,7 +55,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
onJoinClick: View.OnClickListener): VectorEpoxyModel<*> {
listener: RoomListListener?): VectorEpoxyModel<*> {
return SpaceChildInfoItem_()
.id("sug_${spaceChildInfo.childRoomId}")
.matrixItem(spaceChildInfo.toMatrixItem())
@ -65,7 +64,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
.buttonLabel(stringProvider.getString(R.string.join))
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
.buttonClickListener(onJoinClick)
.buttonClickListener(DebouncedClickListener({ listener?.onJoinSuggestedRoom(spaceChildInfo) }))
.itemClickListener(DebouncedClickListener({ listener?.onSuggestedRoomClicked(spaceChildInfo) }))
}
private fun createInvitationItem(roomSummary: RoomSummary,

View File

@ -48,7 +48,6 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
@EpoxyAttribute var memberCount: Int = 0
@EpoxyAttribute var loading: Boolean = false
@EpoxyAttribute var space: Boolean = false
@EpoxyAttribute var buttonLabel: String? = null
@ -63,12 +62,8 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false
}
holder.titleView.text = matrixItem.getBestName()
if (space) {
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
} else {
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
holder.titleView.text = matrixItem.displayName ?: holder.rootView.context.getString(R.string.unnamed_room)
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.descriptionText.text = span {
span {

View File

@ -24,11 +24,7 @@ class SuggestedRoomListController(
override fun buildModels(data: SuggestedRoomInfo?) {
data?.rooms?.forEach { info ->
roomSummaryItemFactory.createSuggestion(info, data.joinEcho) {
listener?.onJoinSuggestedRoom(info)
}.let {
add(it)
}
add(roomSummaryItemFactory.createSuggestion(info, data.joinEcho, listener))
}
}
}

View File

@ -41,14 +41,15 @@ data class MatrixToBottomSheetState(
sealed class RoomInfoResult {
data class FullInfo(
val roomItem: MatrixItem.RoomItem,
val roomItem: MatrixItem,
val name: String,
val topic: String,
val memberCount: Int?,
val alias: String?,
val membership: Membership,
val roomType: String?,
val viaServers: List<String>?
val viaServers: List<String>?,
val isPublic: Boolean
) : RoomInfoResult()
data class PartialInfo(

View File

@ -118,11 +118,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
session.getRoom(permalinkData.roomIdOrAlias)
}
?.roomSummary()
// don't take if not active, as it could be outdated
?.takeIf { it.membership.isActive() }
// XXX fix that
val forceRefresh = true
if (!forceRefresh && knownRoom != null) {
// don't take if not Join, as it could be outdated
?.takeIf { it.membership == Membership.JOIN }
if (knownRoom != null) {
setState {
copy(
roomPeekResult = Success(
@ -134,7 +132,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
alias = knownRoom.canonicalAlias,
membership = knownRoom.membership,
roomType = knownRoom.roomType,
viaServers = null
viaServers = null,
isPublic = knownRoom.isPublic
)
)
)
@ -150,7 +149,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
alias = peekResult.alias,
membership = knownRoom?.membership ?: Membership.NONE,
roomType = peekResult.roomType,
viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters
viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters,
isPublic = peekResult.isPublic
).also {
peekResult.someMembers?.let { checkForKnownMembers(it) }
}

View File

@ -39,7 +39,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
import javax.inject.Inject
class MatrixToRoomSpaceFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer
private val avatarRenderer: AvatarRenderer,
private val spaceCardRenderer: SpaceCardRenderer
) : VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() {
private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel()
@ -78,12 +79,19 @@ class MatrixToRoomSpaceFragment @Inject constructor(
when (val peek = item.invoke()) {
is RoomInfoResult.FullInfo -> {
val matrixItem = peek.roomItem
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
if (peek.roomType == RoomType.SPACE) {
views.matrixToBetaTag.isVisible = true
avatarRenderer.renderSpace(matrixItem, views.matrixToCardAvatar)
views.matrixToAccessImage.isVisible = true
if (peek.isPublic) {
views.matrixToAccessText.setTextOrHide(context?.getString(R.string.public_space))
views.matrixToAccessImage.setImageResource(R.drawable.ic_public_room)
} else {
views.matrixToAccessText.setTextOrHide(context?.getString(R.string.private_space))
views.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
}
} else {
views.matrixToBetaTag.isVisible = false
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
}
views.matrixToCardNameText.setTextOrHide(peek.name)
views.matrixToCardAliasText.setTextOrHide(peek.alias)
@ -166,25 +174,12 @@ class MatrixToRoomSpaceFragment @Inject constructor(
}
}
val images = listOf(views.knownMember1, views.knownMember2, views.knownMember3, views.knownMember4, views.knownMember5)
listOf(views.knownMember1, views.knownMember2, views.knownMember3, views.knownMember4, views.knownMember5)
.onEach { it.isGone = true }
when (state.peopleYouKnow) {
is Success -> {
val someYouKnow = state.peopleYouKnow.invoke()
if (someYouKnow.isEmpty()) {
views.peopleYouMayKnowText.isVisible = false
} else {
someYouKnow.forEachIndexed { index, item ->
images[index].isVisible = true
avatarRenderer.render(item, images[index])
}
views.peopleYouMayKnowText.setTextOrHide(
resources.getQuantityString(R.plurals.space_people_you_know,
someYouKnow.count(),
someYouKnow.count()
)
)
}
spaceCardRenderer.renderPeopleYouKnow(views, someYouKnow)
}
else -> {
views.peopleYouMayKnowText.isVisible = false

View File

@ -0,0 +1,149 @@
/*
* 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.matrixto
import androidx.core.view.isGone
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.FragmentMatrixToRoomSpaceCardBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
import im.vector.app.features.home.room.detail.timeline.tools.linkify
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.user.model.User
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class SpaceCardRenderer @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider
) {
fun render(spaceSummary: RoomSummary?,
peopleYouKnow: List<User>,
matrixLinkCallback: TimelineEventController.UrlClickCallback?,
inCard: FragmentMatrixToRoomSpaceCardBinding) {
if (spaceSummary == null) {
inCard.matrixToCardContentVisibility.isVisible = false
inCard.matrixToCardButtonLoading.isVisible = true
} else {
inCard.matrixToCardContentVisibility.isVisible = true
inCard.matrixToCardButtonLoading.isVisible = false
avatarRenderer.render(spaceSummary.toMatrixItem(), inCard.matrixToCardAvatar)
inCard.matrixToCardNameText.text = spaceSummary.name
inCard.matrixToBetaTag.isVisible = true
inCard.matrixToCardAliasText.setTextOrHide(spaceSummary.canonicalAlias)
inCard.matrixToCardDescText.setTextOrHide(spaceSummary.topic.linkify(matrixLinkCallback))
if (spaceSummary.isPublic) {
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.public_space))
inCard.matrixToAccessImage.isVisible = true
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_public_room)
} else {
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.private_space))
inCard.matrixToAccessImage.isVisible = true
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
}
val memberCount = spaceSummary.otherMemberIds.size
if (memberCount != 0) {
inCard.matrixToMemberPills.isVisible = true
inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
} else {
// hide the pill
inCard.matrixToMemberPills.isVisible = false
}
renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() })
}
inCard.matrixToCardDescText.movementMethod = createLinkMovementMethod(object : TimelineEventController.UrlClickCallback {
override fun onUrlClicked(url: String, title: String): Boolean {
return false
}
override fun onUrlLongClicked(url: String): Boolean {
// host.callback?.onUrlInTopicLongClicked(url)
return true
}
})
}
fun render(spaceChildInfo: SpaceChildInfo?,
peopleYouKnow: List<User>,
matrixLinkCallback: TimelineEventController.UrlClickCallback?,
inCard: FragmentMatrixToRoomSpaceCardBinding) {
if (spaceChildInfo == null) {
inCard.matrixToCardContentVisibility.isVisible = false
inCard.matrixToCardButtonLoading.isVisible = true
} else {
inCard.matrixToCardContentVisibility.isVisible = true
inCard.matrixToCardButtonLoading.isVisible = false
avatarRenderer.render(spaceChildInfo.toMatrixItem(), inCard.matrixToCardAvatar)
inCard.matrixToCardNameText.setTextOrHide(spaceChildInfo.name)
inCard.matrixToBetaTag.isVisible = true
inCard.matrixToCardAliasText.setTextOrHide(spaceChildInfo.canonicalAlias)
inCard.matrixToCardDescText.setTextOrHide(spaceChildInfo.topic?.linkify(matrixLinkCallback))
if (spaceChildInfo.worldReadable) {
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.public_space))
inCard.matrixToAccessImage.isVisible = true
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_public_room)
} else {
inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.private_space))
inCard.matrixToAccessImage.isVisible = true
inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private)
}
val memberCount = spaceChildInfo.activeMemberCount ?: 0
if (memberCount != 0) {
inCard.matrixToMemberPills.isVisible = true
inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
} else {
// hide the pill
inCard.matrixToMemberPills.isVisible = false
}
renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() })
}
}
fun renderPeopleYouKnow(inCard: FragmentMatrixToRoomSpaceCardBinding, peopleYouKnow: List<MatrixItem.UserItem>) {
val images = listOf(
inCard.knownMember1,
inCard.knownMember2,
inCard.knownMember3,
inCard.knownMember4,
inCard.knownMember5
).onEach { it.isGone = true }
if (peopleYouKnow.isEmpty()) {
inCard.peopleYouMayKnowText.isVisible = false
} else {
peopleYouKnow.forEachIndexed { index, item ->
images[index].isVisible = true
avatarRenderer.render(item, images[index])
}
inCard.peopleYouMayKnowText.setTextOrHide(
stringProvider.getQuantityString(R.plurals.space_people_you_know,
peopleYouKnow.count(),
peopleYouKnow.count()
)
)
}
}
}

View File

@ -245,7 +245,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = when (mode) {
Mode.FULL_SIZE,
Mode.STICKER -> resolveUrl(data)
Mode.STICKER -> resolveUrl(data)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
}
// Fallback to base url
@ -313,7 +313,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
finalHeight = min(maxImageWidth * height / width, maxImageHeight)
finalWidth = finalHeight * width / height
}
Mode.STICKER -> {
Mode.STICKER -> {
// limit on width
val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2)
finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp)

View File

@ -68,16 +68,14 @@ class RoomSettingsController @Inject constructor(
id("avatar")
enabled(data.actionPermissions.canChangeAvatar)
when (val avatarAction = data.avatarAction) {
RoomSettingsViewState.AvatarAction.None -> {
RoomSettingsViewState.AvatarAction.None -> {
// Use the current value
avatarRenderer(host.avatarRenderer)
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl))
matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl))
}
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
imageUri(null)
is RoomSettingsViewState.AvatarAction.UpdateAvatar ->
imageUri(avatarAction.newAvatarUri)
RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null)
is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri)
}
clickListener { host.callback?.onAvatarChange() }
deleteListener { host.callback?.onAvatarDelete() }

View File

@ -96,7 +96,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
val session = activeSessionHolder.getSafeActiveSession() ?: return
val roomSummary = session.getRoomSummary(spaceArgs.spaceId)
roomSummary?.toMatrixItem()?.let {
avatarRenderer.renderSpace(it, views.spaceAvatarImageView)
avatarRenderer.render(it, views.spaceAvatarImageView)
}
views.spaceNameView.text = roomSummary?.displayName
views.spaceDescription.setTextOrHide(roomSummary?.topic?.takeIf { it.isNotEmpty() })

View File

@ -87,7 +87,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
holder.indentSpace.isVisible = indent > 0
holder.separator.isVisible = showSeparator
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.counterBadgeView.render(countState)
}

View File

@ -81,7 +81,7 @@ abstract class SubSpaceSummaryItem : VectorEpoxyModel<SubSpaceSummaryItem.Holder
width = indent * 30
}
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.counterBadgeView.render(countState)
}

View File

@ -54,7 +54,7 @@ class SpaceDetailEpoxyController @Inject constructor(
enabled(true)
imageUri(data?.avatarUri)
avatarRenderer(host.avatarRenderer)
matrixItem(data?.name?.let { MatrixItem.RoomItem("!", it, null).takeIf { !it.displayName.isNullOrBlank() } })
matrixItem(data?.name?.let { MatrixItem.SpaceItem("!", it, null).takeIf { !it.displayName.isNullOrBlank() } })
clickListener { host.listener?.onAvatarChange() }
deleteListener { host.listener?.onAvatarDelete() }
}

View File

@ -122,13 +122,15 @@ class SpaceDirectoryController @Inject constructor(
val isSpace = info.roomType == RoomType.SPACE
val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true
val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false
// if it's known use that matrixItem because it would have a better computed name
val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem()
?: info.toMatrixItem()
spaceChildInfoItem {
id(info.childRoomId)
matrixItem(info.toMatrixItem())
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
topic(info.topic)
memberCount(info.activeMemberCount ?: 0)
space(isSpace)
loading(isLoading)
buttonLabel(
if (isJoined) host.stringProvider.getString(R.string.action_open)

View File

@ -16,6 +16,7 @@
package im.vector.app.features.spaces.explore
import android.content.DialogInterface
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
@ -23,19 +24,33 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.dialogs.withColoredButton
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.isValidUrl
import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.matrixto.SpaceCardRenderer
import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import java.net.URL
import javax.inject.Inject
@Parcelize
@ -44,9 +59,13 @@ data class SpaceDirectoryArgs(
) : Parcelable
class SpaceDirectoryFragment @Inject constructor(
private val epoxyController: SpaceDirectoryController
private val epoxyController: SpaceDirectoryController,
private val permalinkHandler: PermalinkHandler,
private val spaceCardRenderer: SpaceCardRenderer,
private val colorProvider: ColorProvider
) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
SpaceDirectoryController.InteractionListener,
TimelineEventController.UrlClickCallback,
OnBackPressed {
override fun getMenuRes() = R.menu.menu_space_directory
@ -71,6 +90,9 @@ class SpaceDirectoryFragment @Inject constructor(
viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) {
invalidateOptionsMenu()
}
views.spaceCard.matrixToCardMainButton.isVisible = false
views.spaceCard.matrixToCardSecondaryButton.isVisible = false
}
override fun onDestroyView() {
@ -82,10 +104,21 @@ class SpaceDirectoryFragment @Inject constructor(
override fun invalidate() = withState(viewModel) { state ->
epoxyController.setData(state)
val title = state.hierarchyStack.lastOrNull()?.let { currentParent ->
val currentParent = state.hierarchyStack.lastOrNull()?.let { currentParent ->
state.spaceSummaryApiResult.invoke()?.firstOrNull { it.childRoomId == currentParent }
}?.name ?: getString(R.string.space_explore_activity_title)
views.toolbar.title = title
}
if (currentParent == null) {
val title = getString(R.string.space_explore_activity_title)
views.toolbar.title = title
spaceCardRenderer.render(state.spaceSummary.invoke(), emptyList(), this, views.spaceCard)
} else {
val title = currentParent.name ?: currentParent.canonicalAlias ?: getString(R.string.space_explore_activity_title)
views.toolbar.title = title
spaceCardRenderer.render(currentParent, emptyList(), this, views.spaceCard)
}
}
override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->
@ -96,7 +129,7 @@ class SpaceDirectoryFragment @Inject constructor(
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.spaceAddRoom -> {
R.id.spaceAddRoom -> {
withState(viewModel) { state ->
addExistingRooms(state.spaceId)
}
@ -138,6 +171,44 @@ class SpaceDirectoryFragment @Inject constructor(
override fun addExistingRooms(spaceId: String) {
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms))
}
override fun onUrlClicked(url: String, title: String): Boolean {
permalinkHandler
.launch(requireActivity(), url, null)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { managed ->
if (!managed) {
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.external_link_confirmation_title)
.setMessage(
getString(R.string.external_link_confirmation_message, title, url)
.toSpannable()
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast))
)
.setPositiveButton(R.string._continue) { _, _ ->
openUrlInExternalBrowser(requireContext(), url)
}
.setNegativeButton(R.string.cancel, null)
.show()
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
} else {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
}
}
}
.disposeOnDestroyView()
// In fact it is always managed
return true
}
override fun onUrlLongClicked(url: String): Boolean {
// nothing?
return false
}
// override fun navigateToRoom(roomId: String) {
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
// }

View File

@ -37,7 +37,9 @@ data class SpaceDirectoryState(
val joinedRoomsIds: Set<String> = emptySet(),
// keys are room alias or roomId
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(),
val canAddRooms: Boolean = false
val canAddRooms: Boolean = false,
// cached room summaries of known rooms
val knownRoomSummaries : List<RoomSummary> = emptyList()
) : MvRxState {
constructor(args: SpaceDirectoryArgs) : this(
spaceId = args.spaceId

View File

@ -66,7 +66,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
val spaceSum = session.getRoomSummary(initialState.spaceId)
setState {
copy(
childList = spaceSum?.spaceChildren ?: emptyList()
childList = spaceSum?.spaceChildren ?: emptyList(),
spaceSummary = spaceSum?.let { Success(spaceSum) } ?: Loading()
)
}
@ -101,9 +102,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
viewModelScope.launch(Dispatchers.IO) {
try {
val query = session.spaceService().querySpaceChildren(initialState.spaceId)
val knownSummaries = query.second.mapNotNull {
session.getRoomSummary(it.childRoomId)
?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced)
}
setState {
copy(
spaceSummaryApiResult = Success(query.second)
spaceSummaryApiResult = Success(query.second),
knownRoomSummaries = knownSummaries
)
}
} catch (failure: Throwable) {
@ -148,7 +154,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId))
}
}
SpaceDirectoryViewAction.HandleBack -> {
SpaceDirectoryViewAction.HandleBack -> {
withState {
if (it.hierarchyStack.isEmpty()) {
_viewEvents.post(SpaceDirectoryViewEvents.Dismiss)
@ -161,20 +167,20 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
}
}
}
is SpaceDirectoryViewAction.JoinOrOpen -> {
is SpaceDirectoryViewAction.JoinOrOpen -> {
handleJoinOrOpen(action.spaceChildInfo)
}
is SpaceDirectoryViewAction.NavigateToRoom -> {
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))
}
}
SpaceDirectoryViewAction.Retry -> {
SpaceDirectoryViewAction.Retry -> {
refreshFromApi()
}
}

View File

@ -22,7 +22,6 @@ import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@ -33,12 +32,12 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.ButtonStateView
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.utils.toast
import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.matrixto.SpaceCardRenderer
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
@ -60,6 +59,9 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
@Inject
lateinit var avatarRenderer: AvatarRenderer
@Inject
lateinit var spaceCardRenderer: SpaceCardRenderer
private val viewModel: SpaceInviteBottomSheetViewModel by fragmentViewModel(SpaceInviteBottomSheetViewModel::class)
@Inject lateinit var viewModelFactory: SpaceInviteBottomSheetViewModel.Factory
@ -133,12 +135,7 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
views.inviterMxid.isVisible = false
}
views.spaceCard.matrixToCardContentVisibility.isVisible = true
summary?.toMatrixItem()?.let { avatarRenderer.renderSpace(it, views.spaceCard.matrixToCardAvatar) }
views.spaceCard.matrixToCardNameText.text = summary?.displayName
views.spaceCard.matrixToBetaTag.isVisible = true
views.spaceCard.matrixToCardAliasText.setTextOrHide(summary?.canonicalAlias)
views.spaceCard.matrixToCardDescText.setTextOrHide(summary?.topic)
spaceCardRenderer.render(summary, state.peopleYouKnow.invoke().orEmpty(), null, views.spaceCard)
views.spaceCard.matrixToCardMainButton.button.text = getString(R.string.accept)
views.spaceCard.matrixToCardSecondaryButton.button.text = getString(R.string.decline)
@ -178,40 +175,6 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
views.spaceCard.matrixToCardSecondaryButton.button.isEnabled = true
}
}
val memberCount = summary?.otherMemberIds?.size ?: 0
if (memberCount != 0) {
views.spaceCard.matrixToMemberPills.isVisible = true
views.spaceCard.spaceChildMemberCountText.text = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
} else {
// hide the pill
views.spaceCard.matrixToMemberPills.isVisible = false
}
val peopleYouKnow = state.peopleYouKnow.invoke().orEmpty()
val images = listOf(
views.spaceCard.knownMember1,
views.spaceCard.knownMember2,
views.spaceCard.knownMember3,
views.spaceCard.knownMember4,
views.spaceCard.knownMember5
).onEach { it.isGone = true }
if (peopleYouKnow.isEmpty()) {
views.spaceCard.peopleYouMayKnowText.isVisible = false
} else {
peopleYouKnow.forEachIndexed { index, item ->
images[index].isVisible = true
avatarRenderer.render(item.toMatrixItem(), images[index])
}
views.spaceCard.peopleYouMayKnowText.setTextOrHide(
resources.getQuantityString(R.plurals.space_people_you_know,
peopleYouKnow.count(),
peopleYouKnow.count()
)
)
}
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetInvitedToSpaceBinding {

View File

@ -27,7 +27,6 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.RoomCategoryItem_
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
@ -155,7 +154,6 @@ class AddRoomListController @Inject constructor(
id(item.roomId)
matrixItem(item.toMatrixItem())
avatarRenderer(host.avatarRenderer)
space(item.roomType == RoomType.SPACE)
selected(host.selectedItems[item.roomId] ?: false)
itemClickListener(DebouncedClickListener({
host.listener?.onItemSelected(item)

View File

@ -34,18 +34,14 @@ abstract class RoomManageSelectionItem : VectorEpoxyModel<RoomManageSelectionIte
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var space: Boolean = false
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var suggested: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
if (space) {
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
} else {
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.titleText.text = matrixItem.getBestName()
if (selected) {

View File

@ -33,17 +33,13 @@ abstract class RoomSelectionItem : VectorEpoxyModel<RoomSelectionItem.Holder>()
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var space: Boolean = false
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
if (space) {
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
} else {
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.titleText.text = matrixItem.getBestName()
if (selected) {

View File

@ -27,7 +27,6 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
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.toMatrixItem
import javax.inject.Inject
@ -83,7 +82,6 @@ class SpaceManageRoomsController @Inject constructor(
matrixItem(childInfo.toMatrixItem())
avatarRenderer(host.avatarRenderer)
suggested(childInfo.suggested ?: false)
space(childInfo.roomType == RoomType.SPACE)
selected(data.selectedRooms.contains(childInfo.childRoomId))
itemClickListener(DebouncedClickListener({
host.listener?.toggleSelection(childInfo)

View File

@ -71,7 +71,7 @@ class SpaceSettingsController @Inject constructor(
// Use the current value
avatarRenderer(host.avatarRenderer)
// We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar.
matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl))
matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl))
}
RoomSettingsViewState.AvatarAction.DeleteAvatar ->
imageUri(null)

View File

@ -139,7 +139,7 @@ class SpaceSettingsFragment @Inject constructor(
drawableProvider.getDrawable(R.drawable.ic_beta_pill),
null
)
avatarRenderer.renderSpace(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView)
views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
}

View File

@ -148,8 +148,8 @@ class SpacePreviewFragment @Inject constructor(
// val roomPeekResult = preview.summary.roomPeekResult
val spaceName = spacePreviewState.spaceInfo.invoke()?.name ?: spacePreviewState.name ?: ""
val spaceAvatarUrl = spacePreviewState.spaceInfo.invoke()?.avatarUrl ?: spacePreviewState.avatarUrl
val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spaceName, spaceAvatarUrl)
avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
val mxItem = MatrixItem.SpaceItem(spacePreviewState.idOrAlias, spaceName, spaceAvatarUrl)
avatarRenderer.render(mxItem, views.spacePreviewToolbarAvatar)
views.roomPreviewNoPreviewToolbarTitle.text = spaceName
// }
// is SpacePeekResult.SpacePeekError,

View File

@ -48,8 +48,8 @@ abstract class SubSpaceItem : VectorEpoxyModel<SubSpaceItem.Holder>() {
super.bind(holder)
holder.nameText.text = title
avatarRenderer.renderSpace(
MatrixItem.RoomItem(roomId, title, avatarUrl),
avatarRenderer.render(
MatrixItem.SpaceItem(roomId, title, avatarUrl),
holder.avatarImageView
)
holder.tabView.tabDepth = depth

View File

@ -58,7 +58,7 @@
android:id="@+id/matrixToCardAliasText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginTop="8dp"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="textStart"
@ -70,12 +70,43 @@
tools:text="@sample/rooms.json/data/alias"
tools:visibility="visible" />
<ImageView
android:id="@+id/matrixToAccessImage"
android:layout_width="16dp"
android:layout_height="16dp"
android:importantForAccessibility="no"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/matrixToAccessText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/matrixToAccessText"
app:tint="?riotx_text_secondary"
tools:src="@drawable/ic_public_room"
tools:visibility="visible" />
<TextView
android:id="@+id/matrixToAccessText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textColor="?riotx_text_secondary"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/matrixToAccessImage"
app:layout_constraintTop_toBottomOf="@id/matrixToCardAliasText"
app:layout_goneMarginTop="0dp"
tools:text="Public Space"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/matrixToMemberPills"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/pill_receipt"
android:backgroundTint="?riotx_reaction_background_off"
android:gravity="center"
android:orientation="horizontal"
android:paddingStart="12dp"
@ -85,7 +116,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/matrixToCardAliasText">
app:layout_constraintTop_toBottomOf="@id/matrixToAccessText">
<ImageView
android:id="@+id/spaceChildMemberCountIcon"
@ -113,6 +144,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:autoLink="web"
android:maxLines="4"
android:textAlignment="textStart"
android:textColor="?riotx_text_secondary"

View File

@ -7,29 +7,56 @@
android:layout_height="match_parent"
android:background="?riotx_background">
<androidx.constraintlayout.widget.ConstraintLayout
<com.google.android.material.appbar.AppBarLayout
style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:elevation="4dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/spaceExploreCollapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_height="match_parent"
android:theme="@style/Vector.Toolbar.Profile"
app:contentScrim="?riotx_background"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:scrimAnimationDuration="250"
app:scrimVisibleHeightTrigger="120dp"
app:titleEnabled="false"
app:toolbarId="@+id/toolbar">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomDirectoryPickerList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:listitem="@layout/item_room_directory" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp">
<include
android:id="@+id/spaceCard"
layout="@layout/fragment_matrix_to_room_space_card" />
</FrameLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp"
app:layout_collapseMode="pin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomDirectoryPickerList"
android:background="?riotx_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_room_directory" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -3267,6 +3267,7 @@
<string name="a11y_rule_notify_off">Do not notify</string>
<string name="a11y_view_read_receipts">View read receipts</string>
<string name="a11y_public_room">This room is public</string>
<string name="a11y_public_space">This Space is public</string>
<string name="dev_tools_menu_name">Dev Tools</string>
<string name="dev_tools_explore_room_state">Explore Room State</string>
@ -3298,6 +3299,8 @@
<string name="event_status_delete_all_failed_dialog_title">Delete unsent messages</string>
<string name="event_status_delete_all_failed_dialog_message">Are you sure you want to delete all unsent messages in this room?</string>
<string name="public_space">Public space</string>
<string name="private_space">Private space</string>
<string name="add_space">Add Space</string>
<string name="your_public_space">Your public space</string>
<string name="your_private_space">Your private space</string>
@ -3393,4 +3396,5 @@
<string name="this_space_has_no_rooms_not_admin">Some rooms may be hidden because theyre private and you need an invite.\nYou dont have permission to add rooms.</string>
<string name="this_space_has_no_rooms_admin">Some rooms may be hidden because theyre private and you need an invite.</string>
<string name="unnamed_room">Unnamed Room</string>
</resources>