diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 2cca3c013d..354591b618 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -75,6 +75,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction() data class RequestVerification(val userId: String) : RoomDetailAction() data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction() + data class TapOnFailedToDecrypt(val eventId: String) : RoomDetailAction() data class ReRequestKeys(val eventId: String) : RoomDetailAction() object SelectStickerAttachment : RoomDetailAction() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 4995c16bf9..a450e10be6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -86,6 +86,8 @@ import im.vector.matrix.android.api.session.widgets.model.WidgetType import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem 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.WithHeldCode import im.vector.riotx.R import im.vector.riotx.core.dialogs.ConfirmationDialogBuilder import im.vector.riotx.core.dialogs.withColoredButton @@ -340,6 +342,7 @@ class RoomDetailFragment @Inject constructor( is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) + is RoomDetailViewEvents.ShowE2EErrorMessage -> displayE2eError(it.withHeldCode) RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager() is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it) is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog() @@ -941,6 +944,20 @@ class RoomDetailFragment @Inject constructor( .show() } + private fun displayE2eError(withHeldCode: WithHeldCode?) { + val msgId = when (withHeldCode) { + WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted + WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified + WithHeldCode.UNAUTHORISED, + WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic + else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc + } + AlertDialog.Builder(requireActivity()) + .setMessage(msgId) + .setPositiveButton(R.string.ok, null) + .show() + } + private fun promptReasonToReportContent(action: EventSharedAction.ReportContentCustom) { val inflater = requireActivity().layoutInflater val layout = inflater.inflate(R.layout.dialog_report_content, null) @@ -1205,13 +1222,18 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction)) } - override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) { - if (messageContent is MessageVerificationRequestContent) { - roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) + override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View) { + when (messageContent) { + is MessageVerificationRequestContent -> { + roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) + } + is EncryptedEventContent -> { + roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) + } } } - override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean { + override fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) val roomId = roomDetailArgs.roomId diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index 560da2e116..6ed5373b58 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail import androidx.annotation.StringRes import im.vector.matrix.android.api.session.widgets.model.Widget +import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode import im.vector.riotx.core.platform.VectorViewEvents import im.vector.riotx.features.command.Command import java.io.File @@ -33,6 +34,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { data class ActionFailure(val action: RoomDetailAction, val throwable: Throwable) : RoomDetailViewEvents() data class ShowMessage(val message: String) : RoomDetailViewEvents() + data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents() data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 3fd383c5cd..7a3f77039e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isImageMessage import im.vector.matrix.android.api.session.events.model.isTextMessage @@ -59,6 +60,8 @@ import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import im.vector.matrix.rx.asObservable +import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R @@ -259,6 +262,7 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.RequestVerification -> handleRequestVerification(action) is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) + is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() is RoomDetailAction.StartCall -> handleStartCall(action) @@ -1034,6 +1038,19 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) { + room.getTimeLineEvent(action.eventId)?.let { + val code = when (it.root.mCryptoError) { + MXCryptoError.ErrorType.KEYS_WITHHELD -> { + WithHeldCode.fromCode(it.root.mCryptoErrorReason) + } + else -> null + } + + _viewEvents.post(RoomDetailViewEvents.ShowE2EErrorMessage(code)) + } + } + private fun handleReplyToOptions(action: RoomDetailAction.ReplyToOptions) { room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index e074af1da6..095340caee 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -26,7 +26,6 @@ import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.VisibilityState import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent -import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageFileContent import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent @@ -89,8 +88,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } interface BaseCallback { - fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) - fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean + fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View) + fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean } interface AvatarCallback { @@ -283,13 +282,16 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun getModels(): List> { buildCacheItemsIfNeeded() return modelCache - .map { - val eventModel = if (it == null || mergedHeaderItemFactory.isCollapsed(it.localId)) { + .map { cacheItemData -> + val eventModel = if (cacheItemData == null || mergedHeaderItemFactory.isCollapsed(cacheItemData.localId)) { null } else { - it.eventModel + cacheItemData.eventModel } - listOf(eventModel, it?.mergedHeaderModel, it?.formattedDayModel) + listOf(eventModel, + cacheItemData?.mergedHeaderModel, + cacheItemData?.formattedDayModel?.takeIf { eventModel != null || cacheItemData.mergedHeaderModel != null } + ) } .flatten() .filterNotNull() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index e77d9ec73f..3eeabd801b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -18,6 +18,9 @@ package im.vector.riotx.features.home.room.detail.timeline.action import android.view.View import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Success +import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.R import im.vector.riotx.core.epoxy.bottomsheet.BottomSheetQuickReactionsItem diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 6c192105d7..8aed581804 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -15,6 +15,8 @@ */ package im.vector.riotx.features.home.room.detail.timeline.action +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index 1c82dc0b1c..6a135d528d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -18,8 +18,9 @@ package im.vector.riotx.features.home.room.detail.timeline.factory import im.vector.matrix.android.api.session.crypto.MXCryptoError 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.timeline.TimelineEvent -import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.resources.ColorProvider @@ -31,6 +32,7 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformat import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod +import im.vector.riotx.features.settings.VectorPreferences import me.gujun.android.span.image import me.gujun.android.span.span import javax.inject.Inject @@ -41,7 +43,8 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat private val stringProvider: StringProvider, private val avatarSizeProvider: AvatarSizeProvider, private val drawableProvider: DrawableProvider, - private val attributesFactory: MessageItemAttributesFactory) { + private val attributesFactory: MessageItemAttributesFactory, + private val vectorPreferences: VectorPreferences) { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -52,29 +55,18 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat return when { EventType.ENCRYPTED == event.root.getClearType() -> { val cryptoError = event.root.mCryptoError -// val errorDescription = -// if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { -// stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) -// } else { -// // TODO i18n -// cryptoError?.name -// } - val colorFromAttribute = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) - val spannableStr = if(cryptoError == null) { + val spannableStr = if (vectorPreferences.hideE2ETechnicalErrors()) { + + val colorFromAttribute = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + if (cryptoError == null) { span(stringProvider.getString(R.string.encrypted_message)) { textStyle = "italic" textColor = colorFromAttribute } } else { - when(cryptoError) { + when (cryptoError) { MXCryptoError.ErrorType.KEYS_WITHHELD -> { -// val why = when (event.root.mCryptoErrorReason) { -// WithHeldCode.BLACKLISTED.value -> stringProvider.getString(R.string.crypto_error_withheld_blacklisted) -// WithHeldCode.UNVERIFIED.value -> stringProvider.getString(R.string.crypto_error_withheld_unverified) -// else -> stringProvider.getString(R.string.crypto_error_withheld_generic) -// } - //stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, why) span { apply { drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let { @@ -87,7 +79,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat } } } - else -> { + else -> { span { apply { drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let { @@ -99,20 +91,29 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat textColor = colorFromAttribute } } - } } + } + } else { + val errorDescription = + if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { + stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) + } else { + // TODO i18n + cryptoError?.name + } + val message = stringProvider.getString(R.string.encrypted_message).takeIf { cryptoError == null } + ?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) + span(message) { + textStyle = "italic" + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + } } -// val spannableStr = span(message) { -// textStyle = "italic" -// textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) -// } - // TODO This is not correct format for error, change it val informationData = messageInformationDataFactory.create(event, nextEvent) - val attributes = attributesFactory.create(null, informationData, callback) + val attributes = attributesFactory.create(event.root.content.toModel(), informationData, callback) return MessageTextItem_() .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(highlight) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 6616025110..e82531cef7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home.room.detail.timeline.factory +import im.vector.matrix.android.api.session.crypto.MXCryptoError 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.create.RoomCreateContent @@ -36,11 +37,15 @@ import im.vector.riotx.features.home.room.detail.timeline.item.MergedMembershipE import im.vector.riotx.features.home.room.detail.timeline.item.MergedMembershipEventsItem_ import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MergedUTDItem +import im.vector.riotx.features.home.room.detail.timeline.item.MergedUTDItem_ +import im.vector.riotx.features.settings.VectorPreferences import javax.inject.Inject -class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: AvatarRenderer, +class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, + private val avatarRenderer: AvatarRenderer, private val avatarSizeProvider: AvatarSizeProvider, - private val activeSessionHolder: ActiveSessionHolder) { + private val vectorPreferences: VectorPreferences) { private val collapsedEventIds = linkedSetOf() private val mergeItemCollapseStates = HashMap() @@ -58,7 +63,9 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av callback: TimelineEventController.Callback?, requestModelBuild: () -> Unit) : BasedMergedItem<*>? { - return if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE + return if (shouldMergedAsCannotDecryptGroup(event, nextEvent)) { + buildUTDMergedSummary(currentPosition, items, event, eventIdToHighlight, requestModelBuild, callback) + } else if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE && event.isRoomConfiguration(nextEvent.root.getClearContent()?.toModel()?.creator)) { // It's the first item before room.create // Collapse all room configuration events @@ -130,6 +137,89 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av } } + // Event should be UTD + // Next event should not + private fun shouldMergedAsCannotDecryptGroup(event: TimelineEvent, nextEvent: TimelineEvent?) : Boolean { + if (!vectorPreferences.hideE2ETechnicalErrors()) return false + // if event is not UTD return false + if (event.root.getClearType() != EventType.ENCRYPTED || event.root.mCryptoError != MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) return false + // At this point event cannot be decrypted + // Let's check if older event is not UTD + return nextEvent == null || nextEvent.root.getClearType() != EventType.ENCRYPTED + } + + + private fun buildUTDMergedSummary(currentPosition: Int, + items: List, + event: TimelineEvent, + eventIdToHighlight: String?, + requestModelBuild: () -> Unit, + callback: TimelineEventController.Callback?): MergedUTDItem_? { + + var prevEvent = if (currentPosition > 0) items[currentPosition - 1] else null + var tmpPos = currentPosition - 1 + val mergedEvents = ArrayList().also { it.add(event) } + + while (prevEvent != null + && prevEvent.root.getClearType() == EventType.ENCRYPTED + && prevEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { + mergedEvents.add(prevEvent) + tmpPos-- + prevEvent = if (tmpPos >= 0) items[tmpPos] else null + } + + if (mergedEvents.size < 3) return null + + var highlighted = false + val mergedData = ArrayList(mergedEvents.size) + mergedEvents.reversed() + .forEach { mergedEvent -> + if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { + highlighted = true + } + val senderAvatar = mergedEvent.senderAvatar + val senderName = mergedEvent.getDisambiguatedDisplayName() + val data = BasedMergedItem.Data( + userId = mergedEvent.root.senderId ?: "", + avatarUrl = senderAvatar, + memberName = senderName, + localId = mergedEvent.localId, + eventId = mergedEvent.root.eventId ?: "" + ) + mergedData.add(data) + } + val mergedEventIds = mergedEvents.map { it.localId } + // We try to find if one of the item id were used as mergeItemCollapseStates key + // => handle case where paginating from mergeable events and we get more + val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() + val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) + ?: true + val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } + if (isCollapsed) { + collapsedEventIds.addAll(mergedEventIds) + } else { + collapsedEventIds.removeAll(mergedEventIds) + } + val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } + + val attributes = MergedUTDItem.Attributes( + isCollapsed = isCollapsed, + mergeData = mergedData, + avatarRenderer = avatarRenderer, + onCollapsedStateChanged = { + mergeItemCollapseStates[event.localId] = it + requestModelBuild() + } + ) + return MergedUTDItem_() + .id(mergeId) + .leftGuideline(avatarSizeProvider.leftGuideline) + .highlighted(isCollapsed && highlighted) + .attributes(attributes) + .also { + it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) + } + } private fun buildRoomCreationMergedSummary(currentPosition: Int, items: List, event: TimelineEvent, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 7433b03408..7646fd37a5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -18,7 +18,6 @@ package im.vector.riotx.features.home.room.detail.timeline.helper import android.view.View -import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer @@ -34,7 +33,7 @@ class MessageItemAttributesFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val emojiCompatFontProvider: EmojiCompatFontProvider) { - fun create(messageContent: MessageContent?, + fun create(messageContent: Any?, informationData: MessageInformationData, callback: TimelineEventController.Callback?): AbsMessageItem.Attributes { return AbsMessageItem.Attributes( diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedUTDItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedUTDItem.kt new file mode 100644 index 0000000000..715d329168 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedUTDItem.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 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.ViewGroup +import android.widget.RelativeLayout +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +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_noinfo) +abstract class MergedUTDItem : BasedMergedItem() { + + @EpoxyAttribute + override lateinit var attributes: Attributes + + override fun getViewType() = STUB_ID + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.mergedTile.updateLayoutParams { + this.marginEnd = leftGuideline + } +// if (attributes.isCollapsed) { +// // Take the oldest data +// val data = distinctMergeData.lastOrNull() +// +// val summary = holder.expandView.resources.getString(R.string.room_created_summary_item, +// data?.memberName ?: data?.userId ?: "") +// holder.summaryView.text = summary +// holder.summaryView.visibility = View.VISIBLE +// holder.avatarView.visibility = View.VISIBLE +// if (data != null) { +// holder.avatarView.visibility = View.VISIBLE +// attributes.avatarRenderer.render(data.toMatrixItem(), holder.avatarView) +// } else { +// holder.avatarView.visibility = View.GONE +// } +// +// if (attributes.hasEncryptionEvent) { +// holder.encryptionTile.isVisible = true +// holder.encryptionTile.updateLayoutParams { +// this.marginEnd = leftGuideline +// } +// if (attributes.isEncryptionAlgorithmSecure) { +// holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled) +// holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) +// holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER +// holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( +// ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), +// null, null, null +// ) +// } else { +// holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled) +// holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description) +// holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( +// ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning), +// null, null, null +// ) +// } +// } else { +// holder.encryptionTile.isVisible = false +// } +// } else { +// holder.avatarView.visibility = View.INVISIBLE +// holder.summaryView.visibility = View.GONE +// holder.encryptionTile.isGone = true +// } + // No read receipt for this item + holder.readReceiptsView.isVisible = false + } + + class Holder : BasedMergedItem.Holder(STUB_ID) { + // val summaryView by bind(R.id.itemNoticeTextView) +// val avatarView by bind(R.id.itemNoticeAvatarView) + val mergedTile by bind(R.id.mergedUTDTile) +// +// val e2eTitleTextView by bind(R.id.itemVerificationDoneTitleTextView) +// val e2eTitleDescriptionView by bind(R.id.itemVerificationDoneDetailTextView) + } + + companion object { + private const val STUB_ID = R.id.messageContentMergedUTDStub + } + + data class Attributes( + override val isCollapsed: Boolean, + override val mergeData: List, + override val avatarRenderer: AvatarRenderer, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + override val onCollapsedStateChanged: (Boolean) -> Unit + ) : BasedMergedItem.Attributes +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 5f245c883d..9a427b6d9c 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -144,6 +144,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY" private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" + private const val SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS = "SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS" // analytics const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY" @@ -265,6 +266,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true) } + fun hideE2ETechnicalErrors(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS, true) + } + fun labAllowedExtendedLogging(): Boolean { return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false) } diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index 8d0d9bb513..f8d46bb04e 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -61,6 +61,13 @@ tools:layout_marginTop="240dp" tools:visibility="visible" /> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 525a7efa02..29033a2f23 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1734,6 +1734,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Name or ID (#example:matrix.org) Enable swipe to reply in timeline + Hide failed to decrypt technical details in timeline Link copied to clipboard @@ -2503,7 +2504,10 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming You cannot access this message Waiting for this message, this may take a while - You have been blocked - Session not trusted by sender - Sender purposely did not send the keys + Cannot Decrypt + Due to end-to-end encryption, you might need to wait for someone\'s message to arrive because the encryption keys were not properly sent to you. + You cannot access this message because you have been blocked by the sender + You cannot access this message because your session is not trusted by the sender + You cannot access this message because the sender purposely did not send the keys + Waiting for encryption history diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 2661568f77..0379d49d26 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -40,6 +40,10 @@ android:title="@string/labs_swipe_to_reply_in_timeline" /> + \ No newline at end of file