E2e lab error display

This commit is contained in:
Valere 2020-05-19 09:44:34 +02:00 committed by Benoit Marty
parent f9d931960b
commit 36de891451
16 changed files with 393 additions and 45 deletions

View file

@ -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()

View file

@ -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) {
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

View file

@ -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()

View file

@ -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)
}

View file

@ -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<EpoxyModel<*>> {
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()

View file

@ -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

View file

@ -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

View file

@ -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,16 +55,11 @@ 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 spannableStr = if (vectorPreferences.hideE2ETechnicalErrors()) {
val colorFromAttribute = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
val spannableStr = if(cryptoError == null) {
if (cryptoError == null) {
span(stringProvider.getString(R.string.encrypted_message)) {
textStyle = "italic"
textColor = colorFromAttribute
@ -69,12 +67,6 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
} else {
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 {
@ -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<EncryptedEventContent>(), informationData, callback)
return MessageTextItem_()
.leftGuideline(avatarSizeProvider.leftGuideline)
.highlighted(highlight)

View file

@ -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<Long>()
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
@ -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<RoomCreateContent>()?.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<TimelineEvent>,
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<TimelineEvent>().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<BasedMergedItem.Data>(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<TimelineEvent>,
event: TimelineEvent,

View file

@ -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(

View file

@ -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<MergedUTDItem.Holder>() {
@EpoxyAttribute
override lateinit var attributes: Attributes
override fun getViewType() = STUB_ID
override fun bind(holder: Holder) {
super.bind(holder)
holder.mergedTile.updateLayoutParams<RelativeLayout.LayoutParams> {
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<RelativeLayout.LayoutParams> {
// 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<TextView>(R.id.itemNoticeTextView)
// val avatarView by bind<ImageView>(R.id.itemNoticeAvatarView)
val mergedTile by bind<ViewGroup>(R.id.mergedUTDTile)
//
// val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
// val e2eTitleDescriptionView by bind<TextView>(R.id.itemVerificationDoneDetailTextView)
}
companion object {
private const val STUB_ID = R.id.messageContentMergedUTDStub
}
data class Attributes(
override val isCollapsed: Boolean,
override val mergeData: List<Data>,
override val avatarRenderer: AvatarRenderer,
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
override val onCollapsedStateChanged: (Boolean) -> Unit
) : BasedMergedItem.Attributes
}

View file

@ -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)
}

View file

@ -61,6 +61,13 @@
tools:layout_marginTop="240dp"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageContentMergedUTDStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_merged_utd_stub"
tools:layout_marginTop="240dp"
tools:visibility="visible" />
</FrameLayout>
<ImageView

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<LinearLayout
android:id="@+id/mergedUTDTile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="vertical"
android:layout_gravity="center"
android:layout_marginTop="2dp"
android:layout_marginEnd="52dp"
android:layout_marginBottom="2dp"
android:background="@drawable/rounded_rect_shape_8"
android:padding="8dp">
<TextView
android:id="@+id/itemVerificationDoneTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:drawableStart="@drawable/ic_clock"
android:drawableTint="?riotx_text_secondary"
android:drawablePadding="2dp"
android:gravity="start"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
android:text="@string/notice_crypto_unable_to_decrypt_merged" />
<TextView
android:id="@+id/itemVerificationDoneDetailTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
android:textStyle="italic"
android:text="@string/notice_crypto_unable_to_decrypt_friendly_desc" />
<TextView
android:id="@+id/itemMergedExpandTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="2dp"
android:paddingLeft="8dp"
android:paddingTop="4dp"
android:paddingRight="8dp"
android:paddingBottom="4dp"
android:visibility="gone"
android:text="@string/merged_events_expand"
android:textColor="?attr/colorAccent"
android:textSize="14sp"
android:textStyle="italic" />
</LinearLayout>
<View
android:id="@+id/itemMergedSeparatorView"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/mergedUTDTile"
android:layout_marginTop="4dp"
android:background="?attr/riotx_header_panel_background" />
</RelativeLayout>

View file

@ -1734,6 +1734,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
<string name="labs_hide_technical_e2e_in_timeline">Hide failed to decrypt technical details in timeline</string>
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
@ -2503,7 +2504,10 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="notice_crypto_unable_to_decrypt_final">You cannot access this message</string>
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>
<string name="crypto_error_withheld_blacklisted">You have been blocked</string>
<string name="crypto_error_withheld_unverified">Session not trusted by sender</string>
<string name="crypto_error_withheld_generic">Sender purposely did not send the keys</string>
<string name="crypto_utd">Cannot Decrypt</string>
<string name="notice_crypto_unable_to_decrypt_friendly_desc">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.</string>
<string name="crypto_error_withheld_blacklisted">You cannot access this message because you have been blocked by the sender</string>
<string name="crypto_error_withheld_unverified">You cannot access this message because your session is not trusted by the sender</string>
<string name="crypto_error_withheld_generic">You cannot access this message because the sender purposely did not send the keys</string>
<string name="notice_crypto_unable_to_decrypt_merged">Waiting for encryption history</string>
</resources>

View file

@ -40,6 +40,10 @@
android:title="@string/labs_swipe_to_reply_in_timeline" />
<im.vector.riotx.core.preference.VectorSwitchPreference
android:defaultValue="true"
android:key="SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS"
android:title="@string/labs_hide_technical_e2e_in_timeline" />
<!--</im.vector.riotx.core.preference.VectorPreferenceCategory>-->
</androidx.preference.PreferenceScreen>