mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Incoming DM verification handling in timeline
This commit is contained in:
parent
02f03e6b23
commit
0776a301ea
@ -27,4 +27,3 @@ class ReferencesAggregatedSummary(
|
|||||||
val sourceEvents: List<String>,
|
val sourceEvents: List<String>,
|
||||||
val localEchos: List<String>
|
val localEchos: List<String>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +17,3 @@ internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, tx
|
|||||||
this.eventId = txID
|
this.eventId = txID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,4 +63,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||||||
|
|
||||||
object ClearSendQueue : RoomDetailAction()
|
object ClearSendQueue : RoomDetailAction()
|
||||||
object ResendAll : RoomDetailAction()
|
object ResendAll : RoomDetailAction()
|
||||||
|
|
||||||
|
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
|
||||||
|
data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction()
|
||||||
}
|
}
|
||||||
|
@ -1024,6 +1024,10 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
.show(requireActivity().supportFragmentManager, "DISPLAY_EDITS")
|
.show(requireActivity().supportFragmentManager, "DISPLAY_EDITS")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTimelineItemAction(itemAction: RoomDetailAction) {
|
||||||
|
roomDetailViewModel.handle(itemAction)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRoomCreateLinkClicked(url: String) {
|
override fun onRoomCreateLinkClicked(url: String) {
|
||||||
permalinkHandler.launch(requireContext(), url, object : NavigateToRoomInterceptor {
|
permalinkHandler.launch(requireContext(), url, object : NavigateToRoomInterceptor {
|
||||||
override fun navToRoom(roomId: String, eventId: String?): Boolean {
|
override fun navToRoom(roomId: String, eventId: String?): Boolean {
|
||||||
|
@ -48,6 +48,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
|||||||
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
@ -177,6 +178,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||||
|
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||||
|
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,6 +789,21 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) {
|
||||||
|
session.getSasVerificationService().beginKeyVerificationInDMs(
|
||||||
|
KeyVerificationStart.VERIF_METHOD_SAS,
|
||||||
|
action.transactionId,
|
||||||
|
room.roomId,
|
||||||
|
action.otherUserId,
|
||||||
|
action.otherdDeviceId,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) {
|
||||||
|
Timber.e("TODO implement $action")
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeSyncState() {
|
private fun observeSyncState() {
|
||||||
session.rx()
|
session.rx()
|
||||||
.liveSyncState()
|
.liveSyncState()
|
||||||
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||||||
import im.vector.riotx.core.date.VectorDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.core.epoxy.LoadingItem_
|
import im.vector.riotx.core.epoxy.LoadingItem_
|
||||||
import im.vector.riotx.core.extensions.localDateTime
|
import im.vector.riotx.core.extensions.localDateTime
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewState
|
import im.vector.riotx.features.home.room.detail.RoomDetailViewState
|
||||||
import im.vector.riotx.features.home.room.detail.UnreadState
|
import im.vector.riotx.features.home.room.detail.UnreadState
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
|
import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
|
||||||
@ -62,6 +63,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||||||
fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
|
fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
|
||||||
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
||||||
fun onEditedDecorationClicked(informationData: MessageInformationData)
|
fun onEditedDecorationClicked(informationData: MessageInformationData)
|
||||||
|
|
||||||
|
// TODO move all callbacks to this?
|
||||||
|
fun onTimelineItemAction(itemAction: RoomDetailAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReactionPillCallback {
|
interface ReactionPillCallback {
|
||||||
|
@ -23,10 +23,7 @@ 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.isTextMessage
|
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||||
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.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
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.room.timeline.getLastMessageContent
|
||||||
@ -172,6 +169,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
eventHtmlRenderer.get().render(messageContent.formattedBody
|
eventHtmlRenderer.get().render(messageContent.formattedBody
|
||||||
?: messageContent.body)
|
?: messageContent.body)
|
||||||
|
} else if (messageContent is MessageVerificationRequestContent) {
|
||||||
|
stringProvider.getString(R.string.verification_request)
|
||||||
} else {
|
} else {
|
||||||
messageContent?.body
|
messageContent?.body
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.riotx.features.home.room.detail.timeline.factory
|
package im.vector.riotx.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.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
|
||||||
@ -34,7 +35,8 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider,
|
class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider) {
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
|
private val session: Session) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
@ -46,7 +48,8 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
|
|||||||
sendState = event.root.sendState,
|
sendState = event.root.sendState,
|
||||||
avatarUrl = event.senderAvatar,
|
avatarUrl = event.senderAvatar,
|
||||||
memberName = event.getDisambiguatedDisplayName(),
|
memberName = event.getDisambiguatedDisplayName(),
|
||||||
showInformation = false
|
showInformation = false,
|
||||||
|
sentByMe = event.root.senderId == session.myUserId
|
||||||
)
|
)
|
||||||
val attributes = NoticeItem.Attributes(
|
val attributes = NoticeItem.Attributes(
|
||||||
avatarRenderer = avatarRenderer,
|
avatarRenderer = avatarRenderer,
|
||||||
|
@ -24,6 +24,7 @@ import android.text.style.ClickableSpan
|
|||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
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.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
@ -64,7 +65,8 @@ class MessageItemFactory @Inject constructor(
|
|||||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||||
private val defaultItemFactory: DefaultItemFactory,
|
private val defaultItemFactory: DefaultItemFactory,
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider) {
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
|
private val session: Session) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
@ -104,6 +106,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,6 +131,51 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent,
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
highlight: Boolean,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
|
||||||
|
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
||||||
|
val myUserId = session.myUserId
|
||||||
|
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId
|
||||||
|
val otherUserName = if (informationData.sentByMe) session.getUser(messageContent.toUserId)?.displayName
|
||||||
|
else informationData.memberName
|
||||||
|
return VerificationRequestItem_()
|
||||||
|
.attributes(
|
||||||
|
VerificationRequestItem.Attributes(
|
||||||
|
otherUserId,
|
||||||
|
otherUserName.toString(),
|
||||||
|
messageContent.fromDevice,
|
||||||
|
informationData.eventId,
|
||||||
|
informationData,
|
||||||
|
attributes.avatarRenderer,
|
||||||
|
attributes.colorProvider,
|
||||||
|
attributes.itemLongClickListener,
|
||||||
|
attributes.itemClickListener,
|
||||||
|
attributes.reactionPillCallback,
|
||||||
|
attributes.readReceiptsCallback,
|
||||||
|
attributes.emojiTypeFace
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.callback(callback)
|
||||||
|
// .izLocalFile(messageContent.getFileUrl().isLocalFile())
|
||||||
|
// .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||||
|
.highlighted(highlight)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
// .filename(messageContent.body)
|
||||||
|
// .iconRes(R.drawable.filetype_audio)
|
||||||
|
// .clickListener(
|
||||||
|
// DebouncedClickListener(View.OnClickListener {
|
||||||
|
// callback?.onAudioMessageClicked(messageContent)
|
||||||
|
// }))
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildFileMessageItem(messageContent: MessageFileContent,
|
private fun buildFileMessageItem(messageContent: MessageFileContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
@ -193,7 +241,8 @@ class MessageItemFactory @Inject constructor(
|
|||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val thumbnailData = ImageContentRenderer.Data(
|
val thumbnailData = ImageContentRenderer.Data(
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
|
url = messageContent.videoInfo?.thumbnailFile?.url
|
||||||
|
?: messageContent.videoInfo?.thumbnailUrl,
|
||||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||||
height = messageContent.videoInfo?.height,
|
height = messageContent.videoInfo?.height,
|
||||||
maxHeight = maxHeight,
|
maxHeight = maxHeight,
|
||||||
|
@ -28,7 +28,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||||||
private val encryptedItemFactory: EncryptedItemFactory,
|
private val encryptedItemFactory: EncryptedItemFactory,
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val defaultItemFactory: DefaultItemFactory,
|
private val defaultItemFactory: DefaultItemFactory,
|
||||||
private val roomCreateItemFactory: RoomCreateItemFactory) {
|
private val roomCreateItemFactory: RoomCreateItemFactory,
|
||||||
|
private val verificationConclusionItemFactory: VerificationItemFactory) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
@ -66,13 +67,15 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||||||
}
|
}
|
||||||
EventType.KEY_VERIFICATION_ACCEPT,
|
EventType.KEY_VERIFICATION_ACCEPT,
|
||||||
EventType.KEY_VERIFICATION_START,
|
EventType.KEY_VERIFICATION_START,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
EventType.KEY_VERIFICATION_MAC -> {
|
EventType.KEY_VERIFICATION_MAC -> {
|
||||||
// These events are filtered from timeline in normal case
|
// These events are filtered from timeline in normal case
|
||||||
// Only visible in developer mode
|
// Only visible in developer mode
|
||||||
defaultItemFactory.create(event, highlight, callback)
|
noticeItemFactory.create(event, highlight, callback)
|
||||||
|
}
|
||||||
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
|
verificationConclusionItemFactory.create(event, highlight, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unhandled event types (yet)
|
// Unhandled event types (yet)
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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.room.detail.timeline.factory
|
||||||
|
|
||||||
|
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.RelationType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||||
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem_
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can creates verification conclusion items
|
||||||
|
* Notice that not all KEY_VERIFICATION_DONE will be displayed in timeline,
|
||||||
|
* several checks are made to see if this conclusion is attached to a known request
|
||||||
|
*/
|
||||||
|
class VerificationItemFactory @Inject constructor(
|
||||||
|
private val colorProvider: ColorProvider,
|
||||||
|
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||||
|
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||||
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider,
|
||||||
|
private val session: Session
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun create(event: TimelineEvent,
|
||||||
|
highlight: Boolean,
|
||||||
|
callback: TimelineEventController.Callback?
|
||||||
|
): VectorEpoxyModel<*>? {
|
||||||
|
if (event.root.eventId == null) return null
|
||||||
|
|
||||||
|
val relContent: MessageRelationContent = event.root.content.toModel()
|
||||||
|
?: event.root.getClearContent().toModel()
|
||||||
|
?: return ignoredConclusion(event, highlight, callback)
|
||||||
|
|
||||||
|
if (relContent.relatesTo?.type != RelationType.REFERENCE) return ignoredConclusion(event, highlight, callback)
|
||||||
|
val refEventId = relContent.relatesTo?.eventId
|
||||||
|
?: return ignoredConclusion(event, highlight, callback)
|
||||||
|
|
||||||
|
// If we cannot find the referenced request we do not display the done event
|
||||||
|
val refEvent = session.getRoom(event.root.roomId ?: "")?.getTimeLineEvent(refEventId)
|
||||||
|
?: return ignoredConclusion(event, highlight, callback)
|
||||||
|
|
||||||
|
// If it's not a request ignore this event
|
||||||
|
if (refEvent.root.getClearContent().toModel<MessageVerificationRequestContent>() == null) return ignoredConclusion(event, highlight, callback)
|
||||||
|
|
||||||
|
// Is the request referenced is actually really completed?
|
||||||
|
val referenceInformationData = messageInformationDataFactory.create(refEvent, null)
|
||||||
|
if (referenceInformationData.referencesInfoData?.verificationStatus != VerificationState.DONE) return ignoredConclusion(event, highlight, callback)
|
||||||
|
|
||||||
|
val informationData = messageInformationDataFactory.create(event, null)
|
||||||
|
val attributes = messageItemAttributesFactory.create(null, informationData, callback)
|
||||||
|
|
||||||
|
when (event.root.getClearType()) {
|
||||||
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
|
// We only tale the one sent by me
|
||||||
|
if (informationData.sentByMe) {
|
||||||
|
// We only display the done sent by the other user, the done send by me is ignored
|
||||||
|
return ignoredConclusion(event, highlight, callback)
|
||||||
|
}
|
||||||
|
return VerificationRequestConclusionItem_()
|
||||||
|
.attributes(
|
||||||
|
VerificationRequestConclusionItem.Attributes(
|
||||||
|
toUserId = informationData.senderId,
|
||||||
|
toUserName = informationData.memberName.toString(),
|
||||||
|
informationData = informationData,
|
||||||
|
avatarRenderer = attributes.avatarRenderer,
|
||||||
|
colorProvider = colorProvider,
|
||||||
|
emojiTypeFace = attributes.emojiTypeFace,
|
||||||
|
itemClickListener = attributes.itemClickListener,
|
||||||
|
itemLongClickListener = attributes.itemLongClickListener,
|
||||||
|
reactionPillCallback = attributes.reactionPillCallback,
|
||||||
|
readReceiptsCallback = attributes.readReceiptsCallback
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.highlighted(highlight)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ignoredConclusion(event: TimelineEvent,
|
||||||
|
highlight: Boolean,
|
||||||
|
callback: TimelineEventController.Callback?
|
||||||
|
): VectorEpoxyModel<*>? {
|
||||||
|
if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,12 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
|||||||
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.REACTION,
|
EventType.REACTION,
|
||||||
|
EventType.KEY_VERIFICATION_START,
|
||||||
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
|
EventType.KEY_VERIFICATION_ACCEPT,
|
||||||
|
EventType.KEY_VERIFICATION_MAC,
|
||||||
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
EventType.REDACTION -> formatDebug(timelineEvent.root)
|
EventType.REDACTION -> formatDebug(timelineEvent.root)
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("Type $type not handled by this formatter")
|
Timber.v("Type $type not handled by this formatter")
|
||||||
|
@ -20,15 +20,19 @@ package im.vector.riotx.features.home.room.detail.timeline.helper
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.room.model.ReferencesAggregatedContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
||||||
|
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||||
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.core.extensions.localDateTime
|
import im.vector.riotx.core.extensions.localDateTime
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
import im.vector.riotx.core.date.VectorDateFormatter
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -86,7 +90,15 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||||||
.map {
|
.map {
|
||||||
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
|
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
|
||||||
}
|
}
|
||||||
.toList()
|
.toList(),
|
||||||
|
referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary ->
|
||||||
|
val stateStr = referencesAggregatedSummary.content.toModel<ReferencesAggregatedContent>()?.verificationSummary
|
||||||
|
ReferencesInfoData(
|
||||||
|
VerificationState.values().firstOrNull { stateStr == it.name }
|
||||||
|
?: VerificationState.REQUEST
|
||||||
|
)
|
||||||
|
},
|
||||||
|
sentByMe = event.root.senderId == session.myUserId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* 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.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.riotx.features.reactions.widget.ReactionButton
|
||||||
|
import im.vector.riotx.features.ui.getMessageTextColor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base timeline item with reactions and read receipts.
|
||||||
|
* Manages associated click listeners and send status.
|
||||||
|
* Should not be used as this, use a subclass.
|
||||||
|
*/
|
||||||
|
abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem<H>() {
|
||||||
|
|
||||||
|
abstract val baseAttributes: Attributes
|
||||||
|
|
||||||
|
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
|
baseAttributes.readReceiptsCallback?.onReadReceiptsClicked(baseAttributes.informationData.readReceipts)
|
||||||
|
})
|
||||||
|
|
||||||
|
private var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
||||||
|
override fun onReacted(reactionButton: ReactionButton) {
|
||||||
|
baseAttributes.reactionPillCallback?.onClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnReacted(reactionButton: ReactionButton) {
|
||||||
|
baseAttributes.reactionPillCallback?.onClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(reactionButton: ReactionButton) {
|
||||||
|
baseAttributes.reactionPillCallback?.onLongClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun shouldShowReactionAtBottom(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEventIds(): List<String> {
|
||||||
|
return listOf(baseAttributes.informationData.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(holder: H) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.readReceiptsView.render(
|
||||||
|
baseAttributes.informationData.readReceipts,
|
||||||
|
baseAttributes.avatarRenderer,
|
||||||
|
_readReceiptsClickListener
|
||||||
|
)
|
||||||
|
|
||||||
|
val reactions = baseAttributes.informationData.orderedReactionList
|
||||||
|
if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) {
|
||||||
|
holder.reactionsContainer.isVisible = false
|
||||||
|
} else {
|
||||||
|
holder.reactionsContainer.isVisible = true
|
||||||
|
holder.reactionsContainer.removeAllViews()
|
||||||
|
reactions.take(8).forEach { reaction ->
|
||||||
|
val reactionButton = ReactionButton(holder.view.context)
|
||||||
|
reactionButton.reactedListener = reactionClickListener
|
||||||
|
reactionButton.setTag(R.id.reactionsContainer, reaction.key)
|
||||||
|
reactionButton.reactionString = reaction.key
|
||||||
|
reactionButton.reactionCount = reaction.count
|
||||||
|
reactionButton.setChecked(reaction.addedByMe)
|
||||||
|
reactionButton.isEnabled = reaction.synced
|
||||||
|
holder.reactionsContainer.addView(reactionButton)
|
||||||
|
}
|
||||||
|
holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.view.setOnClickListener(baseAttributes.itemClickListener)
|
||||||
|
holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: H) {
|
||||||
|
holder.readReceiptsView.unbind()
|
||||||
|
super.unbind(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) {
|
||||||
|
root.isClickable = baseAttributes.informationData.sendState.isSent()
|
||||||
|
val state = if (baseAttributes.informationData.hasPendingEdits) SendState.UNSENT else baseAttributes.informationData.sendState
|
||||||
|
textView?.setTextColor(baseAttributes.colorProvider.getMessageTextColor(state))
|
||||||
|
failureIndicator?.isVisible = baseAttributes.informationData.sendState.hasFailed()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
|
||||||
|
val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds all the common attributes for timeline items.
|
||||||
|
*/
|
||||||
|
interface Attributes {
|
||||||
|
// val avatarSize: Int,
|
||||||
|
val informationData: MessageInformationData
|
||||||
|
val avatarRenderer: AvatarRenderer
|
||||||
|
val colorProvider: ColorProvider
|
||||||
|
val itemLongClickListener: View.OnLongClickListener?
|
||||||
|
val itemClickListener: View.OnClickListener?
|
||||||
|
// val memberClickListener: View.OnClickListener?
|
||||||
|
val reactionPillCallback: TimelineEventController.ReactionPillCallback?
|
||||||
|
// val avatarCallback: TimelineEventController.AvatarCallback?
|
||||||
|
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback?
|
||||||
|
// val emojiTypeFace: Typeface?
|
||||||
|
}
|
||||||
|
|
||||||
|
// data class AbsAttributes(
|
||||||
|
// override val informationData: MessageInformationData,
|
||||||
|
// override val avatarRenderer: AvatarRenderer,
|
||||||
|
// override val colorProvider: ColorProvider,
|
||||||
|
// override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||||
|
// override val itemClickListener: View.OnClickListener? = null,
|
||||||
|
// override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||||
|
// override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||||
|
// ) : Attributes
|
||||||
|
}
|
@ -18,22 +18,24 @@ package im.vector.riotx.features.home.room.detail.timeline.item
|
|||||||
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.riotx.features.reactions.widget.ReactionButton
|
|
||||||
import im.vector.riotx.features.ui.getMessageTextColor
|
|
||||||
|
|
||||||
abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
/**
|
||||||
|
* Base timeline item that adds an optional information bar with the sender avatar, name and time
|
||||||
|
* Adds associated click listeners (on avatar, displayname)
|
||||||
|
*/
|
||||||
|
abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>() {
|
||||||
|
|
||||||
|
override val baseAttributes: AbsBaseMessageItem.Attributes
|
||||||
|
get() = attributes
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var attributes: Attributes
|
lateinit var attributes: Attributes
|
||||||
@ -45,24 +47,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
attributes.avatarCallback?.onMemberNameClicked(attributes.informationData)
|
attributes.avatarCallback?.onMemberNameClicked(attributes.informationData)
|
||||||
})
|
})
|
||||||
|
|
||||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
|
||||||
attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts)
|
|
||||||
})
|
|
||||||
|
|
||||||
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
|
||||||
override fun onReacted(reactionButton: ReactionButton) {
|
|
||||||
attributes.reactionPillCallback?.onClickOnReactionPill(attributes.informationData, reactionButton.reactionString, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUnReacted(reactionButton: ReactionButton) {
|
|
||||||
attributes.reactionPillCallback?.onClickOnReactionPill(attributes.informationData, reactionButton.reactionString, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLongClick(reactionButton: ReactionButton) {
|
|
||||||
attributes.reactionPillCallback?.onLongClickOnReactionPill(attributes.informationData, reactionButton.reactionString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bind(holder: H) {
|
override fun bind(holder: H) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
if (attributes.informationData.showInformation) {
|
if (attributes.informationData.showInformation) {
|
||||||
@ -94,60 +78,12 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
holder.avatarImageView.setOnLongClickListener(null)
|
holder.avatarImageView.setOnLongClickListener(null)
|
||||||
holder.memberNameView.setOnLongClickListener(null)
|
holder.memberNameView.setOnLongClickListener(null)
|
||||||
}
|
}
|
||||||
holder.view.setOnClickListener(attributes.itemClickListener)
|
|
||||||
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
|
|
||||||
|
|
||||||
holder.readReceiptsView.render(
|
|
||||||
attributes.informationData.readReceipts,
|
|
||||||
attributes.avatarRenderer,
|
|
||||||
_readReceiptsClickListener
|
|
||||||
)
|
|
||||||
|
|
||||||
val reactions = attributes.informationData.orderedReactionList
|
|
||||||
if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) {
|
|
||||||
holder.reactionsContainer.isVisible = false
|
|
||||||
} else {
|
|
||||||
holder.reactionsContainer.isVisible = true
|
|
||||||
holder.reactionsContainer.removeAllViews()
|
|
||||||
reactions.take(8).forEach { reaction ->
|
|
||||||
val reactionButton = ReactionButton(holder.view.context)
|
|
||||||
reactionButton.reactedListener = reactionClickListener
|
|
||||||
reactionButton.setTag(R.id.reactionsContainer, reaction.key)
|
|
||||||
reactionButton.reactionString = reaction.key
|
|
||||||
reactionButton.reactionCount = reaction.count
|
|
||||||
reactionButton.setChecked(reaction.addedByMe)
|
|
||||||
reactionButton.isEnabled = reaction.synced
|
|
||||||
holder.reactionsContainer.addView(reactionButton)
|
|
||||||
}
|
|
||||||
holder.reactionsContainer.setOnLongClickListener(attributes.itemLongClickListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: H) {
|
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
|
||||||
holder.readReceiptsView.unbind()
|
|
||||||
super.unbind(holder)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun shouldShowReactionAtBottom(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEventIds(): List<String> {
|
|
||||||
return listOf(attributes.informationData.eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) {
|
|
||||||
root.isClickable = attributes.informationData.sendState.isSent()
|
|
||||||
val state = if (attributes.informationData.hasPendingEdits) SendState.UNSENT else attributes.informationData.sendState
|
|
||||||
textView?.setTextColor(attributes.colorProvider.getMessageTextColor(state))
|
|
||||||
failureIndicator?.isVisible = attributes.informationData.sendState.hasFailed()
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
|
||||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
val timeView by bind<TextView>(R.id.messageTimeView)
|
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,15 +91,15 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||||||
*/
|
*/
|
||||||
data class Attributes(
|
data class Attributes(
|
||||||
val avatarSize: Int,
|
val avatarSize: Int,
|
||||||
val informationData: MessageInformationData,
|
override val informationData: MessageInformationData,
|
||||||
val avatarRenderer: AvatarRenderer,
|
override val avatarRenderer: AvatarRenderer,
|
||||||
val colorProvider: ColorProvider,
|
override val colorProvider: ColorProvider,
|
||||||
val itemLongClickListener: View.OnLongClickListener? = null,
|
override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||||
val itemClickListener: View.OnClickListener? = null,
|
override val itemClickListener: View.OnClickListener? = null,
|
||||||
val memberClickListener: View.OnClickListener? = null,
|
val memberClickListener: View.OnClickListener? = null,
|
||||||
val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||||
val avatarCallback: TimelineEventController.AvatarCallback? = null,
|
val avatarCallback: TimelineEventController.AvatarCallback? = null,
|
||||||
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||||
val emojiTypeFace: Typeface? = null
|
val emojiTypeFace: Typeface? = null
|
||||||
)
|
) : AbsBaseMessageItem.Attributes
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@ -33,7 +34,14 @@ data class MessageInformationData(
|
|||||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||||
val hasBeenEdited: Boolean = false,
|
val hasBeenEdited: Boolean = false,
|
||||||
val hasPendingEdits: Boolean = false,
|
val hasPendingEdits: Boolean = false,
|
||||||
val readReceipts: List<ReadReceiptData> = emptyList()
|
val readReceipts: List<ReadReceiptData> = emptyList(),
|
||||||
|
val referencesInfoData: ReferencesInfoData? = null,
|
||||||
|
val sentByMe : Boolean
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ReferencesInfoData(
|
||||||
|
val verificationStatus: VerificationState
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
|
||||||
|
abstract class VerificationRequestConclusionItem : AbsBaseMessageItem<VerificationRequestConclusionItem.Holder>() {
|
||||||
|
|
||||||
|
override val baseAttributes: AbsBaseMessageItem.Attributes
|
||||||
|
get() = attributes
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var attributes: Attributes
|
||||||
|
|
||||||
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.endGuideline.updateLayoutParams<RelativeLayout.LayoutParams> {
|
||||||
|
this.marginEnd = leftGuideline
|
||||||
|
}
|
||||||
|
holder.titleView.text = holder.view.context.getString(R.string.sas_verified)
|
||||||
|
holder.descriptionView.text = "${attributes.informationData.memberName} (${attributes.informationData.senderId})"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : AbsBaseMessageItem.Holder(STUB_ID) {
|
||||||
|
val titleView by bind<AppCompatTextView>(R.id.itemVerificationDoneTitleTextView)
|
||||||
|
val descriptionView by bind<AppCompatTextView>(R.id.itemVerificationDoneDetailTextView)
|
||||||
|
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STUB_ID = R.id.messageVerificationDoneStub
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds all the common attributes for timeline items.
|
||||||
|
*/
|
||||||
|
data class Attributes(
|
||||||
|
val toUserId: String,
|
||||||
|
val toUserName: String,
|
||||||
|
override val informationData: MessageInformationData,
|
||||||
|
override val avatarRenderer: AvatarRenderer,
|
||||||
|
override val colorProvider: ColorProvider,
|
||||||
|
override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||||
|
override val itemClickListener: View.OnClickListener? = null,
|
||||||
|
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||||
|
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||||
|
val emojiTypeFace: Typeface? = null
|
||||||
|
) : AbsBaseMessageItem.Attributes
|
||||||
|
}
|
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
|
||||||
|
abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestItem.Holder>() {
|
||||||
|
|
||||||
|
override val baseAttributes: AbsBaseMessageItem.Attributes
|
||||||
|
get() = attributes
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var attributes: Attributes
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
holder.endGuideline.updateLayoutParams<RelativeLayout.LayoutParams> {
|
||||||
|
this.marginEnd = leftGuideline
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.titleView.text = if (attributes.informationData.sentByMe)
|
||||||
|
holder.view.context.getString(R.string.verification_sent)
|
||||||
|
// + "\n ${attributes.informationData.referencesInfoData?.verificationStatus?.name
|
||||||
|
// ?: "??"}"
|
||||||
|
else
|
||||||
|
holder.view.context.getString(R.string.verification_request)
|
||||||
|
// + "\n ${attributes.informationData.referencesInfoData?.verificationStatus?.name
|
||||||
|
// ?: "??"}"
|
||||||
|
|
||||||
|
holder.descriptionView.text = if (!attributes.informationData.sentByMe)
|
||||||
|
"${attributes.informationData.memberName} (${attributes.informationData.senderId})"
|
||||||
|
else
|
||||||
|
"${attributes.otherUserName} (${attributes.otherUserId})"
|
||||||
|
|
||||||
|
when (attributes.informationData.referencesInfoData?.verificationStatus) {
|
||||||
|
VerificationState.REQUEST,
|
||||||
|
null -> {
|
||||||
|
holder.buttonBar.isVisible = !attributes.informationData.sentByMe
|
||||||
|
holder.statusTextView.text = null
|
||||||
|
holder.statusTextView.isVisible = false
|
||||||
|
}
|
||||||
|
VerificationState.CANCELED_BY_OTHER -> {
|
||||||
|
holder.buttonBar.isVisible = false
|
||||||
|
holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_other_cancelled, attributes.informationData.memberName)
|
||||||
|
holder.statusTextView.isVisible = true
|
||||||
|
}
|
||||||
|
VerificationState.CANCELED_BY_ME -> {
|
||||||
|
holder.buttonBar.isVisible = false
|
||||||
|
holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_you_cancelled)
|
||||||
|
holder.statusTextView.isVisible = true
|
||||||
|
}
|
||||||
|
VerificationState.WAITING -> {
|
||||||
|
holder.buttonBar.isVisible = false
|
||||||
|
holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_waiting)
|
||||||
|
holder.statusTextView.isVisible = true
|
||||||
|
}
|
||||||
|
VerificationState.DONE -> {
|
||||||
|
holder.buttonBar.isVisible = false
|
||||||
|
holder.statusTextView.text = if (attributes.informationData.sentByMe)
|
||||||
|
holder.view.context.getString(R.string.verification_request_other_accepted, attributes.otherUserName)
|
||||||
|
else
|
||||||
|
holder.view.context.getString(R.string.verification_request_you_accepted)
|
||||||
|
holder.statusTextView.isVisible = true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
holder.buttonBar.isVisible = false
|
||||||
|
holder.statusTextView.text = null
|
||||||
|
holder.statusTextView.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.callback = callback
|
||||||
|
holder.attributes = attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
super.unbind(holder)
|
||||||
|
holder.callback = null
|
||||||
|
holder.attributes = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : AbsBaseMessageItem.Holder(STUB_ID) {
|
||||||
|
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
var attributes: Attributes? = null
|
||||||
|
|
||||||
|
private val _clickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
|
val att = attributes ?: return@OnClickListener
|
||||||
|
if (it == acceptButton) {
|
||||||
|
callback?.onTimelineItemAction(RoomDetailAction.AcceptVerificationRequest(
|
||||||
|
att.referenceId,
|
||||||
|
att.otherUserId,
|
||||||
|
att.fromDevide))
|
||||||
|
} else if (it == declineButton) {
|
||||||
|
callback?.onTimelineItemAction(RoomDetailAction.DeclineVerificationRequest(att.referenceId))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val titleView by bind<AppCompatTextView>(R.id.itemVerificationTitleTextView)
|
||||||
|
val descriptionView by bind<AppCompatTextView>(R.id.itemVerificationDetailTextView)
|
||||||
|
val buttonBar by bind<ViewGroup>(R.id.itemVerificationButtonBar)
|
||||||
|
val statusTextView by bind<TextView>(R.id.itemVerificationStatusText)
|
||||||
|
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
||||||
|
private val declineButton by bind<Button>(R.id.sas_verification_verified_decline_button)
|
||||||
|
private val acceptButton by bind<Button>(R.id.sas_verification_verified_accept_button)
|
||||||
|
|
||||||
|
override fun bindView(itemView: View) {
|
||||||
|
super.bindView(itemView)
|
||||||
|
acceptButton.setOnClickListener(_clickListener)
|
||||||
|
declineButton.setOnClickListener(_clickListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STUB_ID = R.id.messageVerificationRequestStub
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds all the common attributes for timeline items.
|
||||||
|
*/
|
||||||
|
data class Attributes(
|
||||||
|
val otherUserId: String,
|
||||||
|
val otherUserName: String,
|
||||||
|
val fromDevide: String,
|
||||||
|
val referenceId: String,
|
||||||
|
// val avatarSize: Int,
|
||||||
|
override val informationData: MessageInformationData,
|
||||||
|
override val avatarRenderer: AvatarRenderer,
|
||||||
|
override val colorProvider: ColorProvider,
|
||||||
|
override val itemLongClickListener: View.OnLongClickListener? = null,
|
||||||
|
override val itemClickListener: View.OnClickListener? = null,
|
||||||
|
// val memberClickListener: View.OnClickListener? = null,
|
||||||
|
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||||
|
// val avatarCallback: TimelineEventController.AvatarCallback? = null,
|
||||||
|
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||||
|
val emojiTypeFace: Typeface? = null
|
||||||
|
) : AbsBaseMessageItem.Attributes
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/riotx_button_disabled_alpha12" android:state_enabled="false" />
|
||||||
|
<item android:color="@color/riotx_destructive_accent_alpha12" android:state_enabled="true" />
|
||||||
|
</selector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/riotx_disabled_accent" android:state_enabled="false" />
|
||||||
|
<item android:color="@color/button_destructive_enabled_text_color" />
|
||||||
|
</selector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/riotx_button_disabled_alpha12" android:state_enabled="false" />
|
||||||
|
<item android:color="@color/riotx_positive_accent_alpha12" android:state_enabled="true" />
|
||||||
|
</selector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/riotx_disabled_accent" android:state_enabled="false" />
|
||||||
|
<item android:color="@color/riotx_positive_accent" />
|
||||||
|
</selector>
|
14
vector/src/main/res/drawable/ic_shield_black.xml
Normal file
14
vector/src/main/res/drawable/ic_shield_black.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M12,21C12,21 21,17.2 21,11.5V4.85L12,2L3,4.85V11.5C3,17.2 12,21 12,21Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:fillColor="#2E2F32"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
18
vector/src/main/res/drawable/ic_shield_trusted.xml
Normal file
18
vector/src/main/res/drawable/ic_shield_trusted.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M12,21C12,21 21,17.2 21,11.5V4.85L12,2L3,4.85V11.5C3,17.2 12,21 12,21Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:fillColor="#03B381"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.2268,7.8065C17.6053,8.1718 17.6053,8.7639 17.2268,9.1291L11.4013,14.7502C11.0228,15.1154 10.4091,15.1154 10.0306,14.7502L10.0145,14.7342C10.0084,14.7286 10.0023,14.7229 9.9964,14.7171L7.3235,12.1381C6.926,11.7546 6.926,11.1328 7.3235,10.7493C7.7209,10.3658 8.3653,10.3658 8.7627,10.7493L10.7838,12.6995L15.8561,7.8065C16.2346,7.4413 16.8483,7.4413 17.2268,7.8065Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
11
vector/src/main/res/drawable/rounded_rect_shape_8.xml
Normal file
11
vector/src/main/res/drawable/rounded_rect_shape_8.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<size android:width="40dp" android:height="40dp"/>
|
||||||
|
|
||||||
|
<solid android:color="?vctr_list_header_background_color" />
|
||||||
|
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
|
||||||
|
</shape>
|
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:addStatesFromChildren="true"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.CheckableView
|
||||||
|
android:id="@+id/messageSelectedBackground"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignBottom="@+id/informationBottom"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="?riotx_highlighted_message_background" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/messageStartGuideline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="52dp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/messageEndGuideline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginEnd="52dp" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/viewStubContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_marginBottom="2dp"
|
||||||
|
android:layout_toStartOf="@id/messageEndGuideline"
|
||||||
|
android:layout_toEndOf="@id/messageStartGuideline"
|
||||||
|
android:background="@drawable/rounded_rect_shape_8"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messageVerificationRequestStub"
|
||||||
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout="@layout/item_timeline_event_verification_stub"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messageVerificationDoneStub"
|
||||||
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout="@layout/item_timeline_event_verification_done_stub"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/informationBottom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/viewStubContainer"
|
||||||
|
android:layout_toEndOf="@id/messageStartGuideline"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.flexbox.FlexboxLayout
|
||||||
|
android:id="@+id/reactionsContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
app:dividerDrawable="@drawable/reaction_divider"
|
||||||
|
app:flexWrap="wrap"
|
||||||
|
app:showDivider="middle"
|
||||||
|
tools:background="#F0E0F0"
|
||||||
|
tools:layout_height="40dp">
|
||||||
|
|
||||||
|
<!-- ReactionButtons will be added here in the code -->
|
||||||
|
<!--im.vector.riotx.features.reactions.widget.ReactionButton
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" /-->
|
||||||
|
|
||||||
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
|
||||||
|
<im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
|
android:id="@+id/readReceiptsView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="4dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVerificationDoneTitleTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:drawableStart="@drawable/ic_shield_trusted"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="@string/sas_verified" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVerificationDoneDetailTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="Alice (@alice:matrix.org)" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVerificationTitleTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:drawableStart="@drawable/ic_shield_black"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="@string/verification_request" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVerificationDetailTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="Alice (@alice:matrix.org)" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/itemVerificationButtonBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/sas_verification_verified_decline_button"
|
||||||
|
style="@style/VectorButtonStyleDestructive"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="@string/decline" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/sas_verification_verified_accept_button"
|
||||||
|
style="@style/VectorButtonStylePositive"
|
||||||
|
android:text="@string/accept" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVerificationStatusText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="@string/verification_request_you_accepted"
|
||||||
|
android:textColor="?attr/vctr_notice_secondary"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -123,6 +123,9 @@
|
|||||||
<!-- Button color -->
|
<!-- Button color -->
|
||||||
<color name="button_enabled_text_color">#FFFFFFFF</color>
|
<color name="button_enabled_text_color">#FFFFFFFF</color>
|
||||||
<color name="button_disabled_text_color">#FFFFFFFF</color>
|
<color name="button_disabled_text_color">#FFFFFFFF</color>
|
||||||
|
<color name="button_destructive_enabled_text_color">#FF4B55</color>
|
||||||
|
<color name="button_destructive_disabled_text_color">#FF4B55</color>
|
||||||
|
|
||||||
|
|
||||||
<!-- Link color -->
|
<!-- Link color -->
|
||||||
<color name="link_color_light">#368BD6</color>
|
<color name="link_color_light">#368BD6</color>
|
||||||
|
@ -7,6 +7,17 @@
|
|||||||
<color name="riotx_accent">#FF03B381</color>
|
<color name="riotx_accent">#FF03B381</color>
|
||||||
<color name="riotx_accent_alpha25">#3F03B381</color>
|
<color name="riotx_accent_alpha25">#3F03B381</color>
|
||||||
|
|
||||||
|
|
||||||
|
<color name="riotx_destructive_accent">#FFFF4B55</color>
|
||||||
|
<color name="riotx_destructive_accent_alpha12">#1EFF4B55</color>
|
||||||
|
|
||||||
|
|
||||||
|
<color name="riotx_positive_accent">#03B381</color>
|
||||||
|
<color name="riotx_disabled_accent">#61708B</color>
|
||||||
|
<color name="riotx_positive_accent_alpha12">#1E03B381</color>
|
||||||
|
<color name="riotx_button_disabled_alpha12">#1E61708B</color>
|
||||||
|
|
||||||
|
|
||||||
<color name="riotx_notice">#FFFF4B55</color>
|
<color name="riotx_notice">#FFFF4B55</color>
|
||||||
<color name="riotx_notice_secondary">#FF61708B</color>
|
<color name="riotx_notice_secondary">#FF61708B</color>
|
||||||
<color name="riotx_links">#FF368BD6</color>
|
<color name="riotx_links">#FF368BD6</color>
|
||||||
|
@ -142,4 +142,14 @@
|
|||||||
|
|
||||||
<string name="seen_by">Seen by</string>
|
<string name="seen_by">Seen by</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="verification_request">Verification Request</string>
|
||||||
|
<string name="verification_sent">Verification Sent</string>
|
||||||
|
<string name="verification_request_you_accepted">You accepted</string>
|
||||||
|
<string name="verification_request_other_accepted">%s accepted</string>
|
||||||
|
<string name="verification_request_you_cancelled">You cancelled</string>
|
||||||
|
<string name="verification_request_other_cancelled">%s cancelled</string>
|
||||||
|
<string name="verification_request_waiting">Waiting…</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -134,6 +134,23 @@
|
|||||||
<item name="android:textColor">@color/button_text_color_selector</item>
|
<item name="android:textColor">@color/button_text_color_selector</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="VectorButtonStyleDestructive" parent="Widget.MaterialComponents.Button.UnelevatedButton">
|
||||||
|
<item name="backgroundTint">@color/button_destructive_background_selector</item>
|
||||||
|
<item name="android:paddingLeft">16dp</item>
|
||||||
|
<item name="android:paddingRight">16dp</item>
|
||||||
|
<item name="android:minWidth">94dp</item>
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:textAllCaps">false</item>
|
||||||
|
<item name="android:textColor">@color/button_destructive_text_color_selector</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="VectorButtonStylePositive" parent="VectorButtonStyleDestructive">
|
||||||
|
<item name="backgroundTint">@color/button_positive_background_selector</item>
|
||||||
|
<item name="android:textColor">@color/button_positive_text_color_selector</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<!--Widget.AppCompat.Button.Borderless.Colored, which sets the text color to colorAccent,
|
<!--Widget.AppCompat.Button.Borderless.Colored, which sets the text color to colorAccent,
|
||||||
using colorControlHighlight as an overlay for focused and pressed states.-->
|
using colorControlHighlight as an overlay for focused and pressed states.-->
|
||||||
<style name="VectorButtonStyleText" parent="Widget.MaterialComponents.Button.TextButton">
|
<style name="VectorButtonStyleText" parent="Widget.MaterialComponents.Button.TextButton">
|
||||||
|
Loading…
Reference in New Issue
Block a user