Merge branch 'develop' into feature/initial_sync
This commit is contained in:
commit
63828bc159
|
@ -6,6 +6,8 @@ Features ✨:
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- The initial sync is now handled by a foreground service
|
- The initial sync is now handled by a foreground service
|
||||||
|
- Render aliases and canonical alias change in the timeline
|
||||||
|
- Fix autocompletion issues and add support for rooms and groups
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
-
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package im.vector.matrix.android.api.permalinks
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatrixLinkify take a piece of text and turns all of the
|
* MatrixLinkify take a piece of text and turns all of the
|
||||||
|
@ -30,7 +29,13 @@ object MatrixLinkify {
|
||||||
*
|
*
|
||||||
* @param spannable the text in which the matrix items has to be clickable.
|
* @param spannable the text in which the matrix items has to be clickable.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||||
|
/**
|
||||||
|
* 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
|
// sanity checks
|
||||||
if (spannable.isEmpty()) {
|
if (spannable.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
|
@ -50,5 +55,7 @@ object MatrixLinkify {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasMatch
|
return hasMatch
|
||||||
|
*/
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,23 +56,23 @@ object PermalinkParser {
|
||||||
|
|
||||||
val identifier = params.getOrNull(0)
|
val identifier = params.getOrNull(0)
|
||||||
val extraParameter = params.getOrNull(1)
|
val extraParameter = params.getOrNull(1)
|
||||||
if (identifier.isNullOrEmpty()) {
|
|
||||||
return PermalinkData.FallbackLink(uri)
|
|
||||||
}
|
|
||||||
return when {
|
return when {
|
||||||
|
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
|
||||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||||
MatrixPatterns.isRoomId(identifier) -> {
|
MatrixPatterns.isRoomId(identifier) -> {
|
||||||
val eventId = extraParameter.takeIf {
|
PermalinkData.RoomLink(
|
||||||
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
|
roomIdOrAlias = identifier,
|
||||||
}
|
isRoomAlias = false,
|
||||||
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false, eventId = eventId)
|
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
MatrixPatterns.isRoomAlias(identifier) -> {
|
MatrixPatterns.isRoomAlias(identifier) -> {
|
||||||
val eventId = extraParameter.takeIf {
|
PermalinkData.RoomLink(
|
||||||
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
|
roomIdOrAlias = identifier,
|
||||||
}
|
isRoomAlias = true,
|
||||||
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true, eventId = eventId)
|
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> PermalinkData.FallbackLink(uri)
|
else -> PermalinkData.FallbackLink(uri)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,10 +50,10 @@ object EventType {
|
||||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||||
const val STATE_ROOM_ALIASES = "m.room.aliases"
|
const val STATE_ROOM_ALIASES = "m.room.aliases"
|
||||||
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
|
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
|
||||||
const val STATE_CANONICAL_ALIAS = "m.room.canonical_alias"
|
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"
|
||||||
const val STATE_HISTORY_VISIBILITY = "m.room.history_visibility"
|
const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility"
|
||||||
const val STATE_RELATED_GROUPS = "m.room.related_groups"
|
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
||||||
const val STATE_PINNED_EVENT = "m.room.pinned_events"
|
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
||||||
|
|
||||||
// Call Events
|
// Call Events
|
||||||
|
|
||||||
|
@ -86,10 +86,12 @@ object EventType {
|
||||||
STATE_ROOM_JOIN_RULES,
|
STATE_ROOM_JOIN_RULES,
|
||||||
STATE_ROOM_GUEST_ACCESS,
|
STATE_ROOM_GUEST_ACCESS,
|
||||||
STATE_ROOM_POWER_LEVELS,
|
STATE_ROOM_POWER_LEVELS,
|
||||||
|
STATE_ROOM_ALIASES,
|
||||||
STATE_ROOM_TOMBSTONE,
|
STATE_ROOM_TOMBSTONE,
|
||||||
STATE_HISTORY_VISIBILITY,
|
STATE_ROOM_CANONICAL_ALIAS,
|
||||||
STATE_RELATED_GROUPS,
|
STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
STATE_PINNED_EVENT
|
STATE_ROOM_RELATED_GROUPS,
|
||||||
|
STATE_ROOM_PINNED_EVENT
|
||||||
)
|
)
|
||||||
|
|
||||||
fun isStateEvent(type: String): Boolean {
|
fun isStateEvent(type: String): Boolean {
|
||||||
|
|
|
@ -31,6 +31,13 @@ interface GroupService {
|
||||||
*/
|
*/
|
||||||
fun getGroup(groupId: String): Group?
|
fun getGroup(groupId: String): Group?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a groupSummary from a groupId
|
||||||
|
* @param groupId the groupId to look for.
|
||||||
|
* @return the groupSummary with groupId or null
|
||||||
|
*/
|
||||||
|
fun getGroupSummary(groupId: String): GroupSummary?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of [GroupSummary]
|
* @return the [LiveData] of [GroupSummary]
|
||||||
|
|
|
@ -52,6 +52,13 @@ interface RoomService {
|
||||||
*/
|
*/
|
||||||
fun getRoom(roomId: String): Room?
|
fun getRoom(roomId: String): Room?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a roomSummary from a roomId or a room alias
|
||||||
|
* @param roomIdOrAlias the roomId or the alias of a room to look for.
|
||||||
|
* @return a matching room summary or null
|
||||||
|
*/
|
||||||
|
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @return the [LiveData] of [RoomSummary]
|
||||||
|
|
|
@ -20,7 +20,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the EventType.STATE_CANONICAL_ALIAS state event content
|
* Class representing the EventType.STATE_ROOM_CANONICAL_ALIAS state event content
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class RoomCanonicalAliasContent(
|
data class RoomCanonicalAliasContent(
|
||||||
|
|
|
@ -145,13 +145,13 @@ class CreateRoomParams {
|
||||||
*/
|
*/
|
||||||
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
||||||
// Remove the existing value if any.
|
// Remove the existing value if any.
|
||||||
initialStates?.removeAll { it.getClearType() == EventType.STATE_HISTORY_VISIBILITY }
|
initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY }
|
||||||
|
|
||||||
if (historyVisibility != null) {
|
if (historyVisibility != null) {
|
||||||
val contentMap = HashMap<String, RoomHistoryVisibility>()
|
val contentMap = HashMap<String, RoomHistoryVisibility>()
|
||||||
contentMap["history_visibility"] = historyVisibility
|
contentMap["history_visibility"] = historyVisibility
|
||||||
|
|
||||||
val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY,
|
val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
stateKey = "",
|
stateKey = "",
|
||||||
content = contentMap.toContent())
|
content = contentMap.toContent())
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ interface RelationService {
|
||||||
/**
|
/**
|
||||||
* Reply to an event in the timeline (must be in same room)
|
* Reply to an event in the timeline (must be in same room)
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
||||||
* The replyText can be a Spannable and contains special spans (UserMentionSpan) that will be translated
|
* The replyText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||||
* by the sdk into pills.
|
* by the sdk into pills.
|
||||||
* @param eventReplied the event referenced by the reply
|
* @param eventReplied the event referenced by the reply
|
||||||
* @param replyText the reply text
|
* @param replyText the reply text
|
||||||
|
|
|
@ -19,9 +19,9 @@ package im.vector.matrix.android.api.session.room.send
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag class for spans that should mention a user.
|
* Tag class for spans that should mention a matrix item.
|
||||||
* These Spans will be transformed into pills when detected in message to send
|
* These Spans will be transformed into pills when detected in message to send
|
||||||
*/
|
*/
|
||||||
interface UserMentionSpan {
|
interface MatrixItemSpan {
|
||||||
val matrixItem: MatrixItem
|
val matrixItem: MatrixItem
|
||||||
}
|
}
|
|
@ -29,7 +29,7 @@ interface SendService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send a text message asynchronously.
|
* Method to send a text message asynchronously.
|
||||||
* The text to send can be a Spannable and contains special spans (UserMentionSpan) that will be translated
|
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||||
* by the sdk into pills.
|
* by the sdk into pills.
|
||||||
* @param text the text message to send
|
* @param text the text message to send
|
||||||
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
||||||
|
|
|
@ -62,6 +62,9 @@ sealed class MatrixItem(
|
||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
|
override fun getBestName() = id
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GroupItem(override val id: String,
|
data class GroupItem(override val id: String,
|
||||||
|
@ -71,9 +74,12 @@ sealed class MatrixItem(
|
||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
|
override fun getBestName() = id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBestName(): String {
|
open fun getBestName(): String {
|
||||||
return displayName?.takeIf { it.isNotBlank() } ?: id
|
return displayName?.takeIf { it.isNotBlank() } ?: id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +101,7 @@ sealed class MatrixItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun firstLetterOfDisplayName(): String {
|
fun firstLetterOfDisplayName(): String {
|
||||||
return getBestName()
|
return (displayName?.takeIf { it.isNotBlank() } ?: id)
|
||||||
.let { dn ->
|
.let { dn ->
|
||||||
var startIndex = 0
|
var startIndex = 0
|
||||||
val initial = dn[startIndex]
|
val initial = dn[startIndex]
|
||||||
|
@ -138,4 +144,5 @@ sealed class MatrixItem(
|
||||||
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
||||||
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
|
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
|
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
|
||||||
|
|
|
@ -147,7 +147,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
when {
|
when {
|
||||||
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
when {
|
when {
|
||||||
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultGroupService @Inject constructor(private val monarchy: Monarchy) : GroupService {
|
internal class DefaultGroupService @Inject constructor(private val monarchy: Monarchy) : GroupService {
|
||||||
|
@ -33,6 +34,13 @@ internal class DefaultGroupService @Inject constructor(private val monarchy: Mon
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getGroupSummary(groupId: String): GroupSummary? {
|
||||||
|
return monarchy.fetchCopyMap(
|
||||||
|
{ realm -> GroupSummaryEntity.where(realm, groupId).findFirst() },
|
||||||
|
{ it, _ -> it.asDomain() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun liveGroupSummaries(): LiveData<List<GroupSummary>> {
|
override fun liveGroupSummaries(): LiveData<List<GroupSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm -> GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME) },
|
{ realm -> GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME) },
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.query.findByAlias
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
|
@ -38,6 +39,7 @@ import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -69,6 +71,21 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
|
return monarchy
|
||||||
|
.fetchCopyMap({
|
||||||
|
if (roomIdOrAlias.startsWith("!")) {
|
||||||
|
// It's a roomId
|
||||||
|
RoomSummaryEntity.where(it, roomId = roomIdOrAlias).findFirst()
|
||||||
|
} else {
|
||||||
|
// Assume it's a room alias
|
||||||
|
RoomSummaryEntity.findByAlias(it, roomIdOrAlias)
|
||||||
|
}
|
||||||
|
}, { entity, _ ->
|
||||||
|
roomSummaryMapper.map(entity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm ->
|
{ realm ->
|
||||||
|
|
|
@ -52,7 +52,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
EventType.STATE_ROOM_MEMBER,
|
EventType.STATE_ROOM_MEMBER,
|
||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
|
@ -91,7 +91,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
||||||
|
|
||||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
||||||
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
|
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
|
||||||
val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
|
val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev()
|
||||||
val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
|
val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
|
||||||
|
|
||||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||||
|
|
|
@ -62,7 +62,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
||||||
return@doWithRealm
|
return@doWithRealm
|
||||||
}
|
}
|
||||||
|
|
||||||
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
|
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev()
|
||||||
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
|
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
|
||||||
if (!name.isNullOrEmpty()) {
|
if (!name.isNullOrEmpty()) {
|
||||||
return@doWithRealm
|
return@doWithRealm
|
||||||
|
|
|
@ -118,7 +118,7 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
||||||
"redact",
|
"redact",
|
||||||
"invite")
|
"invite")
|
||||||
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
|
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
|
||||||
EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
|
EventType.STATE_ROOM_CANONICAL_ALIAS -> listOf("alias")
|
||||||
EventType.FEEDBACK -> listOf("type", "target_event_id")
|
EventType.FEEDBACK -> listOf("type", "target_event_id")
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.send.pills
|
package im.vector.matrix.android.internal.session.room.send.pills
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.send.UserMentionSpan
|
import im.vector.matrix.android.api.session.room.send.MatrixItemSpan
|
||||||
|
|
||||||
internal data class MentionLinkSpec(
|
internal data class MentionLinkSpec(
|
||||||
val span: UserMentionSpan,
|
val span: MatrixItemSpan,
|
||||||
val start: Int,
|
val start: Int,
|
||||||
val end: Int
|
val end: Int
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,15 +16,13 @@
|
||||||
package im.vector.matrix.android.internal.session.room.send.pills
|
package im.vector.matrix.android.internal.session.room.send.pills
|
||||||
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import im.vector.matrix.android.api.session.room.send.UserMentionSpan
|
import im.vector.matrix.android.api.session.room.send.MatrixItemSpan
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to detect special span in CharSequence and turn them into
|
* Utility class to detect special span in CharSequence and turn them into
|
||||||
* formatted text to send them as a Matrix messages.
|
* formatted text to send them as a Matrix messages.
|
||||||
*
|
|
||||||
* For now only support UserMentionSpans (TODO rooms, room aliases, etc...)
|
|
||||||
*/
|
*/
|
||||||
internal class TextPillsUtils @Inject constructor(
|
internal class TextPillsUtils @Inject constructor(
|
||||||
private val mentionLinkSpecComparator: MentionLinkSpecComparator
|
private val mentionLinkSpecComparator: MentionLinkSpecComparator
|
||||||
|
@ -49,7 +47,7 @@ internal class TextPillsUtils @Inject constructor(
|
||||||
private fun transformPills(text: CharSequence, template: String): String? {
|
private fun transformPills(text: CharSequence, template: String): String? {
|
||||||
val spannableString = SpannableString.valueOf(text)
|
val spannableString = SpannableString.valueOf(text)
|
||||||
val pills = spannableString
|
val pills = spannableString
|
||||||
?.getSpans(0, text.length, UserMentionSpan::class.java)
|
?.getSpans(0, text.length, MatrixItemSpan::class.java)
|
||||||
?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) }
|
?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) }
|
||||||
?.toMutableList()
|
?.toMutableList()
|
||||||
?.takeIf { it.isNotEmpty() }
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
@ -65,7 +63,7 @@ internal class TextPillsUtils @Inject constructor(
|
||||||
// append text before pill
|
// append text before pill
|
||||||
append(text, currIndex, start)
|
append(text, currIndex, start)
|
||||||
// append the pill
|
// append the pill
|
||||||
append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.displayName))
|
append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.getBestName()))
|
||||||
currIndex = end
|
currIndex = end
|
||||||
}
|
}
|
||||||
// append text after the last pill
|
// append text after the last pill
|
||||||
|
|
|
@ -2,6 +2,19 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
|
|
||||||
|
<plurals name="notice_room_aliases_added">
|
||||||
|
<item quantity="one">%1$s added %2$s as an address for this room.</item>
|
||||||
|
<item quantity="other">%1$s added %2$s as addresses for this room.</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notice_room_aliases_removed">
|
||||||
|
<item quantity="one">%1$s removed %2$s as an address for this room.</item>
|
||||||
|
<item quantity="other">%1$s removed %3$s as addresses for this room.</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="notice_room_aliases_added_and_removed">%1$s added %2$s and removed %3$s as addresses for this room.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_canonical_alias_set">"%1$s set the main address for this room to %2$s."</string>
|
||||||
|
<string name="notice_room_canonical_alias_unset">"%1$s removed the main address for this room."</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.autocomplete.user
|
package im.vector.riotx.features.autocomplete
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
@ -25,23 +25,27 @@ import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_autocomplete_user)
|
@EpoxyModelClass(layout = R.layout.item_autocomplete_matrix_item)
|
||||||
abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() {
|
abstract class AutocompleteMatrixItem : VectorEpoxyModel<AutocompleteMatrixItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
|
@EpoxyAttribute var subName: String? = null
|
||||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
holder.view.setOnClickListener(clickListener)
|
holder.view.setOnClickListener(clickListener)
|
||||||
holder.nameView.text = matrixItem.getBestName()
|
holder.nameView.text = matrixItem.getBestName()
|
||||||
|
holder.subNameView.setTextOrHide(subName)
|
||||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val nameView by bind<TextView>(R.id.userAutocompleteName)
|
val nameView by bind<TextView>(R.id.matrixItemAutocompleteName)
|
||||||
val avatarImageView by bind<ImageView>(R.id.userAutocompleteAvatar)
|
val subNameView by bind<TextView>(R.id.matrixItemAutocompleteSubname)
|
||||||
|
val avatarImageView by bind<ImageView>(R.id.matrixItemAutocompleteAvatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.riotx.features.autocomplete
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.DataSetObserver
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.airbnb.epoxy.EpoxyController
|
|
||||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
|
||||||
import com.otaliastudios.autocomplete.AutocompletePresenter
|
|
||||||
|
|
||||||
abstract class EpoxyAutocompletePresenter<T>(context: Context) : AutocompletePresenter<T>(context), AutocompleteClickListener<T> {
|
|
||||||
|
|
||||||
private var recyclerView: EpoxyRecyclerView? = null
|
|
||||||
private var clicks: AutocompletePresenter.ClickProvider<T>? = null
|
|
||||||
private var observer: Observer? = null
|
|
||||||
|
|
||||||
override fun registerClickProvider(provider: AutocompletePresenter.ClickProvider<T>) {
|
|
||||||
this.clicks = provider
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun registerDataSetObserver(observer: DataSetObserver) {
|
|
||||||
this.observer = Observer(observer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getView(): ViewGroup? {
|
|
||||||
recyclerView = EpoxyRecyclerView(context).apply {
|
|
||||||
setController(providesController())
|
|
||||||
observer?.let {
|
|
||||||
adapter?.registerAdapterDataObserver(it)
|
|
||||||
}
|
|
||||||
itemAnimator = null
|
|
||||||
}
|
|
||||||
return recyclerView
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewShown() {}
|
|
||||||
|
|
||||||
override fun onViewHidden() {
|
|
||||||
recyclerView = null
|
|
||||||
observer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun providesController(): EpoxyController
|
|
||||||
|
|
||||||
protected fun dispatchLayoutChange() {
|
|
||||||
observer?.onChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(t: T) {
|
|
||||||
clicks?.click(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() {
|
|
||||||
|
|
||||||
override fun onChanged() {
|
|
||||||
root.onChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
|
||||||
root.onChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
|
||||||
root.onChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
|
||||||
root.onChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
root.onChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,21 +17,28 @@
|
||||||
package im.vector.riotx.features.autocomplete.command
|
package im.vector.riotx.features.autocomplete.command
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import im.vector.riotx.features.autocomplete.EpoxyAutocompletePresenter
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteCommandPresenter @Inject constructor(context: Context,
|
class AutocompleteCommandPresenter @Inject constructor(context: Context,
|
||||||
private val controller: AutocompleteCommandController) :
|
private val controller: AutocompleteCommandController) :
|
||||||
EpoxyAutocompletePresenter<Command>(context) {
|
RecyclerViewPresenter<Command>(context), AutocompleteClickListener<Command> {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun providesController(): EpoxyController {
|
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
||||||
return controller
|
// Also remove animation
|
||||||
|
recyclerView?.itemAnimator = null
|
||||||
|
return controller.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(t: Command) {
|
||||||
|
dispatchClick(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQuery(query: CharSequence?) {
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.autocomplete.group
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutocompleteGroupController @Inject constructor() : TypedEpoxyController<List<GroupSummary>>() {
|
||||||
|
|
||||||
|
var listener: AutocompleteClickListener<GroupSummary>? = null
|
||||||
|
|
||||||
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
|
override fun buildModels(data: List<GroupSummary>?) {
|
||||||
|
if (data.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.forEach { groupSummary ->
|
||||||
|
autocompleteMatrixItem {
|
||||||
|
id(groupSummary.groupId)
|
||||||
|
matrixItem(groupSummary.toMatrixItem())
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
clickListener { _ ->
|
||||||
|
listener?.onItemClick(groupSummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.autocomplete.group
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutocompleteGroupPresenter @Inject constructor(context: Context,
|
||||||
|
private val controller: AutocompleteGroupController
|
||||||
|
) : RecyclerViewPresenter<GroupSummary>(context), AutocompleteClickListener<GroupSummary> {
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
||||||
|
// Also remove animation
|
||||||
|
recyclerView?.itemAnimator = null
|
||||||
|
return controller.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(t: GroupSummary) {
|
||||||
|
dispatchClick(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
callback?.onQueryGroups(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(groups: Async<List<GroupSummary>>) {
|
||||||
|
if (groups is Success) {
|
||||||
|
controller.setData(groups())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onQueryGroups(query: CharSequence?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.autocomplete.room
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutocompleteRoomController @Inject constructor() : TypedEpoxyController<List<RoomSummary>>() {
|
||||||
|
|
||||||
|
var listener: AutocompleteClickListener<RoomSummary>? = null
|
||||||
|
|
||||||
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
|
override fun buildModels(data: List<RoomSummary>?) {
|
||||||
|
if (data.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.forEach { roomSummary ->
|
||||||
|
autocompleteMatrixItem {
|
||||||
|
id(roomSummary.roomId)
|
||||||
|
matrixItem(roomSummary.toMatrixItem())
|
||||||
|
subName(roomSummary.canonicalAlias)
|
||||||
|
avatarRenderer(avatarRenderer)
|
||||||
|
clickListener { _ ->
|
||||||
|
listener?.onItemClick(roomSummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.features.autocomplete.room
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AutocompleteRoomPresenter @Inject constructor(context: Context,
|
||||||
|
private val controller: AutocompleteRoomController
|
||||||
|
) : RecyclerViewPresenter<RoomSummary>(context), AutocompleteClickListener<RoomSummary> {
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
||||||
|
// Also remove animation
|
||||||
|
recyclerView?.itemAnimator = null
|
||||||
|
return controller.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(t: RoomSummary) {
|
||||||
|
dispatchClick(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
callback?.onQueryRooms(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(rooms: Async<List<RoomSummary>>) {
|
||||||
|
if (rooms is Success) {
|
||||||
|
controller.setData(rooms())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onQueryRooms(query: CharSequence?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ class AutocompleteUserController @Inject constructor() : TypedEpoxyController<Li
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data.forEach { user ->
|
data.forEach { user ->
|
||||||
autocompleteUserItem {
|
autocompleteMatrixItem {
|
||||||
id(user.userId)
|
id(user.userId)
|
||||||
matrixItem(user.toMatrixItem())
|
matrixItem(user.toMatrixItem())
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
|
|
|
@ -17,16 +17,17 @@
|
||||||
package im.vector.riotx.features.autocomplete.user
|
package im.vector.riotx.features.autocomplete.user
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.features.autocomplete.EpoxyAutocompletePresenter
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteUserPresenter @Inject constructor(context: Context,
|
class AutocompleteUserPresenter @Inject constructor(context: Context,
|
||||||
private val controller: AutocompleteUserController
|
private val controller: AutocompleteUserController
|
||||||
) : EpoxyAutocompletePresenter<User>(context) {
|
) : RecyclerViewPresenter<User>(context), AutocompleteClickListener<User> {
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
@ -34,8 +35,14 @@ class AutocompleteUserPresenter @Inject constructor(context: Context,
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun providesController(): EpoxyController {
|
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
||||||
return controller
|
// Also remove animation
|
||||||
|
recyclerView?.itemAnimator = null
|
||||||
|
return controller.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(t: User) {
|
||||||
|
dispatchClick(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQuery(query: CharSequence?) {
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
|
|
@ -59,7 +59,9 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
|
@ -68,6 +70,7 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.dialogs.withColoredButton
|
import im.vector.riotx.core.dialogs.withColoredButton
|
||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
|
@ -83,6 +86,8 @@ import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||||
import im.vector.riotx.features.attachments.ContactAttachment
|
import im.vector.riotx.features.attachments.ContactAttachment
|
||||||
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
||||||
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||||
|
import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
||||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
@ -140,6 +145,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private val commandAutocompletePolicy: CommandAutocompletePolicy,
|
private val commandAutocompletePolicy: CommandAutocompletePolicy,
|
||||||
private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
|
private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
|
||||||
private val autocompleteUserPresenter: AutocompleteUserPresenter,
|
private val autocompleteUserPresenter: AutocompleteUserPresenter,
|
||||||
|
private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
|
||||||
|
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
|
||||||
private val permalinkHandler: PermalinkHandler,
|
private val permalinkHandler: PermalinkHandler,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
||||||
|
@ -150,6 +157,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
VectorBaseFragment(),
|
VectorBaseFragment(),
|
||||||
TimelineEventController.Callback,
|
TimelineEventController.Callback,
|
||||||
AutocompleteUserPresenter.Callback,
|
AutocompleteUserPresenter.Callback,
|
||||||
|
AutocompleteRoomPresenter.Callback,
|
||||||
|
AutocompleteGroupPresenter.Callback,
|
||||||
VectorInviteView.Callback,
|
VectorInviteView.Callback,
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
|
@ -592,6 +601,98 @@ class RoomDetailFragment @Inject constructor(
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
autocompleteRoomPresenter.callback = this
|
||||||
|
Autocomplete.on<RoomSummary>(composerLayout.composerEditText)
|
||||||
|
.with(CharPolicy('#', true))
|
||||||
|
.with(autocompleteRoomPresenter)
|
||||||
|
.with(elevation)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<RoomSummary> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: RoomSummary): Boolean {
|
||||||
|
// Detect last '#' and remove it
|
||||||
|
var startIndex = editable.lastIndexOf("#")
|
||||||
|
if (startIndex == -1) {
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect next word separator
|
||||||
|
var endIndex = editable.indexOf(" ", startIndex)
|
||||||
|
if (endIndex == -1) {
|
||||||
|
endIndex = editable.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the word by its completion
|
||||||
|
val matrixItem = item.toRoomAliasMatrixItem()
|
||||||
|
val displayName = matrixItem.getBestName()
|
||||||
|
|
||||||
|
// with a trailing space
|
||||||
|
editable.replace(startIndex, endIndex, "$displayName ")
|
||||||
|
|
||||||
|
// Add the span
|
||||||
|
val span = PillImageSpan(
|
||||||
|
glideRequests,
|
||||||
|
avatarRenderer,
|
||||||
|
requireContext(),
|
||||||
|
matrixItem
|
||||||
|
)
|
||||||
|
span.bind(composerLayout.composerEditText)
|
||||||
|
|
||||||
|
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
autocompleteGroupPresenter.callback = this
|
||||||
|
Autocomplete.on<GroupSummary>(composerLayout.composerEditText)
|
||||||
|
.with(CharPolicy('+', true))
|
||||||
|
.with(autocompleteGroupPresenter)
|
||||||
|
.with(elevation)
|
||||||
|
.with(backgroundDrawable)
|
||||||
|
.with(object : AutocompleteCallback<GroupSummary> {
|
||||||
|
override fun onPopupItemClicked(editable: Editable, item: GroupSummary): Boolean {
|
||||||
|
// Detect last '+' and remove it
|
||||||
|
var startIndex = editable.lastIndexOf("+")
|
||||||
|
if (startIndex == -1) {
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect next word separator
|
||||||
|
var endIndex = editable.indexOf(" ", startIndex)
|
||||||
|
if (endIndex == -1) {
|
||||||
|
endIndex = editable.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the word by its completion
|
||||||
|
val matrixItem = item.toMatrixItem()
|
||||||
|
val displayName = matrixItem.getBestName()
|
||||||
|
|
||||||
|
// with a trailing space
|
||||||
|
editable.replace(startIndex, endIndex, "$displayName ")
|
||||||
|
|
||||||
|
// Add the span
|
||||||
|
val span = PillImageSpan(
|
||||||
|
glideRequests,
|
||||||
|
avatarRenderer,
|
||||||
|
requireContext(),
|
||||||
|
matrixItem
|
||||||
|
)
|
||||||
|
span.bind(composerLayout.composerEditText)
|
||||||
|
|
||||||
|
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPopupVisibilityChanged(shown: Boolean) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
autocompleteUserPresenter.callback = this
|
autocompleteUserPresenter.callback = this
|
||||||
Autocomplete.on<User>(composerLayout.composerEditText)
|
Autocomplete.on<User>(composerLayout.composerEditText)
|
||||||
.with(CharPolicy('@', true))
|
.with(CharPolicy('@', true))
|
||||||
|
@ -734,6 +835,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private fun renderTextComposerState(state: TextComposerViewState) {
|
private fun renderTextComposerState(state: TextComposerViewState) {
|
||||||
autocompleteUserPresenter.render(state.asyncUsers)
|
autocompleteUserPresenter.render(state.asyncUsers)
|
||||||
|
autocompleteRoomPresenter.render(state.asyncRooms)
|
||||||
|
autocompleteGroupPresenter.render(state.asyncGroups)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTombstoneEventHandling(async: Async<String>) {
|
private fun renderTombstoneEventHandling(async: Async<String>) {
|
||||||
|
@ -1066,6 +1169,18 @@ class RoomDetailFragment @Inject constructor(
|
||||||
textComposerViewModel.handle(TextComposerAction.QueryUsers(query))
|
textComposerViewModel.handle(TextComposerAction.QueryUsers(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutocompleteRoomPresenter.Callback
|
||||||
|
|
||||||
|
override fun onQueryRooms(query: CharSequence?) {
|
||||||
|
textComposerViewModel.handle(TextComposerAction.QueryRooms(query))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutocompleteGroupPresenter.Callback
|
||||||
|
|
||||||
|
override fun onQueryGroups(query: CharSequence?) {
|
||||||
|
textComposerViewModel.handle(TextComposerAction.QueryGroups(query))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleActions(action: EventSharedAction) {
|
private fun handleActions(action: EventSharedAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is EventSharedAction.AddReaction -> {
|
is EventSharedAction.AddReaction -> {
|
||||||
|
|
|
@ -20,4 +20,6 @@ import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class TextComposerAction : VectorViewModelAction {
|
sealed class TextComposerAction : VectorViewModelAction {
|
||||||
data class QueryUsers(val query: CharSequence?) : TextComposerAction()
|
data class QueryUsers(val query: CharSequence?) : TextComposerAction()
|
||||||
|
data class QueryRooms(val query: CharSequence?) : TextComposerAction()
|
||||||
|
data class QueryGroups(val query: CharSequence?) : TextComposerAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
@ -32,16 +34,17 @@ import io.reactivex.Observable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
typealias AutocompleteUserQuery = CharSequence
|
typealias AutocompleteQuery = CharSequence
|
||||||
|
|
||||||
class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: TextComposerViewState,
|
class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: TextComposerViewState,
|
||||||
private val session: Session
|
private val session: Session
|
||||||
) : VectorViewModel<TextComposerViewState, TextComposerAction>(initialState) {
|
) : VectorViewModel<TextComposerViewState, TextComposerAction>(initialState) {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
private val roomId = initialState.roomId
|
|
||||||
|
|
||||||
private val usersQueryObservable = BehaviorRelay.create<Option<AutocompleteUserQuery>>()
|
private val usersQueryObservable = BehaviorRelay.create<Option<AutocompleteQuery>>()
|
||||||
|
private val roomsQueryObservable = BehaviorRelay.create<Option<AutocompleteQuery>>()
|
||||||
|
private val groupsQueryObservable = BehaviorRelay.create<Option<AutocompleteQuery>>()
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
@ -59,11 +62,15 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeUsersQuery()
|
observeUsersQuery()
|
||||||
|
observeRoomsQuery()
|
||||||
|
observeGroupsQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: TextComposerAction) {
|
override fun handle(action: TextComposerAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is TextComposerAction.QueryUsers -> handleQueryUsers(action)
|
is TextComposerAction.QueryUsers -> handleQueryUsers(action)
|
||||||
|
is TextComposerAction.QueryRooms -> handleQueryRooms(action)
|
||||||
|
is TextComposerAction.QueryGroups -> handleQueryGroups(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +79,18 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
usersQueryObservable.accept(query)
|
usersQueryObservable.accept(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleQueryRooms(action: TextComposerAction.QueryRooms) {
|
||||||
|
val query = Option.fromNullable(action.query)
|
||||||
|
roomsQueryObservable.accept(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleQueryGroups(action: TextComposerAction.QueryGroups) {
|
||||||
|
val query = Option.fromNullable(action.query)
|
||||||
|
groupsQueryObservable.accept(query)
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeUsersQuery() {
|
private fun observeUsersQuery() {
|
||||||
Observable.combineLatest<List<String>, Option<AutocompleteUserQuery>, List<User>>(
|
Observable.combineLatest<List<String>, Option<AutocompleteQuery>, List<User>>(
|
||||||
room.rx().liveRoomMemberIds(),
|
room.rx().liveRoomMemberIds(),
|
||||||
usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||||
BiFunction { roomMemberIds, query ->
|
BiFunction { roomMemberIds, query ->
|
||||||
|
@ -84,9 +101,10 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
users
|
users
|
||||||
} else {
|
} else {
|
||||||
users.filter {
|
users.filter {
|
||||||
it.displayName?.startsWith(prefix = filter, ignoreCase = true) ?: false
|
it.displayName?.contains(filter, ignoreCase = true) ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sortedBy { it.displayName }
|
||||||
}
|
}
|
||||||
).execute { async ->
|
).execute { async ->
|
||||||
copy(
|
copy(
|
||||||
|
@ -94,4 +112,47 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeRoomsQuery() {
|
||||||
|
Observable.combineLatest<List<RoomSummary>, Option<AutocompleteQuery>, List<RoomSummary>>(
|
||||||
|
session.rx().liveRoomSummaries(),
|
||||||
|
roomsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||||
|
BiFunction { roomSummaries, query ->
|
||||||
|
val filter = query.orNull() ?: ""
|
||||||
|
// Keep only room with a canonical alias
|
||||||
|
roomSummaries
|
||||||
|
.filter {
|
||||||
|
it.canonicalAlias?.contains(filter, ignoreCase = true) == true
|
||||||
|
}
|
||||||
|
.sortedBy { it.displayName }
|
||||||
|
}
|
||||||
|
).execute { async ->
|
||||||
|
copy(
|
||||||
|
asyncRooms = async
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeGroupsQuery() {
|
||||||
|
Observable.combineLatest<List<GroupSummary>, Option<AutocompleteQuery>, List<GroupSummary>>(
|
||||||
|
session.rx().liveGroupSummaries(),
|
||||||
|
groupsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||||
|
BiFunction { groupSummaries, query ->
|
||||||
|
val filter = query.orNull()
|
||||||
|
if (filter.isNullOrBlank()) {
|
||||||
|
groupSummaries
|
||||||
|
} else {
|
||||||
|
groupSummaries
|
||||||
|
.filter {
|
||||||
|
it.groupId.contains(filter, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedBy { it.displayName }
|
||||||
|
}
|
||||||
|
).execute { async ->
|
||||||
|
copy(
|
||||||
|
asyncGroups = async
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,15 @@ package im.vector.riotx.features.home.room.detail.composer
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
||||||
|
|
||||||
data class TextComposerViewState(val roomId: String,
|
data class TextComposerViewState(val roomId: String,
|
||||||
val asyncUsers: Async<List<User>> = Uninitialized
|
val asyncUsers: Async<List<User>> = Uninitialized,
|
||||||
|
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||||
|
val asyncGroups: Async<List<GroupSummary>> = Uninitialized
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomDetailArgs) : this(roomId = args.roomId)
|
constructor(args: RoomDetailArgs) : this(roomId = args.roomId)
|
||||||
|
|
|
@ -187,7 +187,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
EventType.STATE_ROOM_MEMBER,
|
EventType.STATE_ROOM_MEMBER,
|
||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_ALIASES,
|
||||||
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER -> {
|
EventType.CALL_ANSWER -> {
|
||||||
|
|
|
@ -45,8 +45,10 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
EventType.STATE_ROOM_MEMBER,
|
EventType.STATE_ROOM_MEMBER,
|
||||||
|
EventType.STATE_ROOM_ALIASES,
|
||||||
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
EventType.STATE_ROOM_JOIN_RULES,
|
EventType.STATE_ROOM_JOIN_RULES,
|
||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
|
|
|
@ -37,7 +37,9 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
|
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
|
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName())
|
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName())
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
|
@ -58,7 +60,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
|
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
|
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName)
|
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName)
|
||||||
EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName)
|
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName)
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER -> formatCallEvent(event, senderName)
|
EventType.CALL_ANSWER -> formatCallEvent(event, senderName)
|
||||||
|
@ -136,6 +138,34 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatRoomAliasesEvent(event: Event, senderName: String?): String? {
|
||||||
|
val eventContent: RoomAliasesContent? = event.getClearContent().toModel()
|
||||||
|
val prevEventContent: RoomAliasesContent? = event.unsignedData?.prevContent?.toModel()
|
||||||
|
|
||||||
|
val addedAliases = eventContent?.aliases.orEmpty() - prevEventContent?.aliases.orEmpty()
|
||||||
|
val removedAliases = prevEventContent?.aliases.orEmpty() - eventContent?.aliases.orEmpty()
|
||||||
|
|
||||||
|
return if (addedAliases.isNotEmpty() && removedAliases.isNotEmpty()) {
|
||||||
|
sp.getString(R.string.notice_room_aliases_added_and_removed, senderName, addedAliases.joinToString(), removedAliases.joinToString())
|
||||||
|
} else if (addedAliases.isNotEmpty()) {
|
||||||
|
sp.getQuantityString(R.plurals.notice_room_aliases_added, addedAliases.size, senderName, addedAliases.joinToString())
|
||||||
|
} else if (removedAliases.isNotEmpty()) {
|
||||||
|
sp.getQuantityString(R.plurals.notice_room_aliases_removed, removedAliases.size, senderName, removedAliases.joinToString())
|
||||||
|
} else {
|
||||||
|
Timber.w("Alias event without any change...")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? {
|
||||||
|
val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel()
|
||||||
|
val canonicalAlias = eventContent?.canonicalAlias
|
||||||
|
return canonicalAlias
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { sp.getString(R.string.notice_room_canonical_alias_set, senderName, it) }
|
||||||
|
?: sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String {
|
private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String {
|
||||||
val displayText = StringBuilder()
|
val displayText = StringBuilder()
|
||||||
// Check display name has been changed
|
// Check display name has been changed
|
||||||
|
|
|
@ -33,6 +33,7 @@ import me.gujun.android.span.span
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO Update this comment
|
||||||
* This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline
|
* This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline
|
||||||
*/
|
*/
|
||||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||||
|
|
|
@ -27,7 +27,9 @@ object TimelineDisplayableEvents {
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
EventType.STATE_ROOM_MEMBER,
|
EventType.STATE_ROOM_MEMBER,
|
||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_ALIASES,
|
||||||
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkData
|
import im.vector.matrix.android.api.permalinks.PermalinkData
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkParser
|
import im.vector.matrix.android.api.permalinks.PermalinkParser
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.glide.GlideRequests
|
import im.vector.riotx.core.glide.GlideRequests
|
||||||
|
@ -39,18 +40,41 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests,
|
||||||
val link = tag.attributes()["href"]
|
val link = tag.attributes()["href"]
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
val permalinkData = PermalinkParser.parse(link)
|
val permalinkData = PermalinkParser.parse(link)
|
||||||
when (permalinkData) {
|
val matrixItem = when (permalinkData) {
|
||||||
is PermalinkData.UserLink -> {
|
is PermalinkData.UserLink -> {
|
||||||
val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
|
val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
|
||||||
val span = PillImageSpan(glideRequests, avatarRenderer, context, MatrixItem.UserItem(permalinkData.userId, user?.displayName
|
MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl)
|
||||||
?: permalinkData.userId, user?.avatarUrl))
|
}
|
||||||
|
is PermalinkData.RoomLink -> {
|
||||||
|
if (permalinkData.eventId == null) {
|
||||||
|
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias)
|
||||||
|
if (permalinkData.isRoomAlias) {
|
||||||
|
MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
||||||
|
} else {
|
||||||
|
MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Exclude event link (used in reply events, we do not want to pill the "in reply to")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PermalinkData.GroupLink -> {
|
||||||
|
val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId)
|
||||||
|
MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrixItem == null) {
|
||||||
|
super.handle(visitor, renderer, tag)
|
||||||
|
} else {
|
||||||
|
val span = PillImageSpan(glideRequests, avatarRenderer, context, matrixItem)
|
||||||
SpannableBuilder.setSpans(
|
SpannableBuilder.setSpans(
|
||||||
visitor.builder(),
|
visitor.builder(),
|
||||||
span,
|
span,
|
||||||
tag.start(),
|
tag.start(),
|
||||||
tag.end()
|
tag.end()
|
||||||
)
|
)
|
||||||
// also add clickable span
|
|
||||||
SpannableBuilder.setSpans(
|
SpannableBuilder.setSpans(
|
||||||
visitor.builder(),
|
visitor.builder(),
|
||||||
URLSpan(link),
|
URLSpan(link),
|
||||||
|
@ -58,8 +82,6 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests,
|
||||||
tag.end()
|
tag.end()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> super.handle(visitor, renderer, tag)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
super.handle(visitor, renderer, tag)
|
super.handle(visitor, renderer, tag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import androidx.annotation.UiThread
|
||||||
import com.bumptech.glide.request.target.SimpleTarget
|
import com.bumptech.glide.request.target.SimpleTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.chip.ChipDrawable
|
import com.google.android.material.chip.ChipDrawable
|
||||||
import im.vector.matrix.android.api.session.room.send.UserMentionSpan
|
import im.vector.matrix.android.api.session.room.send.MatrixItemSpan
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.glide.GlideRequests
|
import im.vector.riotx.core.glide.GlideRequests
|
||||||
|
@ -38,13 +38,13 @@ import java.lang.ref.WeakReference
|
||||||
/**
|
/**
|
||||||
* This span is able to replace a text by a [ChipDrawable]
|
* This span is able to replace a text by a [ChipDrawable]
|
||||||
* It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached.
|
* It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached.
|
||||||
* Implements UserMentionSpan so that it could be automatically transformed in matrix links and displayed as pills.
|
* Implements MatrixItemSpan so that it could be automatically transformed in matrix links and displayed as pills.
|
||||||
*/
|
*/
|
||||||
class PillImageSpan(private val glideRequests: GlideRequests,
|
class PillImageSpan(private val glideRequests: GlideRequests,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
override val matrixItem: MatrixItem
|
override val matrixItem: MatrixItem
|
||||||
) : ReplacementSpan(), UserMentionSpan {
|
) : ReplacementSpan(), MatrixItemSpan {
|
||||||
|
|
||||||
private val pillDrawable = createChipDrawable()
|
private val pillDrawable = createChipDrawable()
|
||||||
private val target = PillImageSpanTarget(this)
|
private val target = PillImageSpanTarget(this)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/bottomSheetRecyclerView"
|
android:id="@+id/bottomSheetRecyclerView"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<?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:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?riotx_background"
|
android:background="?riotx_background"
|
||||||
android:padding="6dp">
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingBottom="6dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/commandName"
|
android:id="@+id/commandName"
|
||||||
|
@ -41,7 +44,7 @@
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:maxLines="1"
|
android:maxLines="2"
|
||||||
android:textColor="?riotx_text_secondary"
|
android:textColor="?riotx_text_secondary"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
tools:text="@string/command_description_invite_user" />
|
tools:text="@string/command_description_invite_user" />
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/matrixItemAutocompleteAvatar"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/matrixItemAutocompleteName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/matrixItemAutocompleteSubname"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="name"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -1,30 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?riotx_background"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="8dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/userAutocompleteAvatar"
|
|
||||||
android:layout_width="28dp"
|
|
||||||
android:layout_height="28dp"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/userAutocompleteName"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginLeft="12dp"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="?riotx_text_primary"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="name" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp">
|
android:layout_height="20dp">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="20dp">
|
android:layout_height="20dp">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?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"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp">
|
android:layout_height="80dp">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Reference in New Issue