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 * @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] * 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 parentRoomId: String?,
val suggested: Boolean?, val suggested: Boolean?,
val canonicalAlias: String?, 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 numJoinedMembers: Int?,
val roomType: String?, val roomType: String?,
val viaServers: List<String>, val viaServers: List<String>,
val someMembers: List<MatrixItem.UserItem>? val someMembers: List<MatrixItem.UserItem>?,
val isPublic: Boolean
) : PeekResult() ) : PeekResult()
data class PeekingNotAllowed( 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.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary 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.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.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.sender.SenderInfo
@ -38,6 +39,8 @@ sealed class MatrixItem(
init { init {
if (BuildConfig.DEBUG) checkId() if (BuildConfig.DEBUG) checkId()
} }
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
} }
data class EventItem(override val id: String, data class EventItem(override val id: String,
@ -47,6 +50,8 @@ sealed class MatrixItem(
init { init {
if (BuildConfig.DEBUG) checkId() if (BuildConfig.DEBUG) checkId()
} }
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
} }
data class RoomItem(override val id: String, data class RoomItem(override val id: String,
@ -56,6 +61,19 @@ sealed class MatrixItem(
init { init {
if (BuildConfig.DEBUG) checkId() 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, 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 // 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 getBestName() = id
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
} }
data class GroupItem(override val id: String, 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 // 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 getBestName() = id
override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
} }
open fun getBestName(): String { 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) * Return the prefix as defined in the matrix spec (and not extracted from the id)
*/ */
fun getIdPrefix() = when (this) { fun getIdPrefix() = when (this) {
is UserItem -> '@' is UserItem -> '@'
is EventItem -> '$' is EventItem -> '$'
is SpaceItem,
is RoomItem -> '!' is RoomItem -> '!'
is RoomAliasItem -> '#' is RoomAliasItem -> '#'
is GroupItem -> '+' is GroupItem -> '+'
@ -148,7 +173,11 @@ fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, 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) 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 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 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.RoomSummary
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.session.room.model.SpaceParentInfo 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, parentRoomId = roomSummaryEntity.roomId,
suggested = it.suggested, suggested = it.suggested,
canonicalAlias = it.childSummaryEntity?.canonicalAlias, 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() flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()

View File

@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor(
return permalinkFactory.createPermalink(id) return permalinkFactory.createPermalink(id)
} }
override fun createRoomPermalink(roomId: String): String? { override fun createRoomPermalink(roomId: String, viaServers: List<String>?): String? {
return permalinkFactory.createRoomPermalink(roomId) return permalinkFactory.createRoomPermalink(roomId, viaServers)
} }
override fun createPermalink(roomId: String, eventId: String): String { 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) } 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()) { return if (roomId.isEmpty()) {
null null
} else { } 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. * current user one.
*/ */
fun computeViaParams(userId: String, roomId: String): String { fun computeViaParams(userId: String, roomId: String): String {
return computeViaParams(userId, roomId, 3) return asUrlViaParameters(computeViaParams(userId, roomId, 3))
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } }
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> { 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.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent 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.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.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
@ -105,7 +107,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
numJoinedMembers = publicRepoResult.numJoinedMembers, numJoinedMembers = publicRepoResult.numJoinedMembers,
viaServers = serverList, viaServers = serverList,
roomType = null, // would be nice to get that from directory... 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 val roomType = stateEvents
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE } .lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
?.content ?.content
@ -158,7 +166,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
numJoinedMembers = memberCount, numJoinedMembers = memberCount,
roomType = roomType, roomType = roomType,
viaServers = serverList, viaServers = serverList,
someMembers = someMembers someMembers = someMembers,
isPublic = historyVisibility == RoomHistoryVisibility.WORLD_READABLE
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
// Would be M_FORBIDDEN if cannot peek :/ // Would be M_FORBIDDEN if cannot peek :/

View File

@ -147,7 +147,8 @@ internal class DefaultSpaceService @Inject constructor(
parentRoomId = childStateEv.roomId, parentRoomId = childStateEv.roomId,
suggested = childStateEvContent.suggested, suggested = childStateEvContent.suggested,
canonicalAlias = childSummary.canonicalAlias, canonicalAlias = childSummary.canonicalAlias,
aliases = childSummary.aliases aliases = childSummary.aliases,
worldReadable = childSummary.worldReadable
) )
} }
}.orEmpty() }.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) .into(holder.image)
} }
matrixItem != null -> { matrixItem != null -> {
avatarRenderer?.renderSpace(matrixItem!!, holder.image) avatarRenderer?.render(matrixItem!!, holder.image)
} }
else -> { else -> {
avatarRenderer?.clear(holder.image) avatarRenderer?.clear(holder.image)

View File

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

View File

@ -30,4 +30,5 @@ sealed class RoomListAction : VectorViewModelAction {
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction() data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
data class LeaveRoom(val roomId: String) : RoomListAction() data class LeaveRoom(val roomId: String) : RoomListAction()
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<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) sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomListViewModel.observeViewEvents { roomListViewModel.observeViewEvents {
when (it) { when (it) {
is RoomListViewEvents.Loading -> showLoading(it.message) is RoomListViewEvents.Loading -> showLoading(it.message)
is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.Failure -> showFailure(it.throwable)
is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) is RoomListViewEvents.SelectRoom -> handleSelectRoom(it)
is RoomListViewEvents.Done -> Unit is RoomListViewEvents.Done -> Unit
is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
}.exhaustive }.exhaustive
} }
@ -155,6 +156,10 @@ class RoomListFragment @Inject constructor(
showErrorInSnackbar(throwable) showErrorInSnackbar(throwable)
} }
private fun handleShowMxToLink(link: String) {
navigator.openMatrixToBottomSheet(requireContext(), link)
}
override fun onDestroyView() { override fun onDestroyView() {
adapterInfosList.onEach { it.contentEpoxyController.removeModelBuildListener(modelBuildListener) } adapterInfosList.onEach { it.contentEpoxyController.removeModelBuildListener(modelBuildListener) }
adapterInfosList.clear() adapterInfosList.clear()
@ -474,6 +479,10 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers)) 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) { override fun onRejectRoomInvitation(room: RoomSummary) {
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId) notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
roomListViewModel.handle(RoomListAction.RejectInvitation(room)) roomListViewModel.handle(RoomListAction.RejectInvitation(room))

View File

@ -26,4 +26,5 @@ interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListen
fun onRejectRoomInvitation(room: RoomSummary) fun onRejectRoomInvitation(room: RoomSummary)
fun onAcceptRoomInvitation(room: RoomSummary) fun onAcceptRoomInvitation(room: RoomSummary)
fun onJoinSuggestedRoom(room: SpaceChildInfo) 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() data class SelectRoom(val roomSummary: RoomSummary) : RoomListViewEvents()
object Done : 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.ToggleTag -> handleToggleTag(action)
is RoomListAction.ToggleSection -> handleToggleSection(action.section) is RoomListAction.ToggleSection -> handleToggleSection(action.section)
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action) is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action)
}.exhaustive }.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) { private fun handleToggleTag(action: RoomListAction.ToggleTag) {
session.getRoom(action.roomId)?.let { room -> session.getRoom(action.roomId)?.let { room ->
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {

View File

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

View File

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

View File

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

View File

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

View File

@ -118,11 +118,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
session.getRoom(permalinkData.roomIdOrAlias) session.getRoom(permalinkData.roomIdOrAlias)
} }
?.roomSummary() ?.roomSummary()
// don't take if not active, as it could be outdated // don't take if not Join, as it could be outdated
?.takeIf { it.membership.isActive() } ?.takeIf { it.membership == Membership.JOIN }
// XXX fix that if (knownRoom != null) {
val forceRefresh = true
if (!forceRefresh && knownRoom != null) {
setState { setState {
copy( copy(
roomPeekResult = Success( roomPeekResult = Success(
@ -134,7 +132,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
alias = knownRoom.canonicalAlias, alias = knownRoom.canonicalAlias,
membership = knownRoom.membership, membership = knownRoom.membership,
roomType = knownRoom.roomType, roomType = knownRoom.roomType,
viaServers = null viaServers = null,
isPublic = knownRoom.isPublic
) )
) )
) )
@ -150,7 +149,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
alias = peekResult.alias, alias = peekResult.alias,
membership = knownRoom?.membership ?: Membership.NONE, membership = knownRoom?.membership ?: Membership.NONE,
roomType = peekResult.roomType, roomType = peekResult.roomType,
viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters,
isPublic = peekResult.isPublic
).also { ).also {
peekResult.someMembers?.let { checkForKnownMembers(it) } 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 import javax.inject.Inject
class MatrixToRoomSpaceFragment @Inject constructor( class MatrixToRoomSpaceFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer,
private val spaceCardRenderer: SpaceCardRenderer
) : VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() { ) : VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() {
private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel() private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel()
@ -78,12 +79,19 @@ class MatrixToRoomSpaceFragment @Inject constructor(
when (val peek = item.invoke()) { when (val peek = item.invoke()) {
is RoomInfoResult.FullInfo -> { is RoomInfoResult.FullInfo -> {
val matrixItem = peek.roomItem val matrixItem = peek.roomItem
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
if (peek.roomType == RoomType.SPACE) { if (peek.roomType == RoomType.SPACE) {
views.matrixToBetaTag.isVisible = true 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 { } else {
views.matrixToBetaTag.isVisible = false views.matrixToBetaTag.isVisible = false
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
} }
views.matrixToCardNameText.setTextOrHide(peek.name) views.matrixToCardNameText.setTextOrHide(peek.name)
views.matrixToCardAliasText.setTextOrHide(peek.alias) 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 } .onEach { it.isGone = true }
when (state.peopleYouKnow) { when (state.peopleYouKnow) {
is Success -> { is Success -> {
val someYouKnow = state.peopleYouKnow.invoke() val someYouKnow = state.peopleYouKnow.invoke()
if (someYouKnow.isEmpty()) { spaceCardRenderer.renderPeopleYouKnow(views, someYouKnow)
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()
)
)
}
} }
else -> { else -> {
views.peopleYouMayKnowText.isVisible = false 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 contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = when (mode) { val resolvedUrl = when (mode) {
Mode.FULL_SIZE, Mode.FULL_SIZE,
Mode.STICKER -> resolveUrl(data) Mode.STICKER -> resolveUrl(data)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
} }
// Fallback to base url // Fallback to base url
@ -313,7 +313,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
finalHeight = min(maxImageWidth * height / width, maxImageHeight) finalHeight = min(maxImageWidth * height / width, maxImageHeight)
finalWidth = finalHeight * width / height finalWidth = finalHeight * width / height
} }
Mode.STICKER -> { Mode.STICKER -> {
// limit on width // limit on width
val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2) val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2)
finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp) finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp)

View File

@ -68,16 +68,14 @@ class RoomSettingsController @Inject constructor(
id("avatar") id("avatar")
enabled(data.actionPermissions.canChangeAvatar) enabled(data.actionPermissions.canChangeAvatar)
when (val avatarAction = data.avatarAction) { when (val avatarAction = data.avatarAction) {
RoomSettingsViewState.AvatarAction.None -> { RoomSettingsViewState.AvatarAction.None -> {
// Use the current value // Use the current value
avatarRenderer(host.avatarRenderer) 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. // 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 -> RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null)
imageUri(null) is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri)
is RoomSettingsViewState.AvatarAction.UpdateAvatar ->
imageUri(avatarAction.newAvatarUri)
} }
clickListener { host.callback?.onAvatarChange() } clickListener { host.callback?.onAvatarChange() }
deleteListener { host.callback?.onAvatarDelete() } deleteListener { host.callback?.onAvatarDelete() }

View File

@ -96,7 +96,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
val roomSummary = session.getRoomSummary(spaceArgs.spaceId) val roomSummary = session.getRoomSummary(spaceArgs.spaceId)
roomSummary?.toMatrixItem()?.let { roomSummary?.toMatrixItem()?.let {
avatarRenderer.renderSpace(it, views.spaceAvatarImageView) avatarRenderer.render(it, views.spaceAvatarImageView)
} }
views.spaceNameView.text = roomSummary?.displayName views.spaceNameView.text = roomSummary?.displayName
views.spaceDescription.setTextOrHide(roomSummary?.topic?.takeIf { it.isNotEmpty() }) 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.indentSpace.isVisible = indent > 0
holder.separator.isVisible = showSeparator holder.separator.isVisible = showSeparator
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.counterBadgeView.render(countState) holder.counterBadgeView.render(countState)
} }

View File

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

View File

@ -54,7 +54,7 @@ class SpaceDetailEpoxyController @Inject constructor(
enabled(true) enabled(true)
imageUri(data?.avatarUri) imageUri(data?.avatarUri)
avatarRenderer(host.avatarRenderer) 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() } clickListener { host.listener?.onAvatarChange() }
deleteListener { host.listener?.onAvatarDelete() } deleteListener { host.listener?.onAvatarDelete() }
} }

View File

@ -122,13 +122,15 @@ class SpaceDirectoryController @Inject constructor(
val isSpace = info.roomType == RoomType.SPACE val isSpace = info.roomType == RoomType.SPACE
val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true
val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false 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 { spaceChildInfoItem {
id(info.childRoomId) id(info.childRoomId)
matrixItem(info.toMatrixItem()) matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer) avatarRenderer(host.avatarRenderer)
topic(info.topic) topic(info.topic)
memberCount(info.activeMemberCount ?: 0) memberCount(info.activeMemberCount ?: 0)
space(isSpace)
loading(isLoading) loading(isLoading)
buttonLabel( buttonLabel(
if (isJoined) host.stringProvider.getString(R.string.action_open) if (isJoined) host.stringProvider.getString(R.string.action_open)

View File

@ -16,6 +16,7 @@
package im.vector.app.features.spaces.explore package im.vector.app.features.spaces.explore
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
@ -23,19 +24,33 @@ import android.view.Menu
import android.view.MenuItem 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.core.text.toSpannable
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R 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.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
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.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.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.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity import im.vector.app.features.spaces.manage.SpaceManageActivity
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import java.net.URL
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -44,9 +59,13 @@ data class SpaceDirectoryArgs(
) : Parcelable ) : Parcelable
class SpaceDirectoryFragment @Inject constructor( 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>(), ) : VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(),
SpaceDirectoryController.InteractionListener, SpaceDirectoryController.InteractionListener,
TimelineEventController.UrlClickCallback,
OnBackPressed { OnBackPressed {
override fun getMenuRes() = R.menu.menu_space_directory override fun getMenuRes() = R.menu.menu_space_directory
@ -71,6 +90,9 @@ class SpaceDirectoryFragment @Inject constructor(
viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) { viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) {
invalidateOptionsMenu() invalidateOptionsMenu()
} }
views.spaceCard.matrixToCardMainButton.isVisible = false
views.spaceCard.matrixToCardSecondaryButton.isVisible = false
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -82,10 +104,21 @@ class SpaceDirectoryFragment @Inject constructor(
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
epoxyController.setData(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 } 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 -> override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->
@ -96,7 +129,7 @@ class SpaceDirectoryFragment @Inject constructor(
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.spaceAddRoom -> { R.id.spaceAddRoom -> {
withState(viewModel) { state -> withState(viewModel) { state ->
addExistingRooms(state.spaceId) addExistingRooms(state.spaceId)
} }
@ -138,6 +171,44 @@ class SpaceDirectoryFragment @Inject constructor(
override fun addExistingRooms(spaceId: String) { override fun addExistingRooms(spaceId: String) {
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms)) 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) { // override fun navigateToRoom(roomId: String) {
// viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId)) // viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId))
// } // }

View File

@ -37,7 +37,9 @@ data class SpaceDirectoryState(
val joinedRoomsIds: Set<String> = emptySet(), val joinedRoomsIds: Set<String> = emptySet(),
// keys are room alias or roomId // keys are room alias or roomId
val changeMembershipStates: Map<String, ChangeMembershipState> = emptyMap(), 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 { ) : MvRxState {
constructor(args: SpaceDirectoryArgs) : this( constructor(args: SpaceDirectoryArgs) : this(
spaceId = args.spaceId spaceId = args.spaceId

View File

@ -66,7 +66,8 @@ 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(),
spaceSummary = spaceSum?.let { Success(spaceSum) } ?: Loading()
) )
} }
@ -101,9 +102,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val query = session.spaceService().querySpaceChildren(initialState.spaceId) 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 { setState {
copy( copy(
spaceSummaryApiResult = Success(query.second) spaceSummaryApiResult = Success(query.second),
knownRoomSummaries = knownSummaries
) )
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
@ -148,7 +154,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId)) copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId))
} }
} }
SpaceDirectoryViewAction.HandleBack -> { SpaceDirectoryViewAction.HandleBack -> {
withState { withState {
if (it.hierarchyStack.isEmpty()) { if (it.hierarchyStack.isEmpty()) {
_viewEvents.post(SpaceDirectoryViewEvents.Dismiss) _viewEvents.post(SpaceDirectoryViewEvents.Dismiss)
@ -161,20 +167,20 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
} }
} }
} }
is SpaceDirectoryViewAction.JoinOrOpen -> { is SpaceDirectoryViewAction.JoinOrOpen -> {
handleJoinOrOpen(action.spaceChildInfo) handleJoinOrOpen(action.spaceChildInfo)
} }
is SpaceDirectoryViewAction.NavigateToRoom -> { is SpaceDirectoryViewAction.NavigateToRoom -> {
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId)) _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 // 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. // It's not ideal as it's doing some peeking that is not needed.
session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let { session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let {
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it))
} }
} }
SpaceDirectoryViewAction.Retry -> { SpaceDirectoryViewAction.Retry -> {
refreshFromApi() refreshFromApi()
} }
} }

View File

@ -22,7 +22,6 @@ import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
@ -33,12 +32,12 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ScreenComponent 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.ButtonStateView
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.matrixto.SpaceCardRenderer
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -60,6 +59,9 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
@Inject @Inject
lateinit var avatarRenderer: AvatarRenderer lateinit var avatarRenderer: AvatarRenderer
@Inject
lateinit var spaceCardRenderer: SpaceCardRenderer
private val viewModel: SpaceInviteBottomSheetViewModel by fragmentViewModel(SpaceInviteBottomSheetViewModel::class) private val viewModel: SpaceInviteBottomSheetViewModel by fragmentViewModel(SpaceInviteBottomSheetViewModel::class)
@Inject lateinit var viewModelFactory: SpaceInviteBottomSheetViewModel.Factory @Inject lateinit var viewModelFactory: SpaceInviteBottomSheetViewModel.Factory
@ -133,12 +135,7 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
views.inviterMxid.isVisible = false views.inviterMxid.isVisible = false
} }
views.spaceCard.matrixToCardContentVisibility.isVisible = true spaceCardRenderer.render(summary, state.peopleYouKnow.invoke().orEmpty(), null, views.spaceCard)
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)
views.spaceCard.matrixToCardMainButton.button.text = getString(R.string.accept) views.spaceCard.matrixToCardMainButton.button.text = getString(R.string.accept)
views.spaceCard.matrixToCardSecondaryButton.button.text = getString(R.string.decline) views.spaceCard.matrixToCardSecondaryButton.button.text = getString(R.string.decline)
@ -178,40 +175,6 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetIn
views.spaceCard.matrixToCardSecondaryButton.button.isEnabled = true 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 { 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 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.ResultBoundaries
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.RoomType
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -155,7 +154,6 @@ class AddRoomListController @Inject constructor(
id(item.roomId) id(item.roomId)
matrixItem(item.toMatrixItem()) matrixItem(item.toMatrixItem())
avatarRenderer(host.avatarRenderer) avatarRenderer(host.avatarRenderer)
space(item.roomType == RoomType.SPACE)
selected(host.selectedItems[item.roomId] ?: false) selected(host.selectedItems[item.roomId] ?: false)
itemClickListener(DebouncedClickListener({ itemClickListener(DebouncedClickListener({
host.listener?.onItemSelected(item) host.listener?.onItemSelected(item)

View File

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

View File

@ -33,17 +33,13 @@ abstract class RoomSelectionItem : VectorEpoxyModel<RoomSelectionItem.Holder>()
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var space: Boolean = false
@EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
if (space) { avatarRenderer.render(matrixItem, holder.avatarImageView)
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
} else {
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
holder.titleText.text = matrixItem.getBestName() holder.titleText.text = matrixItem.getBestName()
if (selected) { 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.ui.list.genericFooterItem
import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer 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.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -83,7 +82,6 @@ class SpaceManageRoomsController @Inject constructor(
matrixItem(childInfo.toMatrixItem()) matrixItem(childInfo.toMatrixItem())
avatarRenderer(host.avatarRenderer) avatarRenderer(host.avatarRenderer)
suggested(childInfo.suggested ?: false) suggested(childInfo.suggested ?: false)
space(childInfo.roomType == RoomType.SPACE)
selected(data.selectedRooms.contains(childInfo.childRoomId)) selected(data.selectedRooms.contains(childInfo.childRoomId))
itemClickListener(DebouncedClickListener({ itemClickListener(DebouncedClickListener({
host.listener?.toggleSelection(childInfo) host.listener?.toggleSelection(childInfo)

View File

@ -71,7 +71,7 @@ class SpaceSettingsController @Inject constructor(
// Use the current value // Use the current value
avatarRenderer(host.avatarRenderer) 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. // 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 -> RoomSettingsViewState.AvatarAction.DeleteAvatar ->
imageUri(null) imageUri(null)

View File

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

View File

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

View File

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

View File

@ -58,7 +58,7 @@
android:id="@+id/matrixToCardAliasText" android:id="@+id/matrixToCardAliasText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="8dp"
android:maxLines="1" android:maxLines="1"
android:singleLine="true" android:singleLine="true"
android:textAlignment="textStart" android:textAlignment="textStart"
@ -70,12 +70,43 @@
tools:text="@sample/rooms.json/data/alias" tools:text="@sample/rooms.json/data/alias"
tools:visibility="visible" /> 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 <LinearLayout
android:id="@+id/matrixToMemberPills" android:id="@+id/matrixToMemberPills"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="@drawable/pill_receipt" android:background="@drawable/pill_receipt"
android:backgroundTint="?riotx_reaction_background_off"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="12dp" android:paddingStart="12dp"
@ -85,7 +116,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/matrixToCardAliasText"> app:layout_constraintTop_toBottomOf="@id/matrixToAccessText">
<ImageView <ImageView
android:id="@+id/spaceChildMemberCountIcon" android:id="@+id/spaceChildMemberCountIcon"
@ -113,6 +144,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:autoLink="web"
android:maxLines="4" android:maxLines="4"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?riotx_text_secondary" android:textColor="?riotx_text_secondary"

View File

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

View File

@ -3267,6 +3267,7 @@
<string name="a11y_rule_notify_off">Do not notify</string> <string name="a11y_rule_notify_off">Do not notify</string>
<string name="a11y_view_read_receipts">View read receipts</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_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_menu_name">Dev Tools</string>
<string name="dev_tools_explore_room_state">Explore Room State</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_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="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="add_space">Add Space</string>
<string name="your_public_space">Your public space</string> <string name="your_public_space">Your public space</string>
<string name="your_private_space">Your private 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_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="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> </resources>