mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 04:52:00 +03:00
E2e lab error display
This commit is contained in:
parent
f9d931960b
commit
36de891451
16 changed files with 393 additions and 45 deletions
|
@ -75,6 +75,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction()
|
data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction()
|
||||||
data class RequestVerification(val userId: String) : RoomDetailAction()
|
data class RequestVerification(val userId: String) : RoomDetailAction()
|
||||||
data class ResumeVerification(val transactionId: String, val otherUserId: 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()
|
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
|
||||||
|
|
||||||
object SelectStickerAttachment : RoomDetailAction()
|
object SelectStickerAttachment : RoomDetailAction()
|
||||||
|
|
|
@ -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.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.dialogs.ConfirmationDialogBuilder
|
import im.vector.riotx.core.dialogs.ConfirmationDialogBuilder
|
||||||
import im.vector.riotx.core.dialogs.withColoredButton
|
import im.vector.riotx.core.dialogs.withColoredButton
|
||||||
|
@ -340,6 +342,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
|
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
|
||||||
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
|
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
|
||||||
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
|
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
|
||||||
|
is RoomDetailViewEvents.ShowE2EErrorMessage -> displayE2eError(it.withHeldCode)
|
||||||
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
|
RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
|
||||||
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
|
is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
|
||||||
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
|
is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
|
||||||
|
@ -941,6 +944,20 @@ class RoomDetailFragment @Inject constructor(
|
||||||
.show()
|
.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) {
|
private fun promptReasonToReportContent(action: EventSharedAction.ReportContentCustom) {
|
||||||
val inflater = requireActivity().layoutInflater
|
val inflater = requireActivity().layoutInflater
|
||||||
val layout = inflater.inflate(R.layout.dialog_report_content, null)
|
val layout = inflater.inflate(R.layout.dialog_report_content, null)
|
||||||
|
@ -1205,13 +1222,18 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
|
roomDetailViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
|
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View) {
|
||||||
if (messageContent is MessageVerificationRequestContent) {
|
when (messageContent) {
|
||||||
|
is MessageVerificationRequestContent -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
|
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)
|
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
val roomId = roomDetailArgs.roomId
|
val roomId = roomDetailArgs.roomId
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
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.core.platform.VectorViewEvents
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -33,6 +34,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||||
data class ActionFailure(val action: RoomDetailAction, val throwable: Throwable) : RoomDetailViewEvents()
|
data class ActionFailure(val action: RoomDetailAction, val throwable: Throwable) : RoomDetailViewEvents()
|
||||||
|
|
||||||
data class ShowMessage(val message: String) : RoomDetailViewEvents()
|
data class ShowMessage(val message: String) : RoomDetailViewEvents()
|
||||||
|
data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents()
|
||||||
|
|
||||||
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||||
import im.vector.matrix.android.api.query.QueryStringValue
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
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.api.util.toOptional
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
import im.vector.matrix.rx.asObservable
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.WithHeldCode
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -259,6 +262,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||||
|
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
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) {
|
private fun handleReplyToOptions(action: RoomDetailAction.ReplyToOptions) {
|
||||||
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.VisibilityState
|
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.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.MessageFileContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
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 {
|
interface BaseCallback {
|
||||||
fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View)
|
fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View)
|
||||||
fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean
|
fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AvatarCallback {
|
interface AvatarCallback {
|
||||||
|
@ -283,13 +282,16 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
private fun getModels(): List<EpoxyModel<*>> {
|
private fun getModels(): List<EpoxyModel<*>> {
|
||||||
buildCacheItemsIfNeeded()
|
buildCacheItemsIfNeeded()
|
||||||
return modelCache
|
return modelCache
|
||||||
.map {
|
.map { cacheItemData ->
|
||||||
val eventModel = if (it == null || mergedHeaderItemFactory.isCollapsed(it.localId)) {
|
val eventModel = if (cacheItemData == null || mergedHeaderItemFactory.isCollapsed(cacheItemData.localId)) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
it.eventModel
|
cacheItemData.eventModel
|
||||||
}
|
}
|
||||||
listOf(eventModel, it?.mergedHeaderModel, it?.formattedDayModel)
|
listOf(eventModel,
|
||||||
|
cacheItemData?.mergedHeaderModel,
|
||||||
|
cacheItemData?.formattedDayModel?.takeIf { eventModel != null || cacheItemData.mergedHeaderModel != null }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.flatten()
|
.flatten()
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
|
|
|
@ -18,6 +18,9 @@ package im.vector.riotx.features.home.room.detail.timeline.action
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.mvrx.Success
|
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.EmojiCompatFontProvider
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.bottomsheet.BottomSheetQuickReactionsItem
|
import im.vector.riotx.core.epoxy.bottomsheet.BottomSheetQuickReactionsItem
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
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.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
|
|
@ -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.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
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.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
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.helper.MessageItemAttributesFactory
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
|
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.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import me.gujun.android.span.image
|
import me.gujun.android.span.image
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -41,7 +43,8 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
private val drawableProvider: DrawableProvider,
|
private val drawableProvider: DrawableProvider,
|
||||||
private val attributesFactory: MessageItemAttributesFactory) {
|
private val attributesFactory: MessageItemAttributesFactory,
|
||||||
|
private val vectorPreferences: VectorPreferences) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
|
@ -52,29 +55,18 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
||||||
return when {
|
return when {
|
||||||
EventType.ENCRYPTED == event.root.getClearType() -> {
|
EventType.ENCRYPTED == event.root.getClearType() -> {
|
||||||
val cryptoError = event.root.mCryptoError
|
val cryptoError = event.root.mCryptoError
|
||||||
// val errorDescription =
|
|
||||||
// if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
val spannableStr = if (vectorPreferences.hideE2ETechnicalErrors()) {
|
||||||
// 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 colorFromAttribute = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||||
val spannableStr = if(cryptoError == null) {
|
if (cryptoError == null) {
|
||||||
span(stringProvider.getString(R.string.encrypted_message)) {
|
span(stringProvider.getString(R.string.encrypted_message)) {
|
||||||
textStyle = "italic"
|
textStyle = "italic"
|
||||||
textColor = colorFromAttribute
|
textColor = colorFromAttribute
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when(cryptoError) {
|
when (cryptoError) {
|
||||||
MXCryptoError.ErrorType.KEYS_WITHHELD -> {
|
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 {
|
span {
|
||||||
apply {
|
apply {
|
||||||
drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let {
|
drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let {
|
||||||
|
@ -99,20 +91,29 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
||||||
textColor = colorFromAttribute
|
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 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_()
|
return MessageTextItem_()
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.factory
|
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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent
|
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.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.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
|
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 avatarSizeProvider: AvatarSizeProvider,
|
||||||
private val activeSessionHolder: ActiveSessionHolder) {
|
private val vectorPreferences: VectorPreferences) {
|
||||||
|
|
||||||
private val collapsedEventIds = linkedSetOf<Long>()
|
private val collapsedEventIds = linkedSetOf<Long>()
|
||||||
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
||||||
|
@ -58,7 +63,9 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
requestModelBuild: () -> Unit)
|
requestModelBuild: () -> Unit)
|
||||||
: BasedMergedItem<*>? {
|
: 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)) {
|
&& event.isRoomConfiguration(nextEvent.root.getClearContent()?.toModel<RoomCreateContent>()?.creator)) {
|
||||||
// It's the first item before room.create
|
// It's the first item before room.create
|
||||||
// Collapse all room configuration events
|
// 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,
|
private fun buildRoomCreationMergedSummary(currentPosition: Int,
|
||||||
items: List<TimelineEvent>,
|
items: List<TimelineEvent>,
|
||||||
event: TimelineEvent,
|
event: TimelineEvent,
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.helper
|
package im.vector.riotx.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|
||||||
import im.vector.riotx.EmojiCompatFontProvider
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
@ -34,7 +33,7 @@ class MessageItemAttributesFactory @Inject constructor(
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
private val emojiCompatFontProvider: EmojiCompatFontProvider) {
|
private val emojiCompatFontProvider: EmojiCompatFontProvider) {
|
||||||
|
|
||||||
fun create(messageContent: MessageContent?,
|
fun create(messageContent: Any?,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?): AbsMessageItem.Attributes {
|
callback: TimelineEventController.Callback?): AbsMessageItem.Attributes {
|
||||||
return AbsMessageItem.Attributes(
|
return AbsMessageItem.Attributes(
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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_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_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_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
|
// analytics
|
||||||
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
|
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)
|
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 {
|
fun labAllowedExtendedLogging(): Boolean {
|
||||||
return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
|
return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,13 @@
|
||||||
tools:layout_marginTop="240dp"
|
tools:layout_marginTop="240dp"
|
||||||
tools:visibility="visible" />
|
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>
|
</FrameLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
|
@ -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>
|
|
@ -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="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_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>
|
<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_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="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_utd">Cannot Decrypt</string>
|
||||||
<string name="crypto_error_withheld_unverified">Session not trusted by sender</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_generic">Sender purposely did not send the keys</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>
|
</resources>
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
android:title="@string/labs_swipe_to_reply_in_timeline" />
|
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>-->
|
<!--</im.vector.riotx.core.preference.VectorPreferenceCategory>-->
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in a new issue