Save the user's reading position in the home timeline (#3614)

- Add a field to AccountEntity to hold the reading position
- Provide a method to save in the viewmodel to save the position
- Save the position when TimelineFragment pauses

Does not restore the position yet.
This commit is contained in:
Nik Clayton 2023-05-08 13:57:17 +02:00 committed by GitHub
parent 0c02dd18bf
commit 367240a612
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1050 additions and 5 deletions

File diff suppressed because it is too large Load Diff

View File

@ -77,7 +77,6 @@ import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
@ -578,6 +577,17 @@ class TimelineFragment :
private var talkBackWasEnabled = false private var talkBackWasEnabled = false
override fun onPause() {
super.onPause()
(binding.recyclerView.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()?.let { position ->
if (position != RecyclerView.NO_POSITION) {
adapter.snapshot().getOrNull(position)?.id?.let { statusId ->
viewModel.saveReadingPosition(statusId)
}
}
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val a11yManager = val a11yManager =

View File

@ -289,6 +289,14 @@ class CachedTimelineViewModel @Inject constructor(
} }
} }
override fun saveReadingPosition(statusId: String) {
accountManager.activeAccount?.let { account ->
Log.d(TAG, "Saving position at: $statusId")
account.lastVisibleHomeTimelineStatusId = statusId
accountManager.saveAccount(account)
}
}
override suspend fun invalidate() { override suspend fun invalidate() {
// invalidating when we don't have statuses yet can cause empty timelines because it cancels the network load // invalidating when we don't have statuses yet can cause empty timelines because it cancels the network load
if (db.timelineDao().getStatusCount(accountManager.activeAccount!!.id) > 0) { if (db.timelineDao().getStatusCount(accountManager.activeAccount!!.id) > 0) {
@ -297,6 +305,7 @@ class CachedTimelineViewModel @Inject constructor(
} }
companion object { companion object {
private const val TAG = "CachedTimelineViewModel"
private const val MAX_STATUSES_IN_CACHE = 1000 private const val MAX_STATUSES_IN_CACHE = 1000
} }
} }

View File

@ -255,6 +255,10 @@ class NetworkTimelineViewModel @Inject constructor(
} }
} }
override fun saveReadingPosition(statusId: String) {
/** Does nothing for non-cached timelines */
}
override suspend fun invalidate() { override suspend fun invalidate() {
currentSource?.invalidate() currentSource?.invalidate()
} }

View File

@ -182,6 +182,9 @@ abstract class TimelineViewModel(
abstract fun clearWarning(status: StatusViewData.Concrete) abstract fun clearWarning(status: StatusViewData.Concrete)
/** Saves the user's reading position so it can be restored later */
abstract fun saveReadingPosition(statusId: String)
/** Triggered when currently displayed data must be reloaded. */ /** Triggered when currently displayed data must be reloaded. */
protected abstract suspend fun invalidate() protected abstract suspend fun invalidate()

View File

@ -82,7 +82,13 @@ data class AccountEntity(
var pushPubKey: String = "", var pushPubKey: String = "",
var pushPrivKey: String = "", var pushPrivKey: String = "",
var pushAuth: String = "", var pushAuth: String = "",
var pushServerKey: String = "" var pushServerKey: String = "",
/**
* ID of the status at the top of the visible list in the home timeline when the
* user navigated away.
*/
var lastVisibleHomeTimelineStatusId: String? = null
) { ) {
val identifier: String val identifier: String

View File

@ -16,6 +16,7 @@
package com.keylesspalace.tusky.db; package com.keylesspalace.tusky.db;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.room.AutoMigration;
import androidx.room.Database; import androidx.room.Database;
import androidx.room.RoomDatabase; import androidx.room.RoomDatabase;
import androidx.room.migration.Migration; import androidx.room.migration.Migration;
@ -29,9 +30,20 @@ import java.io.File;
/** /**
* DB version & declare DAO * DB version & declare DAO
*/ */
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, @Database(
TimelineAccountEntity.class, ConversationEntity.class entities = {
}, version = 48) DraftEntity.class,
AccountEntity.class,
InstanceEntity.class,
TimelineStatusEntity.class,
TimelineAccountEntity.class,
ConversationEntity.class
},
version = 49,
autoMigrations = {
@AutoMigration(from = 48, to = 49)
}
)
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
public abstract AccountDao accountDao(); public abstract AccountDao accountDao();