EmptyRoom tile with quick actions

This commit is contained in:
Valere 2020-11-10 09:43:54 +01:00 committed by Benoit Marty
parent ca0da2c509
commit 2626a761ea
18 changed files with 468 additions and 71 deletions

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.permalinks
import android.text.Spannable
import org.matrix.android.sdk.api.MatrixPatterns
/**
* MatrixLinkify take a piece of text and turns all of the
@ -35,7 +36,7 @@ object MatrixLinkify {
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
*/
/*
// sanity checks
if (spannable.isEmpty()) {
return false
@ -48,14 +49,21 @@ object MatrixLinkify {
val startPos = match.range.first
if (startPos == 0 || text[startPos - 1] != '/') {
val endPos = match.range.last + 1
val url = text.substring(match.range)
var url = text.substring(match.range)
if (MatrixPatterns.isUserId(url)
|| MatrixPatterns.isRoomAlias(url)
|| MatrixPatterns.isRoomId(url)
|| MatrixPatterns.isGroupId(url)
|| MatrixPatterns.isEventId(url)) {
url = PermalinkService.MATRIX_TO_URL_BASE + url
}
val span = MatrixPermalinkSpan(url, callback)
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
return hasMatch
*/
return false
// return false
}
}

View file

@ -46,11 +46,13 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
val roomMembers = RoomMemberHelper(realm, roomId)
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (members.size == 1) {
res = members.firstOrNull()?.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
res = firstOtherMember?.avatarUrl
if (roomMembers.isDirectRoom()) {
if (members.size == 1) {
res = members.firstOrNull()?.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
res = firstOtherMember?.avatarUrl
}
}
return res
}

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import io.realm.Realm
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
@ -50,9 +51,14 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
} else if (!params.searchOnServer) {
Optional.from<String>(null)
} else {
roomId = executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
}.roomId
roomId = try {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
}.roomId
} catch (throwable: Throwable) {
Timber.d(throwable, "## Failed to get roomId from alias")
null
}
Optional.from(roomId)
}
}

View file

@ -93,6 +93,9 @@ internal class RoomDisplayNameResolver @Inject constructor(
}
} else if (roomEntity?.membership == Membership.JOIN) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val invitedCount = roomSummary?.invitedMembersCount ?: 0
val joinedCount = roomSummary?.joinedMembersCount ?: 0
val othersTotalCount = invitedCount + joinedCount - 1
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes.mapNotNull { userId ->
roomMembers.getLastRoomMember(userId)?.takeIf {
@ -102,22 +105,29 @@ internal class RoomDisplayNameResolver @Inject constructor(
} else {
activeMembers.where()
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
.limit(3)
.limit(5)
.findAll()
.createSnapshot()
}
val otherMembersCount = otherMembersSubset.count()
name = when (otherMembersCount) {
0 -> stringProvider.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
2 -> stringProvider.getString(R.string.room_displayname_two_members,
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
)
else -> stringProvider.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1,
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
roomMembers.getNumberOfJoinedMembers() - 1)
0 -> {
stringProvider.getString(R.string.room_displayname_empty_room)
// TODO (was xx and yyy) ...
}
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
else -> {
val names = otherMembersSubset.map {
resolveRoomMemberName(it, roomMembers) ?: ""
}
if (otherMembersCount <= othersTotalCount) {
val remainingCount = invitedCount + joinedCount - names.size
(names.joinToString("${stringProvider.getString(R.string.room_displayname_separator)} ")
+ " " + stringProvider.getQuantityString(R.plurals.and_n_others, remainingCount, remainingCount))
} else {
names.dropLast(1).joinToString(", ") + " & ${names.last()}"
}
}
}
}
return name ?: roomId

View file

@ -98,6 +98,10 @@ internal class RoomMemberHelper(private val realm: Realm,
return getNumberOfJoinedMembers() + getNumberOfInvitedMembers()
}
fun isDirectRoom() : Boolean {
return roomSummary?.isDirect ?: false
}
/**
* Return all the roomMembers ids which are joined or invited to the room
*

View file

@ -180,8 +180,14 @@
<item quantity="one">%1$s and 1 other</item>
<item quantity="other">%1$s and %2$d others</item>
</plurals>
<plurals name="and_n_others">
<item quantity="one">&amp; %d other</item>
<item quantity="other">&amp; %d others</item>
</plurals>
<string name="room_displayname_separator">,</string>
<string name="room_displayname_empty_room">Empty room</string>
<string name="room_displayname_empty_room_was">Empty room (was %s)</string>
<string name="initial_sync_start_importing_account">Initial Sync:\nImporting account…</string>
<string name="initial_sync_start_importing_account_crypto">Initial Sync:\nImporting crypto</string>

View file

@ -16,6 +16,8 @@
package im.vector.app.features.home.room.detail
import android.net.Uri
import android.view.View
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.Event
@ -24,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.util.MatrixItem
sealed class RoomDetailAction : VectorViewModelAction {
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
@ -90,4 +93,9 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class OpenOrCreateDm(val userId: String) : RoomDetailAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailAction()
object QuickActionInvitePeople : RoomDetailAction()
object QuickActionSetAvatar : RoomDetailAction()
data class SetAvatarAction(val newAvatarUri: Uri, val newAvatarFileName: String) : RoomDetailAction()
object QuickActionSetTopic : RoomDetailAction()
data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val transitionView: View?) : RoomDetailAction()
}

View file

@ -71,6 +71,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.dialogs.withColoredButton
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.cleanup
@ -82,6 +83,7 @@ import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.trackItemsVisibilityChange
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.intent.getFilenameFromUri
import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
@ -149,6 +151,7 @@ import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.reactions.EmojiReactionPickerActivity
import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData
@ -196,6 +199,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import timber.log.Timber
import java.io.File
import java.net.URL
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -229,7 +233,7 @@ class RoomDetailFragment @Inject constructor(
JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback,
AttachmentsHelper.Callback,
// RoomWidgetsBannerView.Callback,
GalleryOrCameraDialogHelper.Listener,
ActiveCallView.Callback {
companion object {
@ -250,6 +254,8 @@ class RoomDetailFragment @Inject constructor(
private const val ircPattern = " (IRC)"
}
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
private val roomDetailArgs: RoomDetailArgs by args()
private val glideRequests by lazy {
GlideApp.with(this)
@ -364,6 +370,12 @@ class RoomDetailFragment @Inject constructor(
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId)
RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show()
RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings()
is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item ->
navigator.openBigImageViewer(requireActivity(), it.view, item)
}
}.exhaustive
}
@ -372,6 +384,24 @@ class RoomDetailFragment @Inject constructor(
}
}
override fun onImageReady(uri: Uri?) {
uri ?: return
roomDetailViewModel.handle(
RoomDetailAction.SetAvatarAction(
newAvatarUri = uri,
newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()
)
)
}
private fun handleOpenRoomSettings() {
navigator.openRoomProfile(
requireContext(),
roomDetailArgs.roomId,
RoomProfileActivity.EXTRA_DIRECT_ACCESS_ROOM_SETTINGS
)
}
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
navigator.openRoom(requireContext(), openRoom.roomId, null)
}

View file

@ -17,10 +17,12 @@
package im.vector.app.features.home.room.detail
import android.net.Uri
import android.view.View
import androidx.annotation.StringRes
import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.features.command.Command
import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import java.io.File
@ -43,6 +45,11 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
object OpenInvitePeople : RoomDetailViewEvents()
object OpenSetRoomAvatarDialog : RoomDetailViewEvents()
object OpenRoomSettings : RoomDetailViewEvents()
data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val view: View?) : RoomDetailViewEvents()
object ShowWaitingView : RoomDetailViewEvents()
object HideWaitingView : RoomDetailViewEvents()

View file

@ -277,9 +277,39 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.CancelSend -> handleCancel(action)
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
_viewEvents.post(
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
)
}
}.exhaustive
}
private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
viewModelScope.launch(Dispatchers.IO) {
try {
awaitCallback<Unit> {
room.updateAvatar(action.newAvatarUri, action.newAvatarFileName, it)
}
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
} catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
}
}
}
private fun handleInvitePeople() {
_viewEvents.post(RoomDetailViewEvents.OpenInvitePeople)
}
private fun handleQuickSetAvatar() {
_viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog)
}
private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) {
val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId)
if (existingDmRoomId == null) {

View file

@ -31,10 +31,14 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEve
import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
@ -187,6 +191,11 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
val powerLevelsHelper = roomSummaryHolder.roomSummary?.roomId
?.let { activeSessionHolder.getSafeActiveSession()?.getRoom(it) }
?.let { it.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)?.content?.toModel<PowerLevelsContent>() }
?.let { PowerLevelsHelper(it) }
val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: ""
val attributes = MergedRoomCreationItem.Attributes(
isCollapsed = isCollapsed,
mergeData = mergedData,
@ -198,13 +207,19 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
hasEncryptionEvent = hasEncryption,
isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM,
readReceiptsCallback = callback,
currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: ""
callback = callback,
currentUserId = currentUserId,
roomSummary = roomSummaryHolder.roomSummary,
canChangeAvatar = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false,
canChangeTopic = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false,
canChangeName = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false
)
MergedRoomCreationItem_()
.id(mergeId)
.leftGuideline(avatarSizeProvider.leftGuideline)
.highlighted(isCollapsed && highlighted)
.attributes(attributes)
.movementMethod(createLinkMovementMethod(callback))
.also {
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
}

View file

@ -16,11 +16,14 @@
package im.vector.app.features.home.room.detail.timeline.item
import android.text.SpannableString
import android.text.method.MovementMethod
import android.text.style.ClickableSpan
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
@ -28,8 +31,16 @@ import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.core.utils.tappableMatchingText
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailAction
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.tools.linkify
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.Holder>() {
@ -37,11 +48,16 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
@EpoxyAttribute
override lateinit var attributes: Attributes
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var movementMethod: MovementMethod? = null
override fun getViewType() = STUB_ID
override fun bind(holder: Holder) {
super.bind(holder)
bindCreationSummaryTile(holder)
if (attributes.isCollapsed) {
// Take the oldest data
val data = distinctMergeData.lastOrNull()
@ -70,34 +86,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
holder.avatarView.visibility = View.GONE
}
if (attributes.hasEncryptionEvent) {
holder.encryptionTile.isVisible = true
holder.encryptionTile.updateLayoutParams<RelativeLayout.LayoutParams> {
this.marginEnd = leftGuideline
}
if (attributes.isEncryptionAlgorithmSecure) {
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled)
holder.e2eTitleDescriptionView.text = if (data?.isDirectRoom == true) {
holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description)
} else {
holder.expandView.resources.getString(R.string.encryption_enabled_tile_description)
}
holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black),
null, null, null
)
} else {
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled)
holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description)
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning),
null, null, null
)
}
} else {
holder.encryptionTile.isVisible = false
}
bindEncryptionTile(holder, data)
} else {
holder.avatarView.visibility = View.INVISIBLE
holder.summaryView.visibility = View.GONE
@ -107,6 +96,107 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
holder.readReceiptsView.isVisible = false
}
private fun bindEncryptionTile(holder: Holder, data: Data?) {
if (attributes.hasEncryptionEvent) {
holder.encryptionTile.isVisible = true
holder.encryptionTile.updateLayoutParams<ConstraintLayout.LayoutParams> {
this.marginEnd = leftGuideline
}
if (attributes.isEncryptionAlgorithmSecure) {
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled)
holder.e2eTitleDescriptionView.text = if (data?.isDirectRoom == true) {
holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description)
} else {
holder.expandView.resources.getString(R.string.encryption_enabled_tile_description)
}
holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black),
null, null, null
)
} else {
holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled)
holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description)
holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning),
null, null, null
)
}
} else {
holder.encryptionTile.isVisible = false
}
}
private fun bindCreationSummaryTile(holder: Holder) {
val roomSummary = attributes.roomSummary
val roomDisplayName = roomSummary?.displayName
holder.roomNameText.setTextOrHide(roomDisplayName)
val isDirect = roomSummary?.isDirect == true
val membersCount = roomSummary?.otherMemberIds?.size ?: 0
if (isDirect) {
holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_dm, roomSummary?.displayName ?: "")
} else if (roomDisplayName.isNullOrBlank() || roomSummary.name.isBlank()) {
holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name)
} else {
holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName)
}
val topic = roomSummary?.topic
if (topic.isNullOrBlank()) {
// do not show hint for DMs or group DMs
if (!isDirect) {
val addTopicLink = holder.view.resources.getString(R.string.add_a_topic_link_text)
val styledText = SpannableString(holder.view.resources.getString(R.string.room_created_summary_no_topic_creation_text, addTopicLink))
holder.roomTopicText.setTextOrHide(styledText.tappableMatchingText(addTopicLink, object : ClickableSpan() {
override fun onClick(widget: View) {
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetTopic)
}
}))
}
} else {
holder.roomTopicText.setTextOrHide(span {
span(holder.view.resources.getString(R.string.topic_prefix)) {
textStyle = "bold"
}
+topic.linkify(attributes.callback)
}
)
}
holder.roomTopicText.movementMethod = movementMethod
val roomItem = roomSummary?.toMatrixItem()
if (roomItem != null) {
holder.roomAvatarImageView.isVisible = true
attributes.avatarRenderer.render(roomItem, holder.roomAvatarImageView)
holder.roomAvatarImageView.setOnClickListener(DebouncedClickListener({ view ->
attributes.callback?.onTimelineItemAction(RoomDetailAction.ShowRoomAvatarFullScreen(roomItem, view))
}))
} else {
holder.roomAvatarImageView.isVisible = false
}
if (isDirect) {
holder.addPeopleButton.isVisible = false
} else {
holder.addPeopleButton.isVisible = true
holder.addPeopleButton.setOnClickListener(DebouncedClickListener({ _ ->
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionInvitePeople)
}))
}
val shouldShowSetAvatar = attributes.canChangeAvatar
&& (roomSummary?.isDirect == false || (isDirect && membersCount >= 2))
if (shouldShowSetAvatar && roomItem?.avatarUrl.isNullOrBlank()) {
holder.setAvatarButton.isVisible = true
holder.setAvatarButton.setOnClickListener(DebouncedClickListener({ _ ->
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar)
}))
} else {
holder.setAvatarButton.isVisible = false
}
}
class Holder : BasedMergedItem.Holder(STUB_ID) {
val summaryView by bind<TextView>(R.id.itemNoticeTextView)
val avatarView by bind<ImageView>(R.id.itemNoticeAvatarView)
@ -114,6 +204,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
val e2eTitleDescriptionView by bind<TextView>(R.id.itemVerificationDoneDetailTextView)
val roomNameText by bind<TextView>(R.id.roomNameTileText)
val roomDescriptionText by bind<TextView>(R.id.roomNameDescriptionText)
val roomTopicText by bind<TextView>(R.id.roomNameTopicText)
val roomAvatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
val addPeopleButton by bind<View>(R.id.creationTileAddPeopleButton)
val setAvatarButton by bind<View>(R.id.creationTileSetAvatarButton)
}
companion object {
@ -126,8 +223,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
override val avatarRenderer: AvatarRenderer,
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
override val onCollapsedStateChanged: (Boolean) -> Unit,
val callback: TimelineEventController.Callback? = null,
val currentUserId: String,
val hasEncryptionEvent: Boolean,
val isEncryptionAlgorithmSecure: Boolean
val isEncryptionAlgorithmSecure: Boolean,
val roomSummary: RoomSummary?,
val canChangeAvatar: Boolean = false,
val canChangeName: Boolean = false,
val canChangeTopic: Boolean = false
) : BasedMergedItem.Attributes
}

View file

@ -248,8 +248,8 @@ class DefaultNavigator @Inject constructor(
context.startActivity(KeysBackupManageActivity.intent(context))
}
override fun openRoomProfile(context: Context, roomId: String) {
context.startActivity(RoomProfileActivity.newIntent(context, roomId))
override fun openRoomProfile(context: Context, roomId: String, directAccess: Int?) {
context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess))
}
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {

View file

@ -78,7 +78,7 @@ interface Navigator {
fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)
fun openRoomProfile(context: Context, roomId: String)
fun openRoomProfile(context: Context, roomId: String, directAccess: Int? = null)
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)

View file

@ -46,10 +46,16 @@ class RoomProfileActivity :
companion object {
fun newIntent(context: Context, roomId: String): Intent {
private const val EXTRA_DIRECT_ACCESS = "EXTRA_DIRECT_ACCESS"
const val EXTRA_DIRECT_ACCESS_ROOM_ROOT = 0
const val EXTRA_DIRECT_ACCESS_ROOM_SETTINGS = 1
fun newIntent(context: Context, roomId: String, directAccess: Int?): Intent {
val roomProfileArgs = RoomProfileArgs(roomId)
return Intent(context, RoomProfileActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, roomProfileArgs)
putExtra(EXTRA_DIRECT_ACCESS, directAccess)
}
}
}
@ -80,7 +86,13 @@ class RoomProfileActivity :
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
if (isFirstCreation()) {
addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
when (intent?.extras?.getInt(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOM_ROOT)) {
EXTRA_DIRECT_ACCESS_ROOM_SETTINGS -> {
addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
}
else -> addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
}
}
sharedActionViewModel
.observe()

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19.1001,9C18.7779,9 18.5168,8.7388 18.5168,8.4167V6.0833H16.1834C15.8613,6.0833 15.6001,5.8222 15.6001,5.5C15.6001,5.1778 15.8613,4.9167 16.1834,4.9167H18.5168V2.5833C18.5168,2.2612 18.7779,2 19.1001,2C19.4223,2 19.6834,2.2612 19.6834,2.5833V4.9167H22.0168C22.3389,4.9167 22.6001,5.1778 22.6001,5.5C22.6001,5.8222 22.3389,6.0833 22.0168,6.0833H19.6834V8.4167C19.6834,8.7388 19.4223,9 19.1001,9ZM19.6001,11C20.0669,11 20.5212,10.9467 20.9574,10.8458C21.1161,11.5383 21.2,12.2594 21.2,13C21.2,16.1409 19.6917,18.9294 17.3598,20.6808V20.6807C16.0014,21.7011 14.3635,22.3695 12.5815,22.5505C12.2588,22.5832 11.9314,22.6 11.6,22.6C6.2981,22.6 2,18.302 2,13C2,7.6981 6.2981,3.4 11.6,3.4C12.3407,3.4 13.0618,3.4839 13.7543,3.6427C13.6534,4.0788 13.6001,4.5332 13.6001,5C13.6001,8.3137 16.2864,11 19.6001,11ZM11.5999,20.68C13.6754,20.68 15.5585,19.8567 16.9407,18.5189C16.0859,16.4086 14.0167,14.92 11.5998,14.92C9.183,14.92 7.1138,16.4086 6.259,18.5189C7.6411,19.8567 9.5244,20.68 11.5999,20.68ZM11.7426,7.4117C10.3168,7.5417 9.2,8.7404 9.2,10.2C9.2,11.7464 10.4536,13 12,13C13.0308,13 13.9315,12.443 14.4176,11.6135C13.0673,10.6058 12.0929,9.1225 11.7426,7.4117Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View file

@ -1,31 +1,168 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/creationEncryptionTile"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/creationTile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_gravity="center"
android:layout_marginTop="2dp"
android:layout_marginEnd="52dp"
android:layout_marginBottom="2dp"
android:background="@drawable/rounded_rect_shape_8"
android:padding="8dp">
android:layout_marginTop="0dp">
<include layout="@layout/item_timeline_event_status_tile_stub" />
<FrameLayout
android:id="@+id/creationEncryptionTile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="52dp"
android:layout_marginBottom="2dp"
android:background="@drawable/rounded_rect_shape_8"
android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</FrameLayout>
<include layout="@layout/item_timeline_event_status_tile_stub" />
</FrameLayout>
<ImageView
android:id="@+id/roomAvatarImageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/creationEncryptionTile"
tools:srcCompat="@tools:sample/avatars" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.Button.Icon"
android:id="@+id/creationTileSetAvatarButton"
android:layout_width="30dp"
android:layout_height="30dp"
android:backgroundTint="?riotx_bottom_nav_background_color"
android:contentDescription="@string/room_settings_set_avatar"
android:elevation="2dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:padding="0dp"
app:cornerRadius="30dp"
app:icon="@drawable/ic_camera"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="20dp"
app:iconTint="?riot_primary_text_color"
app:layout_constraintCircle="@+id/roomAvatarImageView"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="34dp"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/roomNameTileText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomAvatarImageView"
tools:text="Room Name" />
<TextView
android:id="@+id/roomNameDescriptionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomNameTileText"
tools:text="This is the beginning of Room name. " />
<TextView
android:id="@+id/roomNameTopicText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textColor="?riotx_text_secondary"
android:textColorLink="@color/riotx_accent"
android:textSize="15sp"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomNameDescriptionText"
tools:text="Add a topic to let people know what this room is about. " />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/creationTileAddPeopleButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/add_people"
android:focusable="true"
android:maxWidth="90dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomNameTopicText">
<View
android:id="@+id/addPeopleButtonBg"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/circle"
android:backgroundTint="@color/riotx_accent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/addPeopleButtonIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/circle"
android:backgroundTint="@color/riotx_accent"
android:src="@drawable/ic_add_people"
app:layout_constraintBottom_toBottomOf="@id/addPeopleButtonBg"
app:layout_constraintEnd_toEndOf="@id/addPeopleButtonBg"
app:layout_constraintStart_toStartOf="@id/addPeopleButtonBg"
app:layout_constraintTop_toTopOf="@id/addPeopleButtonBg"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/add_people"
android:textColor="@color/riotx_accent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/addPeopleButtonBg" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
android:id="@+id/mergedSumContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/creationEncryptionTile">
android:layout_below="@+id/creationTile"
android:layout_marginTop="8dp">
<ImageView
android:id="@+id/itemNoticeAvatarView"

View file

@ -2403,6 +2403,13 @@
<string name="room_created_summary_item_by_you">You created and configured the room.</string>
<string name="direct_room_created_summary_item">%s joined.</string>
<string name="direct_room_created_summary_item_by_you">You joined.</string>
<string name="this_is_the_beginning_of_room">This is the beginning of %s.</string>
<string name="this_is_the_beginning_of_room_no_name">This is the beginning of this conversation.</string>
<string name="this_is_the_beginning_of_dm">This is the beginning of your direct message history with %s.</string>
<!-- First param will be replaced by the value of add_a_topic_link_text, that will be clickable-->
<string name="room_created_summary_no_topic_creation_text">%s to let people know what this room is about. </string>
<string name="add_a_topic_link_text">Add a topic</string>
<string name="topic_prefix">"Topic: "</string>
<string name="qr_code_scanned_self_verif_notice">Almost there! Is the other device showing the same shield?</string>
<string name="qr_code_scanned_verif_waiting_notice">Almost there! Waiting for confirmation…</string>
@ -2511,6 +2518,7 @@
<string name="create_room_dm_failure">"We couldn't create your DM. Please check the users you want to invite and try again."</string>
<string name="add_members_to_room">Add members</string>
<string name="add_people">Add people</string>
<string name="invite_users_to_room_action_invite">INVITE</string>
<string name="inviting_users_to_room">Inviting users…</string>
<string name="invite_users_to_room_title">Invite Users</string>
@ -2575,6 +2583,8 @@
<string name="room_settings_name_hint">Room Name</string>
<string name="room_settings_topic_hint">Topic</string>
<string name="room_settings_save_success">You changed room settings successfully</string>
<string name="room_settings_set_avatar">Set avatar</string>
<string name="notice_crypto_unable_to_decrypt_final">You cannot access this message</string>
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>