Compare commits

..

13 Commits

Author SHA1 Message Date
982331e47c Add unread indent on room list 2019-08-28 16:31:29 +02:00
cbc08d834b Merge pull request #522 from vector-im/feature/fix_e2e_reply
Fix / regression on e2e reply and edit of reply
2019-08-28 10:38:22 +02:00
0ab6b33fb6 Merge branch 'develop' into feature/fix_e2e_reply 2019-08-28 10:38:12 +02:00
1b394527b6 cleaning + code review 2019-08-28 10:22:51 +02:00
a8f1388721 Merge pull request #520 from vector-im/feature/read_receipts_511
Improve read receipt design
2019-08-28 10:17:56 +02:00
166be4e289 Improve read receipt design 2019-08-28 09:56:10 +02:00
b49ccefe63 Merge pull request #521 from vector-im/feature/fix_dome_video_wont_play
Some video won't play
2019-08-28 03:43:35 -04:00
825760d17e Fix / regression on e2e reply and edit of reply 2019-08-27 17:05:04 +02:00
b5af62c3ea Some video won't play
VideoView fails to play some remote uri video on some device. For now video is downloaded locally in internal cache then played. This offers basic support before full media preview implementation
2019-08-27 16:50:02 +02:00
a51d96bf00 Merge pull request #325 from vector-im/feature/non_unicode_reaction
Accept non unicode reactions
2019-08-27 08:10:51 -04:00
7e142d201d Use EmojiCompat to build EmojiSpans from text 2019-08-27 11:06:52 +02:00
2be6058971 accept non unicode reactions 2019-08-27 10:58:21 +02:00
49d73f360e Merge pull request #494 from vector-im/feature/fix_441
Fix text diff removed linebreak
2019-08-27 04:36:03 -04:00
42 changed files with 237 additions and 142 deletions

View File

@ -5,15 +5,17 @@ Features:
- Display read receipts in timeline (#81) - Display read receipts in timeline (#81)
Improvements: Improvements:
- - Reactions: Reinstate the ability to react with non-unicode keys (#307)
Other changes: Other changes:
- -
Bugfix: Bugfix:
- Fix text diff linebreak display (#441) - Fix text diff linebreak display (#441)
- Date change message repeats for each redaction until a normal message (#358) - Date change message repeats for each redaction until a normal message (#358)
- Slide-in reply icon is distorted (#423) - Slide-in reply icon is distorted (#423)
- Regression / e2e replies not encrypted
- Some video won't play
Translations: Translations:
- -

View File

@ -29,10 +29,11 @@ data class RoomSummary(
val topic: String = "", val topic: String = "",
val avatarUrl: String = "", val avatarUrl: String = "",
val isDirect: Boolean = false, val isDirect: Boolean = false,
val latestEvent: TimelineEvent? = null, val latestPreviewableEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(), val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0, val notificationCount: Int = 0,
val highlightCount: Int = 0, val highlightCount: Int = 0,
val hasUnreadMessages: Boolean = false,
val tags: List<RoomTag> = emptyList(), val tags: List<RoomTag> = emptyList(),
val membership: Membership = Membership.NONE, val membership: Membership = Membership.NONE,
val versioningState: VersioningState = VersioningState.NONE val versioningState: VersioningState = VersioningState.NONE

View File

@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import java.util.* import java.util.*
@ -30,12 +31,12 @@ internal class RoomSummaryMapper @Inject constructor(
val timelineEventMapper: TimelineEventMapper val timelineEventMapper: TimelineEventMapper
) { ) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { fun map(roomSummaryEntity: RoomSummaryEntity, readService: ReadService?): RoomSummary {
val tags = roomSummaryEntity.tags.map { val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder) RoomTag(it.tagName, it.tagOrder)
} }
val latestEvent = roomSummaryEntity.latestEvent?.let { val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it) timelineEventMapper.map(it)
} }
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) { if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
@ -43,26 +44,30 @@ internal class RoomSummaryMapper @Inject constructor(
//for now decrypt sync //for now decrypt sync
try { try {
val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString()) val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString())
latestEvent.root.mxDecryptionResult = OlmDecryptionResult( latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,
senderKey = result.senderCurve25519Key, senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
) )
} catch (e: MXCryptoError) { } catch (e: MXCryptoError) {
} }
} }
val hasUnreadMessages = roomSummaryEntity.notificationCount > 0
//avoid this call if we are sure there are unread events
|| latestEvent?.root?.eventId?.let { readService?.isEventRead(it)?.not() } ?: false
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,
displayName = roomSummaryEntity.displayName ?: "", displayName = roomSummaryEntity.displayName ?: "",
topic = roomSummaryEntity.topic ?: "", topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect, isDirect = roomSummaryEntity.isDirect,
latestEvent = latestEvent, latestPreviewableEvent = latestEvent,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount, highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount, notificationCount = roomSummaryEntity.notificationCount,
hasUnreadMessages = hasUnreadMessages,
tags = tags, tags = tags,
membership = roomSummaryEntity.membership, membership = roomSummaryEntity.membership,
versioningState = roomSummaryEntity.versioningState versioningState = roomSummaryEntity.versioningState

View File

@ -26,7 +26,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "", var displayName: String? = "",
var avatarUrl: String? = "", var avatarUrl: String? = "",
var topic: String? = "", var topic: String? = "",
var latestEvent: TimelineEventEntity? = null, var latestPreviewableEvent: TimelineEventEntity? = null,
var heroes: RealmList<String> = RealmList(), var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0, var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0,

View File

@ -58,7 +58,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
} }
return Transformations.map(liveRealmData) { results -> return Transformations.map(liveRealmData) { results ->
val roomSummaries = results.map { roomSummaryMapper.map(it) } val roomSummaries = results.map { roomSummaryMapper.map(it, this) }
if (roomSummaries.isEmpty()) { if (roomSummaries.isEmpty()) {
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache // Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
@ -72,7 +72,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
override fun roomSummary(): RoomSummary? { override fun roomSummary(): RoomSummary? {
return monarchy.fetchAllMappedSync( return monarchy.fetchAllMappedSync(
{ realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, { realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
{ roomSummaryMapper.map(it) } { roomSummaryMapper.map(it, this) }
).firstOrNull() ).firstOrNull()
} }

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.VersioningState
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper 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
@ -69,7 +70,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
}, },
{ roomSummaryMapper.map(it) } { roomSummaryMapper.map(it, getRoom(it.roomId)) }
) )
} }

View File

@ -85,7 +85,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomSummaryEntity.membership = membership roomSummaryEntity.membership = membership
} }
val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, includedTypes = PREVIEWABLE_TYPES) val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, includedTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val otherRoomMembers = RoomMembers(realm, roomId) val otherRoomMembers = RoomMembers(realm, roomId)
@ -98,7 +98,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.latestEvent = latestEvent roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)

View File

@ -69,15 +69,11 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
val sendRelationWork = createSendRelationWork(event) val sendRelationWork = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork) TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
return CancelableWork(context, sendRelationWork.id) return CancelableWork(context, sendRelationWork.id)
} }
private fun createSendRelationWork(event: Event): OneTimeWorkRequest {
return createSendEventWork(event)
}
override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ { override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ {
val params = FindReactionEventForUndoTask.Params( val params = FindReactionEventForUndoTask.Params(
@ -134,42 +130,42 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
return CancelableWork(context, encryptWork.id) CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id) CancelableWork(context, workRequest.id)
} }
} }
override fun editReply(replyToEdit: TimelineEvent, override fun editReply(replyToEdit: TimelineEvent,
originalEvent: TimelineEvent, originalTimelineEvent: TimelineEvent,
newBodyText: String, newBodyText: String,
compatibilityBodyText: String): Cancelable { compatibilityBodyText: String): Cancelable {
val event = eventFactory val event = eventFactory
.createReplaceTextOfReply(roomId, .createReplaceTextOfReply(roomId,
replyToEdit, replyToEdit,
originalEvent, originalTimelineEvent,
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText) newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
return CancelableWork(context, encryptWork.id) CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id) CancelableWork(context, workRequest.id)
} }
} }
@ -187,16 +183,16 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
saveLocalEcho(it) saveLocalEcho(it)
} ?: return null } ?: return null
if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
return CancelableWork(context, encryptWork.id) CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id) CancelableWork(context, workRequest.id)
} }
} }
@ -208,10 +204,10 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true) return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
} }
private fun createSendEventWork(event: Event): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, true) return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
} }
override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> { override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
@ -220,7 +216,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
} }
return Transformations.map(liveEntity) { realmResults -> return Transformations.map(liveEntity) { realmResults ->
realmResults.firstOrNull()?.asDomain() realmResults.firstOrNull()?.asDomain()
?: EventAnnotationsSummary(eventId, emptyList(), null) ?: EventAnnotationsSummary(eventId, emptyList(), null)
} }
} }
@ -233,7 +229,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
private fun saveLocalEcho(event: Event) { private fun saveLocalEcho(event: Event) {
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
?: return@writeAsync ?: return@writeAsync
roomEntity.addSendingEvent(event) roomEntity.addSendingEvent(event)
} }
} }

View File

@ -113,12 +113,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private fun handleJoinedRoom(realm: Realm, private fun handleJoinedRoom(realm: Realm,
roomId: String, roomId: String,
roomSync: RoomSync, roomSync: RoomSync,
isInitalSync: Boolean): RoomEntity { isInitialSync: Boolean): RoomEntity {
Timber.v("Handle join sync for room $roomId") Timber.v("Handle join sync for room $roomId")
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) { if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
handleEphemeral(realm, roomId, roomSync.ephemeral, isInitalSync) handleEphemeral(realm, roomId, roomSync.ephemeral, isInitialSync)
} }
if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) { if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) {

View File

@ -318,6 +318,8 @@ dependencies {
implementation 'diff_match_patch:diff_match_patch:current' implementation 'diff_match_patch:diff_match_patch:current'
implementation "androidx.emoji:emoji-appcompat:1.0.0"
// TESTS // TESTS
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@ -19,10 +19,13 @@ package im.vector.riotx
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import androidx.core.provider.FontRequest import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat import androidx.core.provider.FontsContractCompat
import androidx.emoji.text.EmojiCompat
import androidx.emoji.text.FontRequestEmojiCompatConfig
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
@ -105,6 +108,23 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
) )
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration() vectorConfiguration.initConfiguration()
//Use emoji compat for the benefit of emoji spans
val config = FontRequestEmojiCompatConfig(this, fontRequest)
.setReplaceAll(true) // we want to replace all emojis with selected font
// .setEmojiSpanIndicatorEnabled(true)
// .setEmojiSpanIndicatorColor(Color.GREEN)
EmojiCompat.init(config)
.registerInitCallback(object : EmojiCompat.InitCallback() {
override fun onInitialized() {
Timber.v("Emoji compat onInitialized success ")
}
override fun onFailed(throwable: Throwable?) {
Timber.e(throwable,"Failed to init EmojiCompat")
}
})
NotificationUtils.createNotificationChannels(applicationContext) NotificationUtils.createNotificationChannels(applicationContext)
if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!! val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!

View File

@ -658,7 +658,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private fun observeSummaryState() { private fun observeSummaryState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
if (summary.membership == Membership.INVITE) { if (summary.membership == Membership.INVITE) {
summary.latestEvent?.root?.senderId?.let { senderId -> summary.latestPreviewableEvent?.root?.senderId?.let { senderId ->
session.getUser(senderId) session.getUser(senderId)
}?.also { }?.also {
setState { copy(asyncInviter = Success(it)) } setState { copy(asyncInviter = Success(it)) }

View File

@ -69,7 +69,7 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
LinearLayout.VERTICAL) LinearLayout.VERTICAL)
epoxyRecyclerView.addItemDecoration(dividerItemDecoration) epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
bottomSheetTitle.text = getString(R.string.read_receipts_list) bottomSheetTitle.text = getString(R.string.read_at)
epoxyController.setData(displayReadReceiptArgs.readReceipts) epoxyController.setData(displayReadReceiptArgs.readReceipts)
} }

View File

@ -35,7 +35,6 @@ import im.vector.riotx.R
import im.vector.riotx.core.extensions.canReact import im.vector.riotx.core.extensions.canReact
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.isSingleEmoji
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
@ -244,7 +243,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false if (event.root.getClearType() != EventType.MESSAGE) return false
//TODO if user is admin or moderator //TODO if user is admin or moderator
return event.annotations?.reactionsSummary?.any { isSingleEmoji(it.key) } ?: false return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
} }

View File

@ -38,12 +38,8 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
@EpoxyAttribute @EpoxyAttribute
var timeStamp: CharSequence? = null var timeStamp: CharSequence? = null
@EpoxyAttribute
var emojiTypeFace: Typeface? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.emojiReactionView.text = reactionKey holder.emojiReactionView.text = reactionKey
holder.emojiReactionView.typeface = emojiTypeFace ?: Typeface.DEFAULT
holder.displayNameView.text = authorDisplayName holder.displayNameView.text = authorDisplayName
timeStamp?.let { timeStamp?.let {
holder.timeStampView.text = it holder.timeStampView.text = it

View File

@ -28,7 +28,6 @@ import com.airbnb.epoxy.EpoxyRecyclerView
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
@ -43,13 +42,12 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class) private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class)
@Inject lateinit var viewReactionViewModelFactory: ViewReactionViewModel.Factory @Inject lateinit var viewReactionViewModelFactory: ViewReactionViewModel.Factory
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
@BindView(R.id.bottom_sheet_display_reactions_list) @BindView(R.id.bottom_sheet_display_reactions_list)
lateinit var epoxyRecyclerView: EpoxyRecyclerView lateinit var epoxyRecyclerView: EpoxyRecyclerView
private val epoxyController by lazy { private val epoxyController by lazy {
ViewReactionsEpoxyController(requireContext(), emojiCompatFontProvider.typeface) ViewReactionsEpoxyController(requireContext())
} }
override fun injectWith(screenComponent: ScreenComponent) { override fun injectWith(screenComponent: ScreenComponent) {

View File

@ -90,7 +90,7 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
.flatMapSingle { summaries -> .flatMapSingle { summaries ->
Observable Observable
.fromIterable(summaries.reactionsSummary) .fromIterable(summaries.reactionsSummary)
.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) } //.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
.toReactionInfoList() .toReactionInfoList()
} }
.execute { .execute {

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.text.format.DateUtils import android.text.format.DateUtils
import androidx.emoji.text.EmojiCompat
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
@ -30,7 +31,7 @@ import im.vector.riotx.core.ui.list.genericLoaderItem
/** /**
* Epoxy controller for reaction event list * Epoxy controller for reaction event list
*/ */
class ViewReactionsEpoxyController(private val context: Context, private val emojiCompatTypeface: Typeface?) class ViewReactionsEpoxyController(private val context: Context)
: TypedEpoxyController<DisplayReactionsViewState>() { : TypedEpoxyController<DisplayReactionsViewState>() {
override fun buildModels(state: DisplayReactionsViewState) { override fun buildModels(state: DisplayReactionsViewState) {
@ -50,9 +51,8 @@ class ViewReactionsEpoxyController(private val context: Context, private val emo
state.mapReactionKeyToMemberList()?.forEach { state.mapReactionKeyToMemberList()?.forEach {
reactionInfoSimpleItem { reactionInfoSimpleItem {
id(it.eventId) id(it.eventId)
emojiTypeFace(emojiCompatTypeface)
timeStamp(it.timestamp) timeStamp(it.timestamp)
reactionKey(it.reactionKey) reactionKey(EmojiCompat.get().process(it.reactionKey))
authorDisplayName(it.authorName ?: it.authorId) authorDisplayName(it.authorName ?: it.authorId)
} }
} }

View File

@ -151,7 +151,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
idToRefInFlow.add(reactionButton.id) idToRefInFlow.add(reactionButton.id)
reactionButton.reactionString = reaction.key reactionButton.reactionString = reaction.key
reactionButton.reactionCount = reaction.count reactionButton.reactionCount = reaction.count
reactionButton.emojiTypeFace = emojiTypeFace //reactionButton.emojiTypeFace = emojiTypeFace
reactionButton.setChecked(reaction.addedByMe) reactionButton.setChecked(reaction.addedByMe)
reactionButton.isEnabled = reaction.synced reactionButton.isEnabled = reaction.synced
} }

View File

@ -71,7 +71,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
memberName = formattedMemberName, memberName = formattedMemberName,
showInformation = showInformation, showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary orderedReactionList = event.annotations?.reactionsSummary
?.filter { isSingleEmoji(it.key) } //?.filter { isSingleEmoji(it.key) }
?.map { ?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty()) ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
}, },

View File

@ -25,14 +25,14 @@ class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary
var rightTimestamp = 0L var rightTimestamp = 0L
var leftTimestamp = 0L var leftTimestamp = 0L
if (null != leftRoomSummary) { if (null != leftRoomSummary) {
leftTimestamp = leftRoomSummary.latestEvent?.root?.originServerTs ?: 0 leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
} }
if (null != rightRoomSummary) { if (null != rightRoomSummary) {
rightTimestamp = rightRoomSummary.latestEvent?.root?.originServerTs ?: 0 rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
} }
return if (rightRoomSummary?.latestEvent?.root == null) { return if (rightRoomSummary?.latestPreviewableEvent?.root == null) {
-1 -1
} else if (leftRoomSummary?.latestEvent?.root == null) { } else if (leftRoomSummary?.latestPreviewableEvent?.root == null) {
1 1
} else { } else {
val deltaTimestamp = rightTimestamp - leftTimestamp val deltaTimestamp = rightTimestamp - leftTimestamp

View File

@ -32,7 +32,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
@EpoxyAttribute lateinit var title: CharSequence @EpoxyAttribute lateinit var title: CharSequence
@EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute var expanded: Boolean = false
@EpoxyAttribute var unreadCount: Int = 0 @EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute var listener: (() -> Unit)? = null
@ -42,7 +42,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also { val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also {
DrawableCompat.setTint(it, tintColor) DrawableCompat.setTint(it, tintColor)
} }
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadCount, showHighlighted)) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null) holder.titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
holder.titleView.text = title holder.titleView.text = title
holder.rootView.setOnClickListener { listener?.invoke() } holder.rootView.setOnClickListener { listener?.invoke() }

View File

@ -101,7 +101,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
id(titleRes) id(titleRes)
title(stringProvider.getString(titleRes).toUpperCase()) title(stringProvider.getString(titleRes).toUpperCase())
expanded(isExpanded) expanded(isExpanded)
unreadCount(unreadCount) unreadNotificationCount(unreadCount)
showHighlighted(showHighlighted) showHighlighted(showHighlighted)
listener { listener {
mutateExpandedState() mutateExpandedState()

View File

@ -16,9 +16,11 @@
package im.vector.riotx.features.home.room.list package im.vector.riotx.features.home.room.list
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
@ -36,7 +38,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var lastFormattedEvent: CharSequence @EpoxyAttribute lateinit var lastFormattedEvent: CharSequence
@EpoxyAttribute lateinit var lastEventTime: CharSequence @EpoxyAttribute lateinit var lastEventTime: CharSequence
@EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var unreadCount: Int = 0 @EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute var listener: (() -> Unit)? = null
@ -47,13 +50,15 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
holder.titleView.text = roomName holder.titleView.text = roomName
holder.lastEventTimeView.text = lastEventTime holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent holder.lastEventView.text = lastFormattedEvent
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadCount, showHighlighted)) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val titleView by bind<TextView>(R.id.roomNameView) val titleView by bind<TextView>(R.id.roomNameView)
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView) val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val unreadIndentIndicator by bind<View>(R.id.roomUnreadIndicator)
val lastEventView by bind<TextView>(R.id.roomLastEventView) val lastEventView by bind<TextView>(R.id.roomLastEventView)
val lastEventTimeView by bind<TextView>(R.id.roomLastEventTimeView) val lastEventTimeView by bind<TextView>(R.id.roomLastEventTimeView)
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView) val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)

View File

@ -16,6 +16,7 @@
package im.vector.riotx.features.home.room.list package im.vector.riotx.features.home.room.list
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
@ -38,7 +39,8 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
private val dateFormatter: VectorDateFormatter, private val dateFormatter: VectorDateFormatter,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer) { private val avatarRenderer: AvatarRenderer,
private val session: Session) {
fun create(roomSummary: RoomSummary, fun create(roomSummary: RoomSummary,
joiningRoomsIds: Set<String>, joiningRoomsIds: Set<String>,
@ -59,9 +61,9 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
rejectingErrorRoomsIds: Set<String>, rejectingErrorRoomsIds: Set<String>,
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
val secondLine = if (roomSummary.isDirect) { val secondLine = if (roomSummary.isDirect) {
roomSummary.latestEvent?.root?.senderId roomSummary.latestPreviewableEvent?.root?.senderId
} else { } else {
roomSummary.latestEvent?.root?.senderId?.let { roomSummary.latestPreviewableEvent?.root?.senderId?.let {
stringProvider.getString(R.string.invited_by, it) stringProvider.getString(R.string.invited_by, it)
} }
} }
@ -88,7 +90,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
var latestFormattedEvent: CharSequence = "" var latestFormattedEvent: CharSequence = ""
var latestEventTime: CharSequence = "" var latestEventTime: CharSequence = ""
val latestEvent = roomSummary.latestEvent val latestEvent = roomSummary.latestPreviewableEvent
if (latestEvent != null) { if (latestEvent != null) {
val date = latestEvent.root.localDateTime() val date = latestEvent.root.localDateTime()
val currentDate = DateProvider.currentLocalDateTime() val currentDate = DateProvider.currentLocalDateTime()
@ -131,7 +133,8 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
.roomName(roomSummary.displayName) .roomName(roomSummary.displayName)
.avatarUrl(roomSummary.avatarUrl) .avatarUrl(roomSummary.avatarUrl)
.showHighlighted(showHighlighted) .showHighlighted(showHighlighted)
.unreadCount(unreadCount) .unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)
.listener { listener?.onRoomSelected(roomSummary) } .listener { listener?.onRoomSelected(roomSummary) }
} }

View File

@ -89,18 +89,44 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
}) })
} }
} else { } else {
thumbnailView.isVisible = false
loadingView.isVisible = false
val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) val resolvedUrl = contentUrlResolver.resolveFullSize(data.url)
if (resolvedUrl == null) { if (resolvedUrl == null) {
thumbnailView.isVisible = false
loadingView.isVisible = false
errorView.isVisible = true errorView.isVisible = true
errorView.setText(R.string.unknown_error) errorView.setText(R.string.unknown_error)
} else { } else {
videoView.isVisible = true
videoView.setVideoPath(resolvedUrl) //Temporary code, some remote videos are not played by videoview setVideoUri
videoView.start() //So for now we download them then play
thumbnailView.isVisible = true
loadingView.isVisible = true
activeSessionHolder.getActiveSession()
.downloadFile(
FileService.DownloadMode.FOR_INTERNAL_USE,
data.eventId,
data.filename,
data.url,
null,
object : MatrixCallback<File> {
override fun onSuccess(data: File) {
thumbnailView.isVisible = false
loadingView.isVisible = false
videoView.isVisible = true
videoView.setVideoPath(data.path)
videoView.start()
}
override fun onFailure(failure: Throwable) {
loadingView.isVisible = false
errorView.isVisible = true
errorView.text = errorFormatter.toHumanReadable(failure)
}
})
} }
} }
} }

View File

@ -35,6 +35,7 @@ import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.emoji.text.EmojiCompat
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.utils.TextUtils import im.vector.riotx.core.utils.TextUtils
@ -58,12 +59,6 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
private var reactionSelector: View? = null private var reactionSelector: View? = null
var emojiTypeFace: Typeface? = null
set(value) {
field = value
emojiView?.typeface = value ?: Typeface.DEFAULT
}
private var dotsView: DotsView private var dotsView: DotsView
private var circleView: CircleView private var circleView: CircleView
var reactedListener: ReactedListener? = null var reactedListener: ReactedListener? = null
@ -82,7 +77,9 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
var reactionString = "😀" var reactionString = "😀"
set(value) { set(value) {
field = value field = value
emojiView?.text = field //maybe cache this for performances?
val emojiSpanned = EmojiCompat.get().process(value)
emojiView?.text = emojiSpanned
} }
private var animationScaleFactor: Float = 0.toFloat() private var animationScaleFactor: Float = 0.toFloat()
@ -104,7 +101,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount) countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount)
emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT // emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT
val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0) val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0)

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/riotx_header_panel_border_mobile_black" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/riotx_header_panel_border_mobile_dark" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/riotx_header_panel_border_mobile_light" />
</shape>

View File

@ -34,7 +34,9 @@
android:id="@+id/videoMediaViewerThumbnailView" android:id="@+id/videoMediaViewerThumbnailView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" android:visibility="gone"
android:scaleType="centerInside"
tools:visibility="visible" /> tools:visibility="visible" />
<ProgressBar <ProgressBar
@ -49,6 +51,7 @@
android:id="@+id/videoMediaViewerVideoView" android:id="@+id/videoMediaViewerVideoView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" /> android:visibility="gone" />
<TextView <TextView

View File

@ -5,38 +5,26 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:minHeight="40dp"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingEnd="8dp"> android:paddingEnd="8dp">
<ImageView <ImageView
android:id="@+id/readReceiptAvatar" android:id="@+id/readReceiptAvatar"
android:layout_width="24dp" android:layout_width="32dp"
android:layout_height="24dp" android:layout_height="32dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView
android:id="@+id/readReceiptName" android:id="@+id/readReceiptName"
android:layout_width="0dp" style="@style/BottomSheetItemTextMain"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:ellipsize="end"
android:lines="1"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
tools:text="@sample/matrix.json/data/displayName" /> tools:text="@sample/matrix.json/data/displayName" />
<TextView <TextView
android:id="@+id/readReceiptDate" android:id="@+id/readReceiptDate"
android:layout_width="wrap_content" style="@style/BottomSheetItemTime"
android:layout_height="wrap_content"
android:lines="1"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
tools:text="10:44" /> tools:text="10:44" />
</LinearLayout> </LinearLayout>

View File

@ -10,12 +10,22 @@
android:focusable="true" android:focusable="true"
android:foreground="?attr/selectableItemBackground"> android:foreground="?attr/selectableItemBackground">
<View
android:id="@+id/roomUnreadIndicator"
android:layout_width="4dp"
android:layout_height="0dp"
android:background="?attr/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
tools:visibility="visible"/>
<ImageView <ImageView
android:id="@+id/roomAvatarImageView" android:id="@+id/roomAvatarImageView"
android:layout_width="56dp" android:layout_width="56dp"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View File

@ -6,6 +6,7 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="8dp" android:paddingStart="8dp"
android:minHeight="40dp"
android:paddingEnd="8dp"> android:paddingEnd="8dp">
<TextView <TextView
@ -22,25 +23,12 @@
<TextView <TextView
android:id="@+id/itemSimpleReactionInfoMemberName" android:id="@+id/itemSimpleReactionInfoMemberName"
android:layout_width="0dp" style="@style/BottomSheetItemTextMain"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:lines="1"
android:textColor="?android:textColorPrimary"
android:textSize="16sp"
tools:text="@sample/matrix.json/data/displayName" /> tools:text="@sample/matrix.json/data/displayName" />
<TextView <TextView
android:id="@+id/itemSimpleReactionInfoTime" android:id="@+id/itemSimpleReactionInfoTime"
android:layout_width="wrap_content" style="@style/BottomSheetItemTime"
android:layout_height="wrap_content"
android:lines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
tools:text="10:44" /> tools:text="10:44" />

View File

@ -37,20 +37,23 @@
<TextView <TextView
android:id="@+id/reactionText" android:id="@+id/reactionText"
android:layout_width="20dp" android:layout_width="wrap_content"
android:layout_height="20dp" android:layout_height="20dp"
android:layout_gravity="center" android:minWidth="20dp"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginLeft="6dp" android:layout_marginLeft="6dp"
android:gravity="center" android:gravity="center"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="13sp" android:textSize="13sp"
android:maxEms="10"
android:ellipsize="middle"
android:singleLine="true"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/reactionCount" app:layout_constraintEnd_toStartOf="@id/reactionCount"
tools:text="👍" /> tools:text="* Party Parrot Again * 👀" />
<TextView <TextView
android:id="@+id/reactionCount" android:id="@+id/reactionCount"
@ -58,8 +61,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBaseline_toBaselineOf="@id/reactionText" app:layout_constraintBaseline_toBaselineOf="@id/reactionText"
android:layout_marginStart="-4dp" android:layout_marginStart="2dp"
android:layout_marginLeft="-4dp" android:layout_marginLeft="2dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:gravity="center" android:gravity="center"

View File

@ -9,48 +9,55 @@
<TextView <TextView
android:id="@+id/receiptMore" android:id="@+id/receiptMore"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="18dp"
android:layout_marginEnd="8dp"
android:gravity="center" android:gravity="center"
android:textSize="12sp" android:textSize="12sp"
android:background="?vctr_pill_receipt"
android:paddingStart="4dp"
android:paddingEnd="4dp"
tools:text="999+" /> tools:text="999+" />
<ImageView <ImageView
android:id="@+id/receiptAvatar5" android:id="@+id/receiptAvatar5"
android:layout_width="16dp" android:layout_width="18dp"
android:layout_height="16dp" android:layout_height="18dp"
android:layout_marginStart="2dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<ImageView <ImageView
android:id="@+id/receiptAvatar4" android:id="@+id/receiptAvatar4"
android:layout_width="16dp" android:layout_width="18dp"
android:layout_height="16dp" android:layout_height="18dp"
android:layout_marginStart="2dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<ImageView <ImageView
android:id="@+id/receiptAvatar3" android:id="@+id/receiptAvatar3"
android:layout_width="16dp" android:layout_width="18dp"
android:layout_height="16dp" android:layout_height="18dp"
android:layout_marginStart="2dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<ImageView <ImageView
android:id="@+id/receiptAvatar2" android:id="@+id/receiptAvatar2"
android:layout_width="16dp" android:layout_width="18dp"
android:layout_height="16dp" android:layout_height="18dp"
android:layout_marginStart="2dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<ImageView <ImageView
android:id="@+id/receiptAvatar1" android:id="@+id/receiptAvatar1"
android:layout_width="16dp" android:layout_width="18dp"
android:layout_height="16dp" android:layout_height="18dp"
android:layout_marginStart="2dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />

View File

@ -88,6 +88,7 @@
<attr name="vctr_pill_background_room_alias" format="reference" /> <attr name="vctr_pill_background_room_alias" format="reference" />
<attr name="vctr_pill_text_color_user_id" format="reference" /> <attr name="vctr_pill_text_color_user_id" format="reference" />
<attr name="vctr_pill_text_color_room_alias" format="reference" /> <attr name="vctr_pill_text_color_room_alias" format="reference" />
<attr name="vctr_pill_receipt" format="reference" />
<!-- Widget banner background --> <!-- Widget banner background -->
<attr name="vctr_widget_banner_background" format="color" /> <attr name="vctr_widget_banner_background" format="color" />

View File

@ -2,5 +2,6 @@
<resources> <resources>
<!-- Strings not defined in Riot --> <!-- Strings not defined in Riot -->
<string name="read_at">Read at</string>
</resources> </resources>

View File

@ -319,4 +319,23 @@
<item name="android:background">@drawable/vector_label_background_light</item> <item name="android:background">@drawable/vector_label_background_light</item>
</style> </style>
<style name="BottomSheetItemTextMain">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:ellipsize">end</item>
<item name="android:lines">1</item>
<item name="android:textColor">?riotx_text_primary</item>
<item name="android:textSize">16sp</item>
</style>
<style name="BottomSheetItemTime">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:lines">1</item>
<item name="android:textColor">?riotx_text_secondary</item>
<item name="android:textSize">12sp</item>
</style>
</resources> </resources>

View File

@ -75,6 +75,8 @@
<item name="vctr_markdown_block_background_color">#FF4D4D4D</item> <item name="vctr_markdown_block_background_color">#FF4D4D4D</item>
<item name="vctr_pill_receipt">@drawable/pill_receipt_black</item>
<!-- activities background --> <!-- activities background -->
<item name="android:windowBackground">@color/riot_primary_background_color_black</item> <item name="android:windowBackground">@color/riot_primary_background_color_black</item>
<item name="vctr_bottom_nav_background_color">@color/primary_color_black</item> <item name="vctr_bottom_nav_background_color">@color/primary_color_black</item>

View File

@ -163,6 +163,8 @@
<item name="vctr_pill_text_color_user_id">@android:color/white</item> <item name="vctr_pill_text_color_user_id">@android:color/white</item>
<item name="vctr_pill_text_color_room_alias">@color/riot_primary_text_color_dark</item> <item name="vctr_pill_text_color_room_alias">@color/riot_primary_text_color_dark</item>
<item name="vctr_pill_receipt">@drawable/pill_receipt_dark</item>
<item name="vctr_direct_chat_circle">@drawable/direct_chat_circle_dark</item> <item name="vctr_direct_chat_circle">@drawable/direct_chat_circle_dark</item>
<item name="vctr_widget_banner_background">#FF454545</item> <item name="vctr_widget_banner_background">#FF454545</item>

View File

@ -163,6 +163,8 @@
<item name="vctr_pill_text_color_user_id">@color/riot_primary_text_color_light</item> <item name="vctr_pill_text_color_user_id">@color/riot_primary_text_color_light</item>
<item name="vctr_pill_text_color_room_alias">@android:color/white</item> <item name="vctr_pill_text_color_room_alias">@android:color/white</item>
<item name="vctr_pill_receipt">@drawable/pill_receipt_light</item>
<item name="vctr_direct_chat_circle">@drawable/direct_chat_circle_light</item> <item name="vctr_direct_chat_circle">@drawable/direct_chat_circle_light</item>
<item name="vctr_widget_banner_background">#FFD3EFE1</item> <item name="vctr_widget_banner_background">#FFD3EFE1</item>