From 0a0330a48c746f2e312f4ae48b3399bb159b6070 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 29 Sep 2020 19:29:31 +0200 Subject: [PATCH 1/3] UTD : when reaching UTD and invite state event, stop back pagination --- .../timeline/TimelineEventController.kt | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 56de0f7829..90074db773 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -50,6 +50,11 @@ import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -64,6 +69,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val timelineItemFactory: TimelineItemFactory, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val mergedHeaderItemFactory: MergedHeaderItemFactory, + private val session: Session, @TimelineEventControllerHandler private val backgroundHandler: Handler ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor { @@ -115,6 +121,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val modelCache = arrayListOf() private var currentSnapshot: List = emptyList() private var inSubmitList: Boolean = false + private var hasReachedInvite: Boolean = false + private var hasUTD: Boolean = false private var unreadState: UnreadState = UnreadState.Unknown private var positionOfReadMarker: Int? = null private var eventIdToHighlight: String? = null @@ -267,7 +275,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val timelineModels = getModels() add(timelineModels) - + if (hasReachedInvite && hasUTD) { + return + } // Avoid displaying two loaders if there is no elements between them val showBackwardsLoader = !showingForwardLoader || timelineModels.isNotEmpty() // We can hide the loader but still add the item to controller so it can trigger backwards pagination @@ -327,6 +337,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } private fun buildCacheItemsIfNeeded() = synchronized(modelCache) { + hasUTD = false + hasReachedInvite = false + if (modelCache.isEmpty()) { return } @@ -342,13 +355,21 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun buildCacheItem(currentPosition: Int, items: List): CacheItemData { val event = items[currentPosition] val nextEvent = items.nextOrNull(currentPosition) - val date = event.root.localDateTime() - val nextDate = nextEvent?.root?.localDateTime() - val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() + if (hasReachedInvite && hasUTD) { + return CacheItemData(event.localId, event.root.eventId, null, null, null) + } + updateUTDStates(event, nextEvent) val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, callback).also { it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } + val addDaySeparator = if (hasReachedInvite && hasUTD) { + true + } else { + val date = event.root.localDateTime() + val nextDate = nextEvent?.root?.localDateTime() + date.toLocalDate() != nextDate?.toLocalDate() + } val mergedHeaderModel = mergedHeaderItemFactory.create(event, nextEvent = nextEvent, items = items, @@ -372,6 +393,24 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } + private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) { + if (event.root.type == EventType.STATE_ROOM_MEMBER + && event.root.stateKey == session.myUserId) { + val content = event.root.content.toModel() + if (content?.membership == Membership.INVITE) { + hasReachedInvite = true + } else if (content?.membership == Membership.JOIN) { + val prevContent = event.root.resolvedPrevContent().toModel() + if (prevContent?.membership?.isActive() == false) { + hasReachedInvite = true + } + } + } + if (nextEvent?.root?.getClearType() == EventType.ENCRYPTED) { + hasUTD = true + } + } + /** * Return true if added */ From f0272fd2831bd9a411d1d83ce1dc2e0e5e4cc129 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 30 Sep 2020 15:23:46 +0200 Subject: [PATCH 2/3] UTD pagination: clean up and add developer settings to enable complete history --- .../timeline/TimelineEventController.kt | 5 + .../factory/MergedHeaderItemFactory.kt | 89 +----------- .../detail/timeline/item/MergedUTDItem.kt | 127 ------------------ .../features/settings/VectorPreferences.kt | 6 +- vector/src/main/res/values/strings.xml | 1 + .../xml/vector_settings_advanced_settings.xml | 6 + .../src/main/res/xml/vector_settings_labs.xml | 7 - 7 files changed, 17 insertions(+), 224 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedUTDItem.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 90074db773..bddc7fa126 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer +import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -64,6 +65,7 @@ import javax.inject.Inject private const val DEFAULT_PREFETCH_THRESHOLD = 30 class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter, + private val vectorPreferences: VectorPreferences, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, private val timelineItemFactory: TimelineItemFactory, @@ -394,6 +396,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) { + if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) { + return + } if (event.root.type == EventType.STATE_ROOM_MEMBER && event.root.stateKey == session.myUserId) { val content = event.root.content.toModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 08f93b23cd..35db7fe469 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -31,9 +31,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEve import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_ import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_ -import im.vector.app.features.home.room.detail.timeline.item.MergedUTDItem -import im.vector.app.features.home.room.detail.timeline.item.MergedUTDItem_ -import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -41,14 +38,12 @@ import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent -import timber.log.Timber import javax.inject.Inject class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val avatarRenderer: AvatarRenderer, private val avatarSizeProvider: AvatarSizeProvider, - private val roomSummaryHolder: RoomSummaryHolder, - private val vectorPreferences: VectorPreferences) { + private val roomSummaryHolder: RoomSummaryHolder) { private val collapsedEventIds = linkedSetOf() private val mergeItemCollapseStates = HashMap() @@ -66,10 +61,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde callback: TimelineEventController.Callback?, requestModelBuild: () -> Unit) : BasedMergedItem<*>? { - return if (shouldMergedAsCannotDecryptGroup(event, nextEvent)) { - Timber.v("## MERGE: Candidate for merge, top event ${event.eventId}") - buildUTDMergedSummary(currentPosition, items, event, eventIdToHighlight, /*requestModelBuild,*/ callback) - } else if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE + return if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE && event.isRoomConfiguration(nextEvent.root.getClearContent()?.toModel()?.creator)) { // It's the first item before room.create // Collapse all room configuration events @@ -144,83 +136,6 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde } } - // Event should be UTD - // Next event should not - private fun shouldMergedAsCannotDecryptGroup(event: TimelineEvent, nextEvent: TimelineEvent?): Boolean { - if (!vectorPreferences.mergeUTDinTimeline()) return false - // if event is not UTD return false - if (!isEventUTD(event)) return false - // At this point event cannot be decrypted - // Let's check if older event is not UTD - return nextEvent == null || !isEventUTD(event) - } - - private fun isEventUTD(event: TimelineEvent): Boolean { - return event.root.getClearType() == EventType.ENCRYPTED && !event.root.isRedacted() - } - - private fun buildUTDMergedSummary(currentPosition: Int, - items: List, - event: TimelineEvent, - eventIdToHighlight: String?, - // requestModelBuild: () -> Unit, - callback: TimelineEventController.Callback?): MergedUTDItem_? { - Timber.v("## MERGE: buildUTDMergedSummary from position $currentPosition") - var prevEvent = items.prevOrNull(currentPosition) - var tmpPos = currentPosition - 1 - val mergedEvents = ArrayList().also { it.add(event) } - - while (prevEvent != null && isEventUTD(prevEvent)) { - mergedEvents.add(prevEvent) - tmpPos-- - prevEvent = if (tmpPos >= 0) items[tmpPos] else null - } - - Timber.v("## MERGE: buildUTDMergedSummary merge group size ${mergedEvents.size}") - if (mergedEvents.size < 3) return null - - var highlighted = false - val mergedData = ArrayList(mergedEvents.size) - mergedEvents.reversed() - .forEach { mergedEvent -> - if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { - highlighted = true - } - val senderAvatar = mergedEvent.senderInfo.avatarUrl - val senderName = mergedEvent.senderInfo.disambiguatedDisplayName - val data = BasedMergedItem.Data( - userId = mergedEvent.root.senderId ?: "", - avatarUrl = senderAvatar, - memberName = senderName, - localId = mergedEvent.localId, - eventId = mergedEvent.root.eventId ?: "", - isDirectRoom = isDirectRoom() - ) - mergedData.add(data) - } - val mergedEventIds = mergedEvents.map { it.localId } - - collapsedEventIds.addAll(mergedEventIds) - - val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } - - val attributes = MergedUTDItem.Attributes( - isCollapsed = true, - mergeData = mergedData, - avatarRenderer = avatarRenderer, - onCollapsedStateChanged = {} - ) - return MergedUTDItem_() - .id(mergeId) - .big(mergedEventIds.size > 5) - .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(highlighted) - .attributes(attributes) - .also { - it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) - } - } - private fun buildRoomCreationMergedSummary(currentPosition: Int, items: List, event: TimelineEvent, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedUTDItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedUTDItem.kt deleted file mode 100644 index 9c72d53641..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedUTDItem.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.app.features.home.room.detail.timeline.item - -import android.util.TypedValue -import android.view.ViewGroup -import android.widget.LinearLayout -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.app.R -import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.TimelineEventController - -@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) -abstract class MergedUTDItem : BasedMergedItem() { - - @EpoxyAttribute - override lateinit var attributes: Attributes - - @EpoxyAttribute - var big: Boolean? = false - - override fun getViewType() = STUB_ID - - override fun bind(holder: Holder) { - super.bind(holder) - - holder.mergedTile.updateLayoutParams { - this.marginEnd = leftGuideline - if (big == true) { - this.height = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 800f, - holder.view.context.resources.displayMetrics - ).toInt() - } else { - this.height = LinearLayout.LayoutParams.WRAP_CONTENT - } - } - -// if (attributes.isCollapsed) { -// // Take the oldest data -// val data = distinctMergeData.lastOrNull() -// -// val summary = holder.expandView.resources.getString(R.string.room_created_summary_item, -// data?.memberName ?: data?.userId ?: "") -// holder.summaryView.text = summary -// holder.summaryView.visibility = View.VISIBLE -// holder.avatarView.visibility = View.VISIBLE -// if (data != null) { -// holder.avatarView.visibility = View.VISIBLE -// attributes.avatarRenderer.render(data.toMatrixItem(), holder.avatarView) -// } else { -// holder.avatarView.visibility = View.GONE -// } -// -// if (attributes.hasEncryptionEvent) { -// holder.encryptionTile.isVisible = true -// holder.encryptionTile.updateLayoutParams { -// this.marginEnd = leftGuideline -// } -// if (attributes.isEncryptionAlgorithmSecure) { -// holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled) -// holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) -// holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER -// holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( -// ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), -// null, null, null -// ) -// } else { -// holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled) -// holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description) -// holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( -// ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning), -// null, null, null -// ) -// } -// } else { -// holder.encryptionTile.isVisible = false -// } -// } else { -// holder.avatarView.visibility = View.INVISIBLE -// holder.summaryView.visibility = View.GONE -// holder.encryptionTile.isGone = true -// } - // No read receipt for this item - holder.readReceiptsView.isVisible = false - } - - class Holder : BasedMergedItem.Holder(STUB_ID) { - // val summaryView by bind(R.id.itemNoticeTextView) -// val avatarView by bind(R.id.itemNoticeAvatarView) - val mergedTile by bind(R.id.mergedUTDTile) -// -// val e2eTitleTextView by bind(R.id.itemVerificationDoneTitleTextView) -// val e2eTitleDescriptionView by bind(R.id.itemVerificationDoneDetailTextView) - } - - companion object { - private const val STUB_ID = R.id.messageContentMergedUTDStub - } - - data class Attributes( - override val isCollapsed: Boolean, - override val mergeData: List, - override val avatarRenderer: AvatarRenderer, - override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, - override val onCollapsedStateChanged: (Boolean) -> Unit - ) : BasedMergedItem.Attributes -} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 9ccb8d7031..abe16e7e90 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -153,7 +153,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" // SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS - private const val SETTINGS_LABS_MERGE_E2E_ERRORS = "SETTINGS_LABS_MERGE_E2E_ERRORS" + private const val SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM = "SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM" const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" // analytics @@ -285,8 +285,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true) } - fun mergeUTDinTimeline(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_MERGE_E2E_ERRORS, false) + fun labShowCompleteHistoryInEncryptedRoom(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM, false) } fun labAllowedExtendedLogging(): Boolean { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 68d42e8356..a29959e90e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1710,6 +1710,7 @@ The suggestion failed to be sent (%s) Show hidden events in timeline + "Show complete history in encrypted rooms" Direct Messages diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 6d8de815d8..e136ed0f80 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -16,6 +16,12 @@ android:key="SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY" android:title="@string/settings_labs_show_hidden_events_in_timeline" /> + + - - - - Date: Wed, 30 Sep 2020 18:11:30 +0200 Subject: [PATCH 3/3] Changelog and small improvement --- CHANGES.md | 2 +- .../java/im/vector/app/features/settings/VectorPreferences.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9348717fb5..a5b9e43142 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in Element 1.0.9 (2020-XX-XX) =================================================== Features ✨: - - + - Hide encrypted history (before user is invited). Can be shown if wanted in developer settings Improvements 🙌: - Wording differentiation for direct rooms (#2176) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index abe16e7e90..5e9d8d1ac0 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -286,7 +286,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun labShowCompleteHistoryInEncryptedRoom(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM, false) + return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM, false) } fun labAllowedExtendedLogging(): Boolean {