mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-27 03:49:04 +03:00
Incoming DM verification handling in timeline
This commit is contained in:
parent
02f03e6b23
commit
0776a301ea
32 changed files with 963 additions and 107 deletions
|
@ -27,4 +27,3 @@ class ReferencesAggregatedSummary(
|
|||
val sourceEvents: List<String>,
|
||||
val localEchos: List<String>
|
||||
)
|
||||
|
||||
|
|
|
@ -17,6 +17,3 @@ internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, tx
|
|||
this.eventId = txID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -63,4 +63,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
|
||||
object ClearSendQueue : 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")
|
||||
}
|
||||
|
||||
override fun onTimelineItemAction(itemAction: RoomDetailAction) {
|
||||
roomDetailViewModel.handle(itemAction)
|
||||
}
|
||||
|
||||
override fun onRoomCreateLinkClicked(url: String) {
|
||||
permalinkHandler.launch(requireContext(), url, object : NavigateToRoomInterceptor {
|
||||
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.internal.crypto.attachments.toElementToDecrypt
|
||||
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.unwrap
|
||||
import im.vector.riotx.R
|
||||
|
@ -177,6 +178,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||
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() {
|
||||
session.rx()
|
||||
.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.epoxy.LoadingItem_
|
||||
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.UnreadState
|
||||
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 onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
||||
fun onEditedDecorationClicked(informationData: MessageInformationData)
|
||||
|
||||
// TODO move all callbacks to this?
|
||||
fun onTimelineItemAction(itemAction: RoomDetailAction)
|
||||
}
|
||||
|
||||
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.isTextMessage
|
||||
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.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.model.message.*
|
||||
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.getLastMessageContent
|
||||
|
@ -172,6 +169,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||
eventHtmlRenderer.get().render(messageContent.formattedBody
|
||||
?: messageContent.body)
|
||||
} else if (messageContent is MessageVerificationRequestContent) {
|
||||
stringProvider.getString(R.string.verification_request)
|
||||
} else {
|
||||
messageContent?.body
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.home.room.detail.timeline.factory
|
||||
|
||||
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.EventType
|
||||
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,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val avatarSizeProvider: AvatarSizeProvider) {
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val session: Session) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
highlight: Boolean,
|
||||
|
@ -46,7 +48,8 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
|
|||
sendState = event.root.sendState,
|
||||
avatarUrl = event.senderAvatar,
|
||||
memberName = event.getDisambiguatedDisplayName(),
|
||||
showInformation = false
|
||||
showInformation = false,
|
||||
sentByMe = event.root.senderId == session.myUserId
|
||||
)
|
||||
val attributes = NoticeItem.Attributes(
|
||||
avatarRenderer = avatarRenderer,
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.text.style.ClickableSpan
|
|||
import android.text.style.ForegroundColorSpan
|
||||
import android.view.View
|
||||
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.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.*
|
||||
|
@ -64,7 +65,8 @@ class MessageItemFactory @Inject constructor(
|
|||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||
private val defaultItemFactory: DefaultItemFactory,
|
||||
private val noticeItemFactory: NoticeItemFactory,
|
||||
private val avatarSizeProvider: AvatarSizeProvider) {
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val session: Session) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
|
@ -97,14 +99,15 @@ class MessageItemFactory @Inject constructor(
|
|||
// val all = event.root.toContent()
|
||||
// val ev = all.toModel<Event>()
|
||||
return when (messageContent) {
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageFileContent -> buildFileMessageItem(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
|
@ -193,7 +241,8 @@ class MessageItemFactory @Inject constructor(
|
|||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||
val thumbnailData = ImageContentRenderer.Data(
|
||||
filename = messageContent.body,
|
||||
url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
|
||||
url = messageContent.videoInfo?.thumbnailFile?.url
|
||||
?: messageContent.videoInfo?.thumbnailUrl,
|
||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||
height = messageContent.videoInfo?.height,
|
||||
maxHeight = maxHeight,
|
||||
|
|
|
@ -28,7 +28,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||
private val encryptedItemFactory: EncryptedItemFactory,
|
||||
private val noticeItemFactory: NoticeItemFactory,
|
||||
private val defaultItemFactory: DefaultItemFactory,
|
||||
private val roomCreateItemFactory: RoomCreateItemFactory) {
|
||||
private val roomCreateItemFactory: RoomCreateItemFactory,
|
||||
private val verificationConclusionItemFactory: VerificationItemFactory) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
|
@ -66,13 +67,15 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||
}
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC -> {
|
||||
// These events are filtered from timeline in normal case
|
||||
// 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)
|
||||
|
|
|
@ -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.MESSAGE,
|
||||
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)
|
||||
else -> {
|
||||
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.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.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.resources.ColorProvider
|
||||
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.ReactionInfoData
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -86,7 +90,15 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
.map {
|
||||
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.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
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
|
||||
|
||||
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
|
||||
lateinit var attributes: Attributes
|
||||
|
@ -45,24 +47,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||
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) {
|
||||
super.bind(holder)
|
||||
if (attributes.informationData.showInformation) {
|
||||
|
@ -94,60 +78,12 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||
holder.avatarImageView.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) {
|
||||
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) {
|
||||
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
|
||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
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(
|
||||
val avatarSize: Int,
|
||||
val informationData: MessageInformationData,
|
||||
val avatarRenderer: AvatarRenderer,
|
||||
val colorProvider: ColorProvider,
|
||||
val itemLongClickListener: View.OnLongClickListener? = null,
|
||||
val itemClickListener: View.OnClickListener? = null,
|
||||
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,
|
||||
val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
|
||||
val avatarCallback: TimelineEventController.AvatarCallback? = null,
|
||||
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = 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 im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.session.room.VerificationState
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
|
@ -33,7 +34,14 @@ data class MessageInformationData(
|
|||
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||
val hasBeenEdited: 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
|
||||
|
||||
@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 -->
|
||||
<color name="button_enabled_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 -->
|
||||
<color name="link_color_light">#368BD6</color>
|
||||
|
|
|
@ -7,6 +7,17 @@
|
|||
<color name="riotx_accent">#FF03B381</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_secondary">#FF61708B</color>
|
||||
<color name="riotx_links">#FF368BD6</color>
|
||||
|
|
|
@ -142,4 +142,14 @@
|
|||
|
||||
<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>
|
||||
|
|
|
@ -134,6 +134,23 @@
|
|||
<item name="android:textColor">@color/button_text_color_selector</item>
|
||||
</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,
|
||||
using colorControlHighlight as an overlay for focused and pressed states.-->
|
||||
<style name="VectorButtonStyleText" parent="Widget.MaterialComponents.Button.TextButton">
|
||||
|
|
Loading…
Reference in a new issue