Merge pull request #774 from vector-im/feature/breadcrumbs_fixes

Fix various UI issues
This commit is contained in:
Benoit Marty 2019-12-16 15:00:21 +01:00 committed by GitHub
commit f14f1db0e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 409 additions and 259 deletions

View File

@ -8,10 +8,12 @@ Improvements 🙌:
-
Other changes:
-
- Use same default room colors than Riot-Web
Bugfix 🐛:
-
- Scroll breadcrumbs to top when opened
- Render default room name when it starts with an emoji (#477)
- Do not display " (IRC)") in display names https://github.com/vector-im/riot-android/issues/444
Translations 🗣:
-

View File

@ -10,7 +10,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.google.gms:google-services:4.3.2'
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

View File

@ -16,11 +16,12 @@
package im.vector.matrix.android.api.session.room.send
import im.vector.matrix.android.api.util.MatrixItem
/**
* Tag class for spans that should mention a user.
* These Spans will be transformed into pills when detected in message to send
*/
interface UserMentionSpan {
val displayName: String
val userId: String
val matrixItem: MatrixItem
}

View File

@ -0,0 +1,141 @@
/*
* 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.matrix.android.api.util
import im.vector.matrix.android.BuildConfig
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.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.user.model.User
import java.util.*
sealed class MatrixItem(
open val id: String,
open val displayName: String?,
open val avatarUrl: String?
) {
data class UserItem(override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null)
: MatrixItem(id, displayName?.removeSuffix(ircPattern), avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
}
data class EventItem(override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null)
: MatrixItem(id, displayName, avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
}
data class RoomItem(override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null)
: MatrixItem(id, displayName, avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
}
data class RoomAliasItem(override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null)
: MatrixItem(id, displayName, avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
}
data class GroupItem(override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null)
: MatrixItem(id, displayName, avatarUrl) {
init {
if (BuildConfig.DEBUG) checkId()
}
}
fun getBestName(): String {
return displayName?.takeIf { it.isNotBlank() } ?: id
}
protected fun checkId() {
if (!id.startsWith(getIdPrefix())) {
error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}")
}
}
/**
* Return the prefix as defined in the matrix spec (and not extracted from the id)
*/
fun getIdPrefix() = when (this) {
is UserItem -> '@'
is EventItem -> '$'
is RoomItem -> '!'
is RoomAliasItem -> '#'
is GroupItem -> '+'
}
fun firstLetterOfDisplayName(): String {
return getBestName()
.let { dn ->
var startIndex = 0
val initial = dn[startIndex]
if (initial in listOf('@', '#', '+') && dn.length > 1) {
startIndex++
}
var length = 1
var first = dn[startIndex]
// LEFT-TO-RIGHT MARK
if (dn.length >= 2 && 0x200e == first.toInt()) {
startIndex++
first = dn[startIndex]
}
// check if its the start of a surrogate pair
if (first.toInt() in 0xD800..0xDBFF && dn.length > startIndex + 1) {
val second = dn[startIndex + 1]
if (second.toInt() in 0xDC00..0xDFFF) {
length++
}
}
dn.substring(startIndex, startIndex + length)
}
.toUpperCase(Locale.ROOT)
}
companion object {
private const val ircPattern = " (IRC)"
}
}
/* ==========================================================================================
* Extensions to create MatrixItem
* ========================================================================================== */
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)

View File

@ -65,7 +65,7 @@ internal class TextPillsUtils @Inject constructor(
// append text before pill
append(text, currIndex, start)
// append the pill
append(String.format(template, urlSpan.userId, urlSpan.displayName))
append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.displayName))
currIndex = end
}
// append text after the last pill

View File

@ -16,9 +16,7 @@
package im.vector.matrix.android.internal.util
import im.vector.matrix.android.api.MatrixPatterns
import timber.log.Timber
import java.util.Locale
/**
* Convert a string to an UTF8 String
@ -51,10 +49,3 @@ fun convertFromUTF8(s: String): String {
s
}
}
fun String?.firstLetterOfDisplayName(): String {
if (this.isNullOrEmpty()) return ""
val isUserId = MatrixPatterns.isUserId(this)
val firstLetterIndex = if (isUserId) 1 else 0
return this[firstLetterIndex].toString().toUpperCase(Locale.ROOT)
}

View File

@ -21,6 +21,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -37,11 +38,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var avatarUrl: String
@EpoxyAttribute
lateinit var senderId: String
@EpoxyAttribute
var senderName: String? = null
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
lateinit var body: CharSequence
@EpoxyAttribute
@ -50,8 +47,8 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
var movementMethod: MovementMethod? = null
override fun bind(holder: Holder) {
avatarRenderer.render(avatarUrl, senderId, senderName, holder.avatar)
holder.sender.setTextOrHide(senderName)
avatarRenderer.render(matrixItem, holder.avatar)
holder.sender.setTextOrHide(matrixItem.displayName)
holder.body.movementMethod = movementMethod
holder.body.text = body
body.findPillsAndProcess { it.bind(holder.body) }

View File

@ -21,6 +21,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -36,16 +37,12 @@ abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel<BottomSheetRoomPrev
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var avatarUrl: String
@EpoxyAttribute
lateinit var roomId: String
@EpoxyAttribute
var roomName: String? = null
lateinit var matrixItem: MatrixItem
@EpoxyAttribute var settingsClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
avatarRenderer.render(avatarUrl, roomId, roomName, holder.avatar)
holder.roomName.setTextOrHide(roomName)
avatarRenderer.render(matrixItem, holder.avatar)
holder.roomName.setTextOrHide(matrixItem.displayName)
holder.roomSettings.setOnClickListener(settingsClickListener)
}

View File

@ -23,6 +23,8 @@ import android.widget.ProgressBar
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.extensions.vectorComponent
import im.vector.riotx.features.home.AvatarRenderer
@ -59,9 +61,9 @@ open class UserAvatarPreference : Preference {
val session = mSession ?: return
val view = mAvatarView ?: return
session.getUser(session.myUserId)?.let {
avatarRenderer.render(it, view)
avatarRenderer.render(it.toMatrixItem(), view)
} ?: run {
avatarRenderer.render(null, session.myUserId, null, view)
avatarRenderer.render(MatrixItem.UserItem(session.myUserId), view)
}
}

View File

@ -26,6 +26,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.item.toMatrixItem
import kotlinx.android.synthetic.main.view_read_receipts.view.*
private const val MAX_RECEIPT_DISPLAYED = 5
@ -59,7 +60,7 @@ class ReadReceiptsView @JvmOverloads constructor(
receiptAvatars[index].visibility = View.INVISIBLE
} else {
receiptAvatars[index].visibility = View.VISIBLE
avatarRenderer.render(receiptData.avatarUrl, receiptData.userId, receiptData.displayName, receiptAvatars[index])
avatarRenderer.render(receiptData.toMatrixItem(), receiptAvatars[index])
}
}

View File

@ -18,11 +18,12 @@ package im.vector.riotx.features.autocomplete.user
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
class AutocompleteUserController @Inject constructor(): TypedEpoxyController<List<User>>() {
class AutocompleteUserController @Inject constructor() : TypedEpoxyController<List<User>>() {
var listener: AutocompleteClickListener<User>? = null
@ -35,9 +36,7 @@ class AutocompleteUserController @Inject constructor(): TypedEpoxyController<Lis
data.forEach { user ->
autocompleteUserItem {
id(user.userId)
userId(user.userId)
name(user.displayName)
avatarUrl(user.avatarUrl)
matrixItem(user.toMatrixItem())
avatarRenderer(avatarRenderer)
clickListener { _ ->
listener?.onItemClick(user)

View File

@ -21,6 +21,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -30,15 +31,13 @@ import im.vector.riotx.features.home.AvatarRenderer
abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute var name: String? = null
@EpoxyAttribute var userId: String = ""
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var clickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
holder.view.setOnClickListener(clickListener)
holder.nameView.text = name
avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
holder.nameView.text = matrixItem.getBestName()
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {

View File

@ -22,6 +22,8 @@ import androidx.lifecycle.Observer
import butterknife.BindView
import butterknife.OnClick
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer
@ -57,10 +59,10 @@ class SASVerificationIncomingFragment @Inject constructor(
otherDeviceTextView.text = viewModel.otherDeviceId
viewModel.otherUser?.let {
avatarRenderer.render(it, avatarImageView)
avatarRenderer.render(it.toMatrixItem(), avatarImageView)
} ?: run {
// Fallback to what we know
avatarRenderer.render(null, viewModel.otherUserId ?: "", viewModel.otherUserId, avatarImageView)
avatarRenderer.render(MatrixItem.UserItem(viewModel.otherUserId ?: "", viewModel.otherUserId), avatarImageView)
}
viewModel.transactionState.observe(viewLifecycleOwner, Observer {

View File

@ -27,10 +27,7 @@ import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.DrawableImageViewTarget
import com.bumptech.glide.request.target.Target
import im.vector.matrix.android.api.session.content.ContentUrlResolver
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.internal.util.firstLetterOfDisplayName
import im.vector.riotx.R
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.glide.GlideRequest
@ -45,76 +42,42 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
companion object {
private const val THUMBNAIL_SIZE = 250
private val AVATAR_COLOR_LIST = listOf(
R.color.riotx_avatar_fill_1,
R.color.riotx_avatar_fill_2,
R.color.riotx_avatar_fill_3
)
}
@UiThread
fun render(roomSummary: RoomSummary, imageView: ImageView) {
render(roomSummary.avatarUrl, roomSummary.roomId, roomSummary.displayName, imageView)
}
@UiThread
fun render(user: User, imageView: ImageView) {
render(imageView.context, GlideApp.with(imageView), user.avatarUrl, user.userId, user.displayName, DrawableImageViewTarget(imageView))
}
@UiThread
fun render(avatarUrl: String?, identifier: String, name: String?, imageView: ImageView) {
render(imageView.context, GlideApp.with(imageView), avatarUrl, identifier, name, DrawableImageViewTarget(imageView))
fun render(matrixItem: MatrixItem, imageView: ImageView) {
render(imageView.context,
GlideApp.with(imageView),
matrixItem,
DrawableImageViewTarget(imageView))
}
@UiThread
fun render(context: Context,
glideRequest: GlideRequests,
avatarUrl: String?,
identifier: String,
name: String?,
matrixItem: MatrixItem,
target: Target<Drawable>) {
val displayName = if (name.isNullOrBlank()) {
identifier
} else {
name
}
val placeholder = getPlaceholderDrawable(context, identifier, displayName)
buildGlideRequest(glideRequest, avatarUrl)
val placeholder = getPlaceholderDrawable(context, matrixItem)
buildGlideRequest(glideRequest, matrixItem.avatarUrl)
.placeholder(placeholder)
.into(target)
}
@AnyThread
fun getPlaceholderDrawable(context: Context, identifier: String, text: String): Drawable {
val avatarColor = ContextCompat.getColor(context, getColorFromUserId(identifier))
return if (text.isEmpty()) {
TextDrawable.builder().buildRound("", avatarColor)
} else {
val firstLetter = text.firstLetterOfDisplayName()
TextDrawable.builder()
.beginConfig()
.bold()
.endConfig()
.buildRound(firstLetter, avatarColor)
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
val avatarColor = when (matrixItem) {
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
}
return TextDrawable.builder()
.beginConfig()
.bold()
.endConfig()
.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
}
// PRIVATE API *********************************************************************************
// private fun getAvatarColor(text: String? = null): Int {
// var colorIndex: Long = 0
// if (!text.isNullOrEmpty()) {
// var sum: Long = 0
// for (i in 0 until text.length) {
// sum += text[i].toLong()
// }
// colorIndex = sum % AVATAR_COLOR_LIST.size
// }
// return AVATAR_COLOR_LIST[colorIndex.toInt()]
// }
private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver()
.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)

View File

@ -27,6 +27,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.extensions.commitTransactionNow
import im.vector.riotx.core.platform.ToolbarConfigurable
@ -74,12 +75,7 @@ class HomeDetailFragment @Inject constructor(
private fun onGroupChange(groupSummary: GroupSummary?) {
groupSummary?.let {
avatarRenderer.render(
it.avatarUrl,
it.groupId,
it.displayName,
groupToolbarAvatarImageView
)
avatarRenderer.render(it.toMatrixItem(), groupToolbarAvatarImageView)
}
}
@ -155,7 +151,7 @@ class HomeDetailFragment @Inject constructor(
bottomNavigationView.selectedItemId = when (displayMode) {
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
else -> R.id.bottom_action_home
else -> R.id.bottom_action_home
}
}

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.home
import android.os.Bundle
import android.view.View
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.extensions.observeK
import im.vector.riotx.core.extensions.replaceChildFragment
@ -42,7 +43,7 @@ class HomeDrawerFragment @Inject constructor(
session.liveUser(session.myUserId).observeK(this) { optionalUser ->
val user = optionalUser?.getOrNull()
if (user != null) {
avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
avatarRenderer.render(user.toMatrixItem(), homeDrawerHeaderAvatarView)
homeDrawerUsernameView.text = user.displayName
homeDrawerUserIdView.text = user.userId
}

View File

@ -0,0 +1,29 @@
/*
* 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.home
import androidx.annotation.ColorRes
import im.vector.riotx.R
@ColorRes
fun getColorFromRoomId(roomId: String?): Int {
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) {
1 -> R.color.riotx_avatar_fill_2
2 -> R.color.riotx_avatar_fill_3
else -> R.color.riotx_avatar_fill_1
}
}

View File

@ -22,28 +22,18 @@ import kotlin.math.abs
@ColorRes
fun getColorFromUserId(userId: String?): Int {
if (userId.isNullOrBlank()) {
return R.color.riotx_username_1
}
var hash = 0
var i = 0
var chr: Char
while (i < userId.length) {
chr = userId[i]
hash = (hash shl 5) - hash + chr.toInt()
i++
}
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
return when (abs(hash) % 8 + 1) {
1 -> R.color.riotx_username_1
2 -> R.color.riotx_username_2
3 -> R.color.riotx_username_3
4 -> R.color.riotx_username_4
5 -> R.color.riotx_username_5
6 -> R.color.riotx_username_6
7 -> R.color.riotx_username_7
else -> R.color.riotx_username_8
return when (abs(hash) % 8) {
1 -> R.color.riotx_username_2
2 -> R.color.riotx_username_3
3 -> R.color.riotx_username_4
4 -> R.color.riotx_username_5
5 -> R.color.riotx_username_6
6 -> R.color.riotx_username_7
7 -> R.color.riotx_username_8
else -> R.color.riotx_username_1
}
}

View File

@ -25,6 +25,7 @@ import androidx.core.content.ContextCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.amulyakhare.textdrawable.TextDrawable
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -34,22 +35,20 @@ import im.vector.riotx.features.home.AvatarRenderer
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute var name: String? = null
@EpoxyAttribute var userId: String = ""
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var clickListener: View.OnClickListener? = null
@EpoxyAttribute var selected: Boolean = false
override fun bind(holder: Holder) {
holder.view.setOnClickListener(clickListener)
// If name is empty, use userId as name and force it being centered
if (name.isNullOrEmpty()) {
if (matrixItem.displayName.isNullOrEmpty()) {
holder.userIdView.visibility = View.GONE
holder.nameView.text = userId
holder.nameView.text = matrixItem.id
} else {
holder.userIdView.visibility = View.VISIBLE
holder.nameView.text = name
holder.userIdView.text = userId
holder.nameView.text = matrixItem.displayName
holder.userIdView.text = matrixItem.id
}
renderSelection(holder, selected)
}
@ -62,7 +61,7 @@ abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserI
holder.avatarImageView.setImageDrawable(backgroundDrawable)
} else {
holder.avatarCheckedImageView.visibility = View.GONE
avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
}

View File

@ -30,7 +30,7 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
@ -142,7 +142,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
session.rx()
.searchUsersDirectory(search, 50, emptySet())
.map { users ->
users.sortedBy { it.displayName.firstLetterOfDisplayName() }
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
}
}
stream.toAsync {

View File

@ -19,9 +19,13 @@
package im.vector.riotx.features.home.createdirect
import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.*
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.errorWithRetryItem
import im.vector.riotx.core.epoxy.loadingItem
@ -94,9 +98,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
createDirectRoomUserItem {
id(user.userId)
selected(isSelected)
userId(user.userId)
name(user.displayName)
avatarUrl(user.avatarUrl)
matrixItem(user.toMatrixItem())
avatarRenderer(avatarRenderer)
clickListener { _ ->
callback?.onItemClick(user)

View File

@ -23,7 +23,7 @@ import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.EmptyItem_
import im.vector.riotx.core.epoxy.loadingItem
@ -68,9 +68,7 @@ class KnownUsersController @Inject constructor(private val session: Session,
CreateDirectRoomUserItem_()
.id(item.userId)
.selected(isSelected)
.userId(item.userId)
.name(item.displayName)
.avatarUrl(item.avatarUrl)
.matrixItem(item.toMatrixItem())
.avatarRenderer(avatarRenderer)
.clickListener { _ ->
callback?.onItemClick(item)
@ -87,8 +85,8 @@ class KnownUsersController @Inject constructor(private val session: Session,
var lastFirstLetter: String? = null
for (model in models) {
if (model is CreateDirectRoomUserItem) {
if (model.userId == session.myUserId) continue
val currentFirstLetter = model.name.firstLetterOfDisplayName()
if (model.matrixItem.id == session.myUserId) continue
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
lastFirstLetter = currentFirstLetter

View File

@ -36,7 +36,7 @@ import im.vector.riotx.core.utils.LiveEvent
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID"
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
class GroupListViewModel @AssistedInject constructor(@Assisted initialState: GroupListViewState,
private val selectedGroupStore: SelectedGroupDataSource,

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.group
import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
@ -49,10 +50,8 @@ class GroupSummaryController @Inject constructor(private val avatarRenderer: Ava
groupSummaryItem {
avatarRenderer(avatarRenderer)
id(groupSummary.groupId)
groupId(groupSummary.groupId)
groupName(groupSummary.displayName)
matrixItem(groupSummary.toMatrixItem())
selected(isSelected)
avatarUrl(groupSummary.avatarUrl)
listener { callback?.onGroupSelected(groupSummary) }
}
}

View File

@ -20,6 +20,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -30,18 +31,16 @@ import im.vector.riotx.features.home.AvatarRenderer
abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var groupName: CharSequence
@EpoxyAttribute lateinit var groupId: String
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = groupName
holder.groupNameView.text = matrixItem.displayName
holder.rootView.isChecked = selected
avatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.breadcrumbs
import android.view.View
import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
@ -52,9 +53,7 @@ class BreadcrumbsController @Inject constructor(
breadcrumbsItem {
id(it.roomId)
avatarRenderer(avatarRenderer)
roomId(it.roomId)
roomName(it.displayName)
avatarUrl(it.avatarUrl)
matrixItem(it.toMatrixItem())
unreadNotificationCount(it.notificationCount)
showHighlighted(it.highlightCount > 0)
hasUnreadMessage(it.hasUnreadMessages)

View File

@ -67,4 +67,8 @@ class BreadcrumbsFragment @Inject constructor(
override fun onBreadcrumbClicked(roomId: String) {
sharedActionViewModel.post(RoomDetailSharedAction.SwitchToRoom(roomId))
}
fun scrollToTop() {
breadcrumbsRecyclerView.scrollToPosition(0)
}
}

View File

@ -22,6 +22,7 @@ import android.widget.ImageView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -32,9 +33,7 @@ import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@ -45,7 +44,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
super.bind(holder)
holder.rootView.setOnClickListener(itemClickListener)
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.draftIndentIndicator.isVisible = hasDraft
}

View File

@ -86,9 +86,19 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
override fun onDrawerStateChanged(newState: Int) {
hideKeyboard()
if (!drawerLayout.isDrawerOpen(GravityCompat.START) && newState == DrawerLayout.STATE_DRAGGING) {
// User is starting to open the drawer, scroll the list to op
scrollBreadcrumbsToTop()
}
}
}
private fun scrollBreadcrumbsToTop() {
supportFragmentManager.fragments.filterIsInstance<BreadcrumbsFragment>()
.forEach { it.scrollToTop() }
}
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)

View File

@ -66,6 +66,8 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
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.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.dialogs.withColoredButton
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
@ -408,9 +410,7 @@ class RoomDetailFragment @Inject constructor(
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
avatarRenderer.render(
event.senderAvatar,
event.root.senderId ?: "",
event.getDisambiguatedDisplayName(),
MatrixItem.UserItem(event.root.senderId ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
composerLayout.composerRelatedMessageAvatar
)
composerLayout.expand {
@ -599,20 +599,19 @@ class RoomDetailFragment @Inject constructor(
}
// Replace the word by its completion
val displayName = item.displayName ?: item.userId
val matrixItem = item.toMatrixItem()
val displayName = matrixItem.getBestName()
// with a trailing space
editable.replace(startIndex, endIndex, "$displayName ")
// Add the span
val user = session.getUser(item.userId)
val span = PillImageSpan(
glideRequests,
avatarRenderer,
requireContext(),
item.userId,
user?.displayName ?: item.userId,
user?.avatarUrl)
matrixItem
)
span.bind(composerLayout.composerEditText)
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
@ -684,7 +683,7 @@ class RoomDetailFragment @Inject constructor(
inviteView.visibility = View.GONE
val uid = session.myUserId
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)
avatarRenderer.render(MatrixItem.UserItem(uid, meMember?.displayName, meMember?.avatarUrl), composerLayout.composerAvatarImageView)
} else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
@ -711,7 +710,7 @@ class RoomDetailFragment @Inject constructor(
activity?.finish()
} else {
roomToolbarTitleView.text = it.displayName
avatarRenderer.render(it, roomToolbarAvatarImageView)
avatarRenderer.render(it.toMatrixItem(), roomToolbarAvatarImageView)
roomToolbarSubtitleView.setTextOrHide(it.topic)
}
jumpToBottomView.count = it.notificationCount
@ -1195,9 +1194,8 @@ class RoomDetailFragment @Inject constructor(
glideRequests,
avatarRenderer,
requireContext(),
userId,
displayName,
roomMember?.avatarUrl)
MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl)
)
.also { it.bind(composerLayout.composerEditText) },
0,
displayName.length,

View File

@ -22,6 +22,7 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.features.home.AvatarRenderer
@ -29,15 +30,13 @@ import im.vector.riotx.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_display_read_receipt)
abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptItem.Holder>() {
@EpoxyAttribute var name: String? = null
@EpoxyAttribute var userId: String = ""
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var timestamp: CharSequence? = null
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
override fun bind(holder: Holder) {
avatarRenderer.render(avatarUrl, userId, name, holder.avatarView)
holder.displayNameView.text = name ?: userId
avatarRenderer.render(matrixItem, holder.avatarView)
holder.displayNameView.text = matrixItem.getBestName()
timestamp?.let {
holder.timestampView.text = it
holder.timestampView.isVisible = true

View File

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.item.toMatrixItem
import javax.inject.Inject
/**
@ -36,9 +37,7 @@ class DisplayReadReceiptsController @Inject constructor(private val dateFormatte
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
DisplayReadReceiptItem_()
.id(it.userId)
.userId(it.userId)
.avatarUrl(it.avatarUrl)
.name(it.displayName)
.matrixItem(it.toMatrixItem())
.avatarRenderer(avatarRender)
.timestamp(timestamp)
.addIf(session.myUserId != it.userId, this)

View File

@ -44,9 +44,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
bottomSheetMessagePreviewItem {
id("preview")
avatarRenderer(avatarRenderer)
avatarUrl(state.informationData.avatarUrl ?: "")
senderId(state.informationData.senderId)
senderName(state.senderName())
matrixItem(state.informationData.matrixItem)
movementMethod(createLinkMovementMethod(listener))
body(body.linkify(listener))
time(state.time())

View File

@ -60,7 +60,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
val avatarUrl = event.senderAvatar
val memberName = event.getDisambiguatedDisplayName()
val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: ""))
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId))
}
return MessageInformationData(

View File

@ -77,12 +77,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
holder.timeView.visibility = View.VISIBLE
holder.timeView.text = attributes.informationData.time
holder.memberNameView.text = attributes.informationData.memberName
attributes.avatarRenderer.render(
attributes.informationData.avatarUrl,
attributes.informationData.senderId,
attributes.informationData.memberName?.toString(),
holder.avatarImageView
)
attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener)
holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
} else {

View File

@ -24,6 +24,7 @@ import androidx.core.view.children
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@ -54,7 +55,7 @@ abstract class MergedHeaderItem : BaseEventItem<MergedHeaderItem.Holder>() {
val data = distinctMergeData.getOrNull(index)
if (data != null && view is ImageView) {
view.visibility = View.VISIBLE
attributes.avatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view)
attributes.avatarRenderer.render(data.toMatrixItem(), view)
} else {
view.visibility = View.GONE
}
@ -87,6 +88,8 @@ abstract class MergedHeaderItem : BaseEventItem<MergedHeaderItem.Holder>() {
val avatarUrl: String?
)
fun Data.toMatrixItem() = MatrixItem.UserItem(userId, memberName, avatarUrl)
data class Attributes(
val isCollapsed: Boolean,
val mergeData: List<Data>,

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
import android.os.Parcelable
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.util.MatrixItem
import kotlinx.android.parcel.Parcelize
@Parcelize
@ -34,7 +35,11 @@ data class MessageInformationData(
val hasBeenEdited: Boolean = false,
val hasPendingEdits: Boolean = false,
val readReceipts: List<ReadReceiptData> = emptyList()
) : Parcelable
) : Parcelable {
val matrixItem: MatrixItem
get() = MatrixItem.UserItem(senderId, memberName?.toString(), avatarUrl)
}
@Parcelize
data class ReactionInfoData(
@ -51,3 +56,5 @@ data class ReadReceiptData(
val displayName: String?,
val timestamp: Long
) : Parcelable
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)

View File

@ -39,13 +39,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
override fun bind(holder: Holder) {
super.bind(holder)
holder.noticeTextView.text = attributes.noticeText
attributes.avatarRenderer.render(
attributes.informationData.avatarUrl,
attributes.informationData.senderId,
attributes.informationData.memberName?.toString()
?: attributes.informationData.senderId,
holder.avatarImageView
)
attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
}

View File

@ -22,6 +22,7 @@ import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -33,10 +34,8 @@ import im.vector.riotx.features.home.AvatarRenderer
abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var secondLine: CharSequence? = null
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var listener: (() -> Unit)? = null
@EpoxyAttribute var invitationAcceptInProgress: Boolean = false
@EpoxyAttribute var invitationAcceptInError: Boolean = false
@ -85,9 +84,9 @@ abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>(
rejectListener?.invoke()
}
}
holder.titleView.text = roomName
holder.titleView.text = matrixItem.getBestName()
holder.subtitleView.setTextOrHide(secondLine)
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {

View File

@ -23,6 +23,7 @@ import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -32,11 +33,9 @@ import im.vector.riotx.features.home.AvatarRenderer
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute lateinit var lastFormattedEvent: CharSequence
@EpoxyAttribute lateinit var lastEventTime: CharSequence
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false
@ -48,13 +47,13 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
super.bind(holder)
holder.rootView.setOnClickListener(itemClickListener)
holder.rootView.setOnLongClickListener(itemLongClickListener)
holder.titleView.text = roomName
holder.titleView.text = matrixItem.getBestName()
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
holder.draftView.isVisible = hasDraft
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {

View File

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
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.timeline.getLastMessageContent
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -69,7 +70,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
return RoomInvitationItem_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
.roomId(roomSummary.roomId)
.matrixItem(roomSummary.toMatrixItem())
.secondLine(secondLine)
.invitationAcceptInProgress(joiningRoomsIds.contains(roomSummary.roomId))
.invitationAcceptInError(joiningErrorRoomsIds.contains(roomSummary.roomId))
@ -77,8 +78,6 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
.invitationRejectInError(rejectingErrorRoomsIds.contains(roomSummary.roomId))
.acceptListener { listener?.onAcceptRoomInvitation(roomSummary) }
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
.roomName(roomSummary.displayName)
.avatarUrl(roomSummary.avatarUrl)
.listener { listener?.onRoomClicked(roomSummary) }
}
@ -125,11 +124,9 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
return RoomSummaryItem_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
.roomId(roomSummary.roomId)
.matrixItem(roomSummary.toMatrixItem())
.lastEventTime(latestEventTime)
.lastFormattedEvent(latestFormattedEvent)
.roomName(roomSummary.displayName)
.avatarUrl(roomSummary.avatarUrl)
.showHighlighted(showHighlighted)
.unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.list.actions
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetActionItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetSeparatorItem
@ -39,9 +40,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
bottomSheetRoomPreviewItem {
id("preview")
avatarRenderer(avatarRenderer)
roomName(roomSummary.displayName)
avatarUrl(roomSummary.avatarUrl)
roomId(roomSummary.roomId)
matrixItem(roomSummary.toMatrixItem())
settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) })
}

View File

@ -20,6 +20,7 @@ import android.content.Context
import android.text.style.URLSpan
import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.glide.GlideRequests
import im.vector.riotx.features.home.AvatarRenderer
@ -41,8 +42,8 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests,
when (permalinkData) {
is PermalinkData.UserLink -> {
val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
val span = PillImageSpan(glideRequests, avatarRenderer, context, permalinkData.userId, user?.displayName
?: permalinkData.userId, user?.avatarUrl)
val span = PillImageSpan(glideRequests, avatarRenderer, context, MatrixItem.UserItem(permalinkData.userId, user?.displayName
?: permalinkData.userId, user?.avatarUrl))
SpannableBuilder.setSpans(
visitor.builder(),
span,

View File

@ -29,6 +29,7 @@ import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.chip.ChipDrawable
import im.vector.matrix.android.api.session.room.send.UserMentionSpan
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.glide.GlideRequests
import im.vector.riotx.features.home.AvatarRenderer
@ -42,9 +43,8 @@ import java.lang.ref.WeakReference
class PillImageSpan(private val glideRequests: GlideRequests,
private val avatarRenderer: AvatarRenderer,
private val context: Context,
override val userId: String,
override val displayName: String,
private val avatarUrl: String?) : ReplacementSpan(), UserMentionSpan {
override val matrixItem: MatrixItem
) : ReplacementSpan(), UserMentionSpan {
private val pillDrawable = createChipDrawable()
private val target = PillImageSpanTarget(this)
@ -53,7 +53,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
@UiThread
fun bind(textView: TextView) {
tv = WeakReference(textView)
avatarRenderer.render(context, glideRequests, avatarUrl, userId, displayName, target)
avatarRenderer.render(context, glideRequests, matrixItem, target)
}
// ReplacementSpan *****************************************************************************
@ -101,12 +101,12 @@ class PillImageSpan(private val glideRequests: GlideRequests,
private fun createChipDrawable(): ChipDrawable {
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
text = displayName
text = matrixItem.getBestName()
textEndPadding = textPadding
textStartPadding = textPadding
setChipMinHeightResource(R.dimen.pill_min_height)
setChipIconSizeResource(R.dimen.pill_avatar_size)
chipIcon = avatarRenderer.getPlaceholderDrawable(context, userId, displayName)
chipIcon = avatarRenderer.getPlaceholderDrawable(context, matrixItem)
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
}
}

View File

@ -22,6 +22,7 @@ import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.features.home.AvatarRenderer
@ -56,7 +57,7 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
fun render(sender: User, mode: Mode = Mode.LARGE) {
if (mode == Mode.LARGE) {
updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT }
avatarRenderer.render(sender.avatarUrl, sender.userId, sender.displayName, inviteAvatarView)
avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView)
inviteIdentifierView.text = sender.userId
inviteNameView.text = sender.displayName
inviteLabelView.text = context.getString(R.string.send_you_invite)

View File

@ -21,6 +21,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -35,13 +36,7 @@ abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var avatarUrl: String? = null
@EpoxyAttribute
var roomId: String? = null
@EpoxyAttribute
var roomName: String? = null
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
var roomAlias: String? = null
@ -64,8 +59,8 @@ abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {
override fun bind(holder: Holder) {
holder.rootView.setOnClickListener { globalListener?.invoke() }
avatarRenderer.render(avatarUrl, roomId!!, roomName, holder.avatarView)
holder.nameView.text = roomName
avatarRenderer.render(matrixItem, holder.avatarView)
holder.nameView.text = matrixItem.displayName
holder.aliasView.setTextOrHide(roomAlias)
holder.topicView.setTextOrHide(roomTopic)
// TODO Use formatter for big numbers?

View File

@ -22,6 +22,7 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.errorWithRetryItem
import im.vector.riotx.core.epoxy.loadingItem
@ -83,9 +84,7 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri
publicRoomItem {
avatarRenderer(avatarRenderer)
id(publicRoom.roomId)
roomId(publicRoom.roomId)
avatarUrl(publicRoom.avatarUrl)
roomName(publicRoom.name)
matrixItem(publicRoom.toMatrixItem())
roomAlias(publicRoom.canonicalAlias)
roomTopic(publicRoom.topic)
nbOfMembers(publicRoom.numJoinedMembers)

View File

@ -21,6 +21,7 @@ import android.content.Intent
import android.os.Parcelable
import androidx.appcompat.widget.Toolbar
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.platform.ToolbarConfigurable
@ -34,7 +35,10 @@ data class RoomPreviewData(
val topic: String?,
val worldReadable: Boolean,
val avatarUrl: String?
) : Parcelable
) : Parcelable {
val matrixItem: MatrixItem
get() = MatrixItem.RoomItem(roomId, roomName, avatarUrl)
}
class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {

View File

@ -49,11 +49,11 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
setupToolbar(roomPreviewNoPreviewToolbar)
// Toolbar
avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewToolbarAvatar)
avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewToolbarAvatar)
roomPreviewNoPreviewToolbarTitle.text = roomPreviewData.roomName
// Screen
avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewAvatar)
avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewAvatar)
roomPreviewNoPreviewName.text = roomPreviewData.roomName
roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic)

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.settings.ignored
import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.resources.StringProvider
@ -44,19 +45,19 @@ class IgnoredUsersController @Inject constructor(private val stringProvider: Str
buildIgnoredUserModels(nonNullViewState.ignoredUsers)
}
private fun buildIgnoredUserModels(userIds: List<User>) {
if (userIds.isEmpty()) {
private fun buildIgnoredUserModels(users: List<User>) {
if (users.isEmpty()) {
noResultItem {
id("empty")
text(stringProvider.getString(R.string.no_ignored_users))
}
} else {
userIds.forEach { userId ->
users.forEach { user ->
userItem {
id(userId.userId)
id(user.userId)
avatarRenderer(avatarRenderer)
user(userId)
itemClickAction { callback?.onUserIdClicked(userId.userId) }
matrixItem(user.toMatrixItem())
itemClickAction { callback?.onUserIdClicked(user.userId) }
}
}
}

View File

@ -20,7 +20,7 @@ import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@ -37,7 +37,7 @@ abstract class UserItem : VectorEpoxyModel<UserItem.Holder>() {
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var user: User
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
var itemClickAction: (() -> Unit)? = null
@ -45,9 +45,9 @@ abstract class UserItem : VectorEpoxyModel<UserItem.Holder>() {
override fun bind(holder: Holder) {
holder.root.setOnClickListener { itemClickAction?.invoke() }
avatarRenderer.render(user, holder.avatarImage)
holder.userIdText.setTextOrHide(user.userId)
holder.displayNameText.setTextOrHide(user.displayName)
avatarRenderer.render(matrixItem, holder.avatarImage)
holder.userIdText.setTextOrHide(matrixItem.id)
holder.displayNameText.setTextOrHide(matrixItem.displayName)
}
class Holder : VectorEpoxyHolder() {

View File

@ -40,10 +40,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minWidth="16dp"
android:minHeight="16dp"
android:minWidth="18dp"
android:minHeight="18dp"
android:textColor="@android:color/white"
android:textSize="10sp"
android:textSize="11sp"
android:visibility="gone"
app:layout_constraintCircle="@+id/breadcrumbsImageView"
app:layout_constraintCircleAngle="45"

View File

@ -0,0 +1,42 @@
/*
* 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.home
import im.vector.riotx.R
import org.junit.Assert.assertEquals
import org.junit.Test
class UserColorTest {
@Test
fun testNull() {
assertEquals(R.color.riotx_username_1, getColorFromUserId(null))
}
@Test
fun testEmpty() {
assertEquals(R.color.riotx_username_1, getColorFromUserId(""))
}
@Test
fun testName() {
assertEquals(R.color.riotx_username_1, getColorFromUserId("@ganfra:matrix.org"))
assertEquals(R.color.riotx_username_4, getColorFromUserId("@benoit0816:matrix.org"))
assertEquals(R.color.riotx_username_5, getColorFromUserId("@hubert:uhoreg.ca"))
assertEquals(R.color.riotx_username_7, getColorFromUserId("@nadonomy:matrix.org"))
}
}