mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +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 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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<EncryptedEventContent>(), informationData, callback)
|
||||
return MessageTextItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.highlighted(highlight)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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_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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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="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>
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue