Merge pull request #2165 from vector-im/feature/timeline_scroll_opti

Feature/timeline scroll opti
This commit is contained in:
Benoit Marty 2020-09-29 16:11:57 +02:00 committed by GitHub
commit ab74f6c1a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 13 deletions

View File

@ -6,6 +6,7 @@ Features ✨:
Improvements 🙌: Improvements 🙌:
- PIN code: request PIN code if phone has been locked - PIN code: request PIN code if phone has been locked
- Small optimisation of scrolling experience in timeline (#2114)
Bugfix 🐛: Bugfix 🐛:
- Fix Splash layout on small screens - Fix Splash layout on small screens

View File

@ -326,6 +326,7 @@ dependencies {
implementation 'com.jakewharton.rxbinding3:rxbinding-material:3.0.0' implementation 'com.jakewharton.rxbinding3:rxbinding-material:3.0.0'
implementation("com.airbnb.android:epoxy:$epoxy_version") implementation("com.airbnb.android:epoxy:$epoxy_version")
implementation "com.airbnb.android:epoxy-glide-preloading:$epoxy_version"
kapt "com.airbnb.android:epoxy-processor:$epoxy_version" kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "com.airbnb.android:epoxy-paging:$epoxy_version" implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
implementation 'com.airbnb.android:mvrx:1.3.0' implementation 'com.airbnb.android:mvrx:1.3.0'

View File

@ -53,6 +53,8 @@ import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.epoxy.addGlidePreloader
import com.airbnb.epoxy.glidePreloader
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
@ -75,6 +77,7 @@ import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.extensions.trackItemsVisibilityChange
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
@ -218,7 +221,8 @@ class RoomDetailFragment @Inject constructor(
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val notificationUtils: NotificationUtils, private val notificationUtils: NotificationUtils,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
private val matrixItemColorProvider: MatrixItemColorProvider private val matrixItemColorProvider: MatrixItemColorProvider,
private val imageContentRenderer: ImageContentRenderer
) : ) :
VectorBaseFragment(), VectorBaseFragment(),
TimelineEventController.Callback, TimelineEventController.Callback,
@ -921,6 +925,16 @@ class RoomDetailFragment @Inject constructor(
val touchHelper = ItemTouchHelper(swipeCallback) val touchHelper = ItemTouchHelper(swipeCallback)
touchHelper.attachToRecyclerView(recyclerView) touchHelper.attachToRecyclerView(recyclerView)
} }
recyclerView.addGlidePreloader(
epoxyController = timelineEventController,
requestManager = GlideApp.with(this),
preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ ->
imageContentRenderer.createGlideRequest(
epoxyModel.mediaData,
ImageContentRenderer.Mode.THUMBNAIL,
requestManager as GlideRequests
)
})
} }
private fun updateJumpToReadMarkerViewVisibility() { private fun updateJumpToReadMarkerViewVisibility() {

View File

@ -56,6 +56,8 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import javax.inject.Inject import javax.inject.Inject
private const val DEFAULT_PREFETCH_THRESHOLD = 30
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter, class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
@ -116,6 +118,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
private var unreadState: UnreadState = UnreadState.Unknown private var unreadState: UnreadState = UnreadState.Unknown
private var positionOfReadMarker: Int? = null private var positionOfReadMarker: Int? = null
private var eventIdToHighlight: String? = null private var eventIdToHighlight: String? = null
private var previousModelsSize = 0
var callback: Callback? = null var callback: Callback? = null
var timeline: Timeline? = null var timeline: Timeline? = null
@ -191,6 +194,29 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
models.add(position, readMarker) models.add(position, readMarker)
} }
} }
val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false
if (shouldAddBackwardPrefetch) {
val indexOfPrefetchBackward = (previousModelsSize - 1)
.coerceAtMost(models.size - DEFAULT_PREFETCH_THRESHOLD)
.coerceAtLeast(0)
val loadingItem = LoadingItem_()
.id("prefetch_backward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS)
models.add(indexOfPrefetchBackward, loadingItem)
}
val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false
if (shouldAddForwardPrefetch) {
val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(models.size - 1)
val loadingItem = LoadingItem_()
.id("prefetch_forward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
models.add(indexOfPrefetchForward, loadingItem)
}
previousModelsSize = models.size
} }
fun update(viewState: RoomDetailViewState) { fun update(viewState: RoomDetailViewState) {
@ -355,9 +381,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
return shouldAdd return shouldAdd
} }
/**
* Return true if added
*/
private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ { private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState -> return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) { if (visibilityState == VisibilityState.VISIBLE) {

View File

@ -33,6 +33,7 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequest import im.vector.app.core.glide.GlideRequest
import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.ui.model.Size import im.vector.app.core.ui.model.Size
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.isLocalFile import im.vector.app.core.utils.isLocalFile
@ -206,12 +207,14 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
.into(imageView) .into(imageView)
} }
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest<Drawable> { fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest<Drawable> {
return createGlideRequest(data, mode, GlideApp.with(imageView), size)
}
fun createGlideRequest(data: Data, mode: Mode, glideRequests: GlideRequests, size: Size = processSize(data, mode)): GlideRequest<Drawable> {
return if (data.elementToDecrypt != null) { return if (data.elementToDecrypt != null) {
// Encrypted image // Encrypted image
GlideApp glideRequests.load(data)
.with(imageView)
.load(data)
} else { } else {
// Clear image // Clear image
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
@ -223,15 +226,12 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
// Fallback to base url // Fallback to base url
?: data.url.takeIf { it?.startsWith("content://") == true } ?: data.url.takeIf { it?.startsWith("content://") == true }
GlideApp glideRequests
.with(imageView)
.load(resolvedUrl) .load(resolvedUrl)
.apply { .apply {
if (mode == Mode.THUMBNAIL) { if (mode == Mode.THUMBNAIL) {
error( error(
GlideApp glideRequests.load(resolveUrl(data))
.with(imageView)
.load(resolveUrl(data))
) )
} }
} }