Merge pull request #2369 from vector-im/feature/bca/quick_invite_room_tile
EmptyRoom tile with quick actions
This commit is contained in:
commit
4df68479ac
|
@ -5,6 +5,7 @@ Features ✨:
|
||||||
-
|
-
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
|
- New room creation tile with quick action (#2346)
|
||||||
- Open an existing DM instead of creating a new one (#2319)
|
- Open an existing DM instead of creating a new one (#2319)
|
||||||
- Ask for explicit user consent to send their contact details to the identity server (#2375)
|
- Ask for explicit user consent to send their contact details to the identity server (#2375)
|
||||||
- Handle events of type "m.room.server_acl" (#890)
|
- Handle events of type "m.room.server_acl" (#890)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.session.permalinks
|
package org.matrix.android.sdk.api.session.permalinks
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatrixLinkify take a piece of text and turns all of the
|
* 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:
|
* 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
|
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if (spannable.isEmpty()) {
|
if (spannable.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
|
@ -48,14 +49,21 @@ object MatrixLinkify {
|
||||||
val startPos = match.range.first
|
val startPos = match.range.first
|
||||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||||
val endPos = match.range.last + 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)
|
val span = MatrixPermalinkSpan(url, callback)
|
||||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasMatch
|
return hasMatch
|
||||||
*/
|
|
||||||
return false
|
// return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
|
internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
|
||||||
|
@ -46,11 +48,14 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
|
||||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||||
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
|
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)
|
// 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) {
|
val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false
|
||||||
res = members.firstOrNull()?.avatarUrl
|
if (isDirectRoom) {
|
||||||
} else if (members.size == 2) {
|
if (members.size == 1) {
|
||||||
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
|
res = members.firstOrNull()?.avatarUrl
|
||||||
res = firstOtherMember?.avatarUrl
|
} else if (members.size == 2) {
|
||||||
|
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
|
||||||
|
res = firstOtherMember?.avatarUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.alias
|
package org.matrix.android.sdk.internal.session.room.alias
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.findByAlias
|
import org.matrix.android.sdk.internal.database.query.findByAlias
|
||||||
|
@ -24,8 +27,6 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import io.realm.Realm
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
|
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
|
||||||
|
@ -50,9 +51,11 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
|
||||||
} else if (!params.searchOnServer) {
|
} else if (!params.searchOnServer) {
|
||||||
Optional.from<String>(null)
|
Optional.from<String>(null)
|
||||||
} else {
|
} else {
|
||||||
roomId = executeRequest<RoomAliasDescription>(eventBus) {
|
roomId = tryOrNull("## Failed to get roomId from alias") {
|
||||||
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
|
executeRequest<RoomAliasDescription>(eventBus) {
|
||||||
}.roomId
|
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
|
||||||
|
}
|
||||||
|
}?.roomId
|
||||||
Optional.from(roomId)
|
Optional.from(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,8 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
} else if (roomEntity?.membership == Membership.JOIN) {
|
} else if (roomEntity?.membership == Membership.JOIN) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
|
val invitedCount = roomSummary?.invitedMembersCount ?: 0
|
||||||
|
val joinedCount = roomSummary?.joinedMembersCount ?: 0
|
||||||
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
||||||
roomSummary.heroes.mapNotNull { userId ->
|
roomSummary.heroes.mapNotNull { userId ->
|
||||||
roomMembers.getLastRoomMember(userId)?.takeIf {
|
roomMembers.getLastRoomMember(userId)?.takeIf {
|
||||||
|
@ -102,22 +104,49 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
activeMembers.where()
|
activeMembers.where()
|
||||||
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||||
.limit(3)
|
.limit(5)
|
||||||
.findAll()
|
.findAll()
|
||||||
.createSnapshot()
|
.createSnapshot()
|
||||||
}
|
}
|
||||||
val otherMembersCount = otherMembersSubset.count()
|
val otherMembersCount = otherMembersSubset.count()
|
||||||
name = when (otherMembersCount) {
|
name = when (otherMembersCount) {
|
||||||
0 -> stringProvider.getString(R.string.room_displayname_empty_room)
|
0 -> {
|
||||||
|
stringProvider.getString(R.string.room_displayname_empty_room)
|
||||||
|
// TODO (was xx and yyy) ...
|
||||||
|
}
|
||||||
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
||||||
2 -> stringProvider.getString(R.string.room_displayname_two_members,
|
2 -> {
|
||||||
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
stringProvider.getString(R.string.room_displayname_two_members,
|
||||||
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
|
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),
|
3 -> {
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1)
|
stringProvider.getString(R.string.room_displayname_3_members,
|
||||||
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[2], roomMembers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
stringProvider.getString(R.string.room_displayname_4_members,
|
||||||
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[2], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[3], roomMembers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val remainingCount = invitedCount + joinedCount - otherMembersCount + 1
|
||||||
|
stringProvider.getQuantityString(
|
||||||
|
R.plurals.room_displayname_four_and_more_members,
|
||||||
|
remainingCount,
|
||||||
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[2], roomMembers),
|
||||||
|
remainingCount
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return name ?: roomId
|
return name ?: roomId
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.membership
|
package org.matrix.android.sdk.internal.session.room.membership
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||||
|
@ -25,8 +27,6 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is an helper around STATE_ROOM_MEMBER events.
|
* This class is an helper around STATE_ROOM_MEMBER events.
|
||||||
|
|
|
@ -175,13 +175,22 @@
|
||||||
|
|
||||||
<!-- The 2 parameters will be members' name -->
|
<!-- The 2 parameters will be members' name -->
|
||||||
<string name="room_displayname_two_members">%1$s and %2$s</string>
|
<string name="room_displayname_two_members">%1$s and %2$s</string>
|
||||||
|
<!-- The 3 parameters will be members' name -->
|
||||||
|
<string name="room_displayname_3_members">%1$s, %2$s and %3$s</string>
|
||||||
|
<!-- The 4 parameters will be members' name -->
|
||||||
|
<string name="room_displayname_4_members">%1$s, %2$s, %3$s and %4$s</string>
|
||||||
|
<!-- The 3 first parameters will be members' name -->
|
||||||
|
<plurals name="room_displayname_four_and_more_members">
|
||||||
|
<item quantity="one">%1$s, %2$s, %3$s and %4$d other</item>
|
||||||
|
<item quantity="other">%1$s, %2$s, %3$s and %4$d others</item>
|
||||||
|
</plurals>
|
||||||
<plurals name="room_displayname_three_and_more_members">
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
<item quantity="one">%1$s and 1 other</item>
|
<item quantity="one">%1$s and 1 other</item>
|
||||||
<item quantity="other">%1$s and %2$d others</item>
|
<item quantity="other">%1$s and %2$d others</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<string name="room_displayname_empty_room">Empty room</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">Initial Sync:\nImporting account…</string>
|
||||||
<string name="initial_sync_start_importing_account_crypto">Initial Sync:\nImporting crypto</string>
|
<string name="initial_sync_start_importing_account_crypto">Initial Sync:\nImporting crypto</string>
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail
|
package im.vector.app.features.home.room.detail
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
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.session.widgets.model.Widget
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
sealed class RoomDetailAction : VectorViewModelAction {
|
sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
||||||
|
@ -90,4 +93,9 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
|
|
||||||
data class OpenOrCreateDm(val userId: String) : RoomDetailAction()
|
data class OpenOrCreateDm(val userId: String) : RoomDetailAction()
|
||||||
data class JumpToReadReceipt(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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
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.dialogs.withColoredButton
|
||||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.app.core.extensions.cleanup
|
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.extensions.trackItemsVisibilityChange
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.glide.GlideRequests
|
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.intent.getMimeTypeFromUri
|
||||||
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.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.NavigationInterceptor
|
||||||
import im.vector.app.features.permalink.PermalinkHandler
|
import im.vector.app.features.permalink.PermalinkHandler
|
||||||
import im.vector.app.features.reactions.EmojiReactionPickerActivity
|
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.VectorPreferences
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import im.vector.app.features.share.SharedData
|
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 timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -229,7 +233,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
AttachmentsHelper.Callback,
|
AttachmentsHelper.Callback,
|
||||||
// RoomWidgetsBannerView.Callback,
|
GalleryOrCameraDialogHelper.Listener,
|
||||||
ActiveCallView.Callback {
|
ActiveCallView.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -250,6 +254,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private const val ircPattern = " (IRC)"
|
private const val ircPattern = " (IRC)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
|
||||||
|
|
||||||
private val roomDetailArgs: RoomDetailArgs by args()
|
private val roomDetailArgs: RoomDetailArgs by args()
|
||||||
private val glideRequests by lazy {
|
private val glideRequests by lazy {
|
||||||
GlideApp.with(this)
|
GlideApp.with(this)
|
||||||
|
@ -364,6 +370,12 @@ class RoomDetailFragment @Inject constructor(
|
||||||
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
||||||
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
||||||
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(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
|
}.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) {
|
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
|
||||||
navigator.openRoom(requireContext(), openRoom.roomId, null)
|
navigator.openRoom(requireContext(), openRoom.roomId, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
package im.vector.app.features.home.room.detail
|
package im.vector.app.features.home.room.detail
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import im.vector.app.core.platform.VectorViewEvents
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
import im.vector.app.features.command.Command
|
import im.vector.app.features.command.Command
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
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 org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -43,6 +45,11 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||||
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
||||||
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : 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 ShowWaitingView : RoomDetailViewEvents()
|
||||||
object HideWaitingView : RoomDetailViewEvents()
|
object HideWaitingView : RoomDetailViewEvents()
|
||||||
|
|
||||||
|
|
|
@ -277,9 +277,39 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||||
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
||||||
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(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
|
}.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) {
|
private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) {
|
||||||
val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId)
|
val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId)
|
||||||
if (existingDmRoomId == null) {
|
if (existingDmRoomId == null) {
|
||||||
|
|
|
@ -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.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.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.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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
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.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.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
||||||
|
@ -187,6 +191,11 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
|
||||||
collapsedEventIds.removeAll(mergedEventIds)
|
collapsedEventIds.removeAll(mergedEventIds)
|
||||||
}
|
}
|
||||||
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
|
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(
|
val attributes = MergedRoomCreationItem.Attributes(
|
||||||
isCollapsed = isCollapsed,
|
isCollapsed = isCollapsed,
|
||||||
mergeData = mergedData,
|
mergeData = mergedData,
|
||||||
|
@ -198,13 +207,19 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
|
||||||
hasEncryptionEvent = hasEncryption,
|
hasEncryptionEvent = hasEncryption,
|
||||||
isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM,
|
isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM,
|
||||||
readReceiptsCallback = callback,
|
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_()
|
MergedRoomCreationItem_()
|
||||||
.id(mergeId)
|
.id(mergeId)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.highlighted(isCollapsed && highlighted)
|
.highlighted(isCollapsed && highlighted)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
.also {
|
.also {
|
||||||
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,14 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.item
|
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.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -28,8 +31,16 @@ import androidx.core.view.updateLayoutParams
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
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.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.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)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
|
||||||
abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.Holder>() {
|
abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.Holder>() {
|
||||||
|
@ -37,11 +48,16 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
override lateinit var attributes: Attributes
|
override lateinit var attributes: Attributes
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var movementMethod: MovementMethod? = null
|
||||||
|
|
||||||
override fun getViewType() = STUB_ID
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
|
|
||||||
|
bindCreationSummaryTile(holder)
|
||||||
|
|
||||||
if (attributes.isCollapsed) {
|
if (attributes.isCollapsed) {
|
||||||
// Take the oldest data
|
// Take the oldest data
|
||||||
val data = distinctMergeData.lastOrNull()
|
val data = distinctMergeData.lastOrNull()
|
||||||
|
@ -70,34 +86,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
holder.avatarView.visibility = View.GONE
|
holder.avatarView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.hasEncryptionEvent) {
|
bindEncryptionTile(holder, data)
|
||||||
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
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
holder.avatarView.visibility = View.INVISIBLE
|
holder.avatarView.visibility = View.INVISIBLE
|
||||||
holder.summaryView.visibility = View.GONE
|
holder.summaryView.visibility = View.GONE
|
||||||
|
@ -107,6 +96,109 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
holder.readReceiptsView.isVisible = false
|
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()
|
||||||
|
val shouldSetAvatar = attributes.canChangeAvatar
|
||||||
|
&& (roomSummary?.isDirect == false || (isDirect && membersCount >= 2))
|
||||||
|
&& roomItem?.avatarUrl.isNullOrBlank()
|
||||||
|
|
||||||
|
holder.roomAvatarImageView.isVisible = roomItem != null
|
||||||
|
if (roomItem != null) {
|
||||||
|
attributes.avatarRenderer.render(roomItem, holder.roomAvatarImageView)
|
||||||
|
holder.roomAvatarImageView.setOnClickListener(DebouncedClickListener({ view ->
|
||||||
|
if (shouldSetAvatar) {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar)
|
||||||
|
} else {
|
||||||
|
// Note: this is no op if there is no avatar on the room
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.ShowRoomAvatarFullScreen(roomItem, view))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.setAvatarButton.isVisible = shouldSetAvatar
|
||||||
|
if (shouldSetAvatar) {
|
||||||
|
holder.setAvatarButton.setOnClickListener(DebouncedClickListener({ _ ->
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.addPeopleButton.isVisible = !isDirect
|
||||||
|
if (!isDirect) {
|
||||||
|
holder.addPeopleButton.setOnClickListener(DebouncedClickListener({ _ ->
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionInvitePeople)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Holder : BasedMergedItem.Holder(STUB_ID) {
|
class Holder : BasedMergedItem.Holder(STUB_ID) {
|
||||||
val summaryView by bind<TextView>(R.id.itemNoticeTextView)
|
val summaryView by bind<TextView>(R.id.itemNoticeTextView)
|
||||||
val avatarView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
val avatarView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
||||||
|
@ -114,6 +206,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
|
|
||||||
val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
|
val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
|
||||||
val e2eTitleDescriptionView by bind<TextView>(R.id.itemVerificationDoneDetailTextView)
|
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.creationTileRoomAvatarImageView)
|
||||||
|
val addPeopleButton by bind<View>(R.id.creationTileAddPeopleButton)
|
||||||
|
val setAvatarButton by bind<View>(R.id.creationTileSetAvatarButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -126,8 +225,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
override val avatarRenderer: AvatarRenderer,
|
override val avatarRenderer: AvatarRenderer,
|
||||||
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||||
override val onCollapsedStateChanged: (Boolean) -> Unit,
|
override val onCollapsedStateChanged: (Boolean) -> Unit,
|
||||||
|
val callback: TimelineEventController.Callback? = null,
|
||||||
val currentUserId: String,
|
val currentUserId: String,
|
||||||
val hasEncryptionEvent: Boolean,
|
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
|
) : BasedMergedItem.Attributes
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,8 +248,8 @@ class DefaultNavigator @Inject constructor(
|
||||||
context.startActivity(KeysBackupManageActivity.intent(context))
|
context.startActivity(KeysBackupManageActivity.intent(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openRoomProfile(context: Context, roomId: String) {
|
override fun openRoomProfile(context: Context, roomId: String, directAccess: Int?) {
|
||||||
context.startActivity(RoomProfileActivity.newIntent(context, roomId))
|
context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
|
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
|
||||||
|
|
|
@ -78,7 +78,7 @@ interface Navigator {
|
||||||
|
|
||||||
fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)
|
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)
|
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,16 @@ class RoomProfileActivity :
|
||||||
|
|
||||||
companion object {
|
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)
|
val roomProfileArgs = RoomProfileArgs(roomId)
|
||||||
return Intent(context, RoomProfileActivity::class.java).apply {
|
return Intent(context, RoomProfileActivity::class.java).apply {
|
||||||
putExtra(MvRx.KEY_ARG, roomProfileArgs)
|
putExtra(MvRx.KEY_ARG, roomProfileArgs)
|
||||||
|
putExtra(EXTRA_DIRECT_ACCESS, directAccess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +86,13 @@ class RoomProfileActivity :
|
||||||
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
|
||||||
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
|
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
|
||||||
if (isFirstCreation()) {
|
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
|
sharedActionViewModel
|
||||||
.observe()
|
.observe()
|
||||||
|
|
|
@ -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>
|
|
@ -1,31 +1,149 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/creationEncryptionTile"
|
android:id="@+id/creationTile"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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">
|
|
||||||
|
|
||||||
<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/creationTileRoomAvatarImageView"
|
||||||
|
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
|
||||||
|
android:id="@+id/creationTileSetAvatarButton"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||||
|
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/creationTileRoomAvatarImageView"
|
||||||
|
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/creationTileRoomAvatarImageView"
|
||||||
|
tools:text="@sample/matrix.json/data/roomName" />
|
||||||
|
|
||||||
|
<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="@string/this_is_the_beginning_of_room_no_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="@string/room_created_summary_no_topic_creation_text" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
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:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomNameTopicText">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/addPeopleButtonBg"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/circle"
|
||||||
|
android:backgroundTint="@color/riotx_accent"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_add_people" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:text="@string/add_people"
|
||||||
|
android:textColor="@color/riotx_accent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/mergedSumContainer"
|
android:id="@+id/mergedSumContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/creationEncryptionTile">
|
android:layout_below="@+id/creationTile"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/itemNoticeAvatarView"
|
android:id="@+id/itemNoticeAvatarView"
|
||||||
|
|
|
@ -2403,6 +2403,13 @@
|
||||||
<string name="room_created_summary_item_by_you">You created and configured the room.</string>
|
<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">%s joined.</string>
|
||||||
<string name="direct_room_created_summary_item_by_you">You 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_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>
|
<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="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_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="invite_users_to_room_action_invite">INVITE</string>
|
||||||
<string name="inviting_users_to_room">Inviting users…</string>
|
<string name="inviting_users_to_room">Inviting users…</string>
|
||||||
<string name="invite_users_to_room_title">Invite 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_name_hint">Room Name</string>
|
||||||
<string name="room_settings_topic_hint">Topic</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_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_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>
|
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>
|
||||||
|
|
Loading…
Reference in New Issue