fix: Save the reading position if no post is fully visible (#941)

Previous code saved the reading position of a fully visible status. But
there are situations where no status is fully visible.

1. The user is in the middle of viewing a status longer than the screen
height, and the top/bottom of the status are off the top/bottom of the
screen.

2. The user has scrolled between two statuses. Collectively they are
longer than the screen height, and the top of one status is off the top
of the screen and the bottom of the other status is off the bottom of
the screen.

In both cases the user's reading position was not saved.

In these situations use the ID of the status closest to the bottom of
the screen, even if not fully visible. This should ensure the user never
missing anything.

Fixes #936
This commit is contained in:
Nik Clayton 2024-09-25 11:45:33 +02:00 committed by GitHub
parent 494246097d
commit 561c26ad4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 25 additions and 6 deletions

View File

@ -25,6 +25,8 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.core.content.ContextCompat
import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels
@ -39,6 +41,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BuildConfig
import app.pachli.R
import app.pachli.adapter.StatusBaseViewHolder
import app.pachli.components.timeline.util.isExpected
@ -522,7 +525,7 @@ class TimelineFragment :
}
}
R.id.action_load_newest -> {
Timber.d("Reload because user choose load newest menu item")
Timber.d("Reload because user chose load newest menu item")
viewModel.accept(InfallibleUiAction.LoadNewest)
refreshContent()
true
@ -532,15 +535,31 @@ class TimelineFragment :
}
/**
* Save [statusId] as the reading position. If null then the ID of the first completely visible
* status is used. It is the first status so that when performing a pull-refresh the
* previous first status always remains visible.
* Save [statusId] as the reading position. If null then the ID of the best status is used.
*
* The best status is the first completely visible status, if available. We assume the user
* has read this far, or will recognise it on return.
*
* However, there may not be a completely visible status. E.g., the user is viewing one
* status that is longer the the height of the screen, or the user is at the midpoint of
* two statuses that are each longer than half the height of the screen.
*
* In this case the best status is the last partially visible status, as we can assume the
* user has read this far.
*/
fun saveVisibleId(statusId: String? = null) {
val id = statusId ?: layoutManager.findFirstCompletelyVisibleItemPosition()
.takeIf { it != RecyclerView.NO_POSITION }
val id = statusId ?: (
layoutManager.findFirstCompletelyVisibleItemPosition()
.takeIf { it != RecyclerView.NO_POSITION }
?: layoutManager.findLastVisibleItemPosition()
.takeIf { it != RecyclerView.NO_POSITION }
)
?.let { adapter.snapshot().getOrNull(it)?.id }
if (BuildConfig.DEBUG && id == null) {
Toast.makeText(requireActivity(), "Could not find ID of item to save", LENGTH_LONG).show()
}
id?.let {
Timber.d("Saving ID: %s", it)
viewModel.accept(InfallibleUiAction.SaveVisibleId(visibleId = it))