fix timeline jumping (#4471)

Two things changed here:

The check for `positionStart`only in `onItemRangeInserted` is not always
correct - we only want to jump up when something is inserted at the top,
if we already are at the top.

`enablePlaceholders = false` has unintended side effects - the
recyclerview adapter sometimes receives an "onItemRangeRemoved" followed
by an "onItemRangeInserted", instead of just "onItemRangeChanged".

Together they should make sure the timelines stay were they are.
This commit is contained in:
Konrad Pozniak 2024-05-31 13:41:22 +02:00 committed by GitHub
parent 83403ebb58
commit 0483440381
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 22 additions and 30 deletions

View File

@ -45,8 +45,7 @@ class AccountMediaViewModel @Inject constructor(
val media = Pager( val media = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = LOAD_AT_ONCE, pageSize = LOAD_AT_ONCE,
prefetchDistance = LOAD_AT_ONCE * 2, prefetchDistance = LOAD_AT_ONCE * 2
enablePlaceholders = false
), ),
pagingSourceFactory = { pagingSourceFactory = {
AccountMediaPagingSource( AccountMediaPagingSource(

View File

@ -162,7 +162,8 @@ class ConversationsFragment :
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0 && adapter.itemCount != itemCount) { val firstPos = (binding.recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (firstPos == 0 && positionStart == 0 && adapter.itemCount != itemCount) {
binding.recyclerView.post { binding.recyclerView.post {
if (getView() != null) { if (getView() != null) {
binding.recyclerView.scrollBy(0, Utils.dpToPx(requireContext(), -30)) binding.recyclerView.scrollBy(0, Utils.dpToPx(requireContext(), -30))

View File

@ -45,8 +45,7 @@ class ConversationsViewModel @Inject constructor(
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
val conversationFlow = Pager( val conversationFlow = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 30, pageSize = 30
enablePlaceholders = false
), ),
remoteMediator = ConversationsRemoteMediator(api, database, accountManager), remoteMediator = ConversationsRemoteMediator(api, database, accountManager),
pagingSourceFactory = { pagingSourceFactory = {

View File

@ -41,8 +41,7 @@ class DomainBlocksRepository @Inject constructor(
val domainPager = Pager( val domainPager = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = PAGE_SIZE, pageSize = PAGE_SIZE,
initialLoadSize = PAGE_SIZE, initialLoadSize = PAGE_SIZE
enablePlaceholders = false
), ),
remoteMediator = DomainBlocksRemoteMediator(api, this), remoteMediator = DomainBlocksRemoteMediator(api, this),
pagingSourceFactory = factory pagingSourceFactory = factory

View File

@ -40,8 +40,7 @@ class DraftsViewModel @Inject constructor(
val drafts = Pager( val drafts = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 20, pageSize = 20
enablePlaceholders = false
), ),
pagingSourceFactory = { pagingSourceFactory = {
database.draftDao().draftsPagingSource( database.draftDao().draftsPagingSource(

View File

@ -27,8 +27,7 @@ class FollowedTagsViewModel @Inject constructor(
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
val pager = Pager( val pager = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 100, pageSize = 100
enablePlaceholders = false
), ),
remoteMediator = FollowedTagsRemoteMediator(api, this), remoteMediator = FollowedTagsRemoteMediator(api, this),
pagingSourceFactory = { pagingSourceFactory = {

View File

@ -229,7 +229,8 @@ class NotificationsFragment :
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0 && adapter.itemCount != itemCount) { val firstPos = (binding.recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (firstPos == 0 && positionStart == 0 && adapter.itemCount != itemCount) {
binding.recyclerView.post { binding.recyclerView.post {
if (getView() != null) { if (getView() != null) {
binding.recyclerView.scrollBy( binding.recyclerView.scrollBy(

View File

@ -95,8 +95,7 @@ class NotificationsViewModel @Inject constructor(
val notifications = refreshTrigger.flatMapLatest { val notifications = refreshTrigger.flatMapLatest {
Pager( Pager(
config = PagingConfig( config = PagingConfig(
pageSize = LOAD_AT_ONCE, pageSize = LOAD_AT_ONCE
enablePlaceholders = false
), ),
remoteMediator = remoteMediator, remoteMediator = remoteMediator,
pagingSourceFactory = { pagingSourceFactory = {

View File

@ -79,8 +79,7 @@ class ReportViewModel @Inject constructor(
initialKey = statusId, initialKey = statusId,
config = PagingConfig( config = PagingConfig(
pageSize = 20, pageSize = 20,
initialLoadSize = 20, initialLoadSize = 20
enablePlaceholders = false
), ),
pagingSourceFactory = { StatusesPagingSource(accountId, mastodonApi) } pagingSourceFactory = { StatusesPagingSource(accountId, mastodonApi) }
).flow ).flow

View File

@ -40,8 +40,7 @@ class ScheduledStatusViewModel @Inject constructor(
val data = Pager( val data = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 20, pageSize = 20,
initialLoadSize = 20, initialLoadSize = 20
enablePlaceholders = false
), ),
pagingSourceFactory = pagingSourceFactory pagingSourceFactory = pagingSourceFactory
).flow ).flow

View File

@ -90,8 +90,7 @@ class SearchViewModel @Inject constructor(
val statusesFlow = Pager( val statusesFlow = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = DEFAULT_LOAD_SIZE, pageSize = DEFAULT_LOAD_SIZE,
initialLoadSize = DEFAULT_LOAD_SIZE, initialLoadSize = DEFAULT_LOAD_SIZE
enablePlaceholders = false
), ),
pagingSourceFactory = statusesPagingSourceFactory pagingSourceFactory = statusesPagingSourceFactory
).flow ).flow
@ -100,8 +99,7 @@ class SearchViewModel @Inject constructor(
val accountsFlow = Pager( val accountsFlow = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = DEFAULT_LOAD_SIZE, pageSize = DEFAULT_LOAD_SIZE,
initialLoadSize = DEFAULT_LOAD_SIZE, initialLoadSize = DEFAULT_LOAD_SIZE
enablePlaceholders = false
), ),
pagingSourceFactory = accountsPagingSourceFactory pagingSourceFactory = accountsPagingSourceFactory
).flow ).flow
@ -110,8 +108,7 @@ class SearchViewModel @Inject constructor(
val hashtagsFlow = Pager( val hashtagsFlow = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = DEFAULT_LOAD_SIZE, pageSize = DEFAULT_LOAD_SIZE,
initialLoadSize = DEFAULT_LOAD_SIZE, initialLoadSize = DEFAULT_LOAD_SIZE
enablePlaceholders = false
), ),
pagingSourceFactory = hashtagsPagingSourceFactory pagingSourceFactory = hashtagsPagingSourceFactory
).flow ).flow

View File

@ -252,7 +252,8 @@ class TimelineFragment :
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0 && adapter.itemCount != itemCount) { val firstPos = (binding.recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (firstPos == 0 && positionStart == 0 && adapter.itemCount != itemCount) {
binding.recyclerView.post { binding.recyclerView.post {
if (getView() != null) { if (getView() != null) {
if (isSwipeToRefreshEnabled) { if (isSwipeToRefreshEnabled) {

View File

@ -86,8 +86,7 @@ class CachedTimelineViewModel @Inject constructor(
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
override val statuses = Pager( override val statuses = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = LOAD_AT_ONCE, pageSize = LOAD_AT_ONCE
enablePlaceholders = false
), ),
remoteMediator = CachedTimelineRemoteMediator(accountManager, api, db), remoteMediator = CachedTimelineRemoteMediator(accountManager, api, db),
pagingSourceFactory = { pagingSourceFactory = {

View File

@ -88,8 +88,7 @@ class NetworkTimelineViewModel @Inject constructor(
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
override val statuses = Pager( override val statuses = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = LOAD_AT_ONCE, pageSize = LOAD_AT_ONCE
enablePlaceholders = false
), ),
pagingSourceFactory = { pagingSourceFactory = {
NetworkTimelinePagingSource( NetworkTimelinePagingSource(

View File

@ -26,6 +26,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
@ -76,7 +77,8 @@ class TrendingTagsFragment :
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0 && adapter.itemCount != itemCount) { val firstPos = (binding.recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (firstPos == 0 && positionStart == 0 && adapter.itemCount != itemCount) {
binding.recyclerView.post { binding.recyclerView.post {
if (getView() != null) { if (getView() != null) {
binding.recyclerView.scrollBy( binding.recyclerView.scrollBy(