mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-13 22:00:30 +05:30
feat: watch history pagination
This commit is contained in:
parent
0c39a25a4c
commit
6970bf6ee2
@ -46,12 +46,27 @@ object DatabaseHelper {
|
||||
}
|
||||
|
||||
// delete the first watch history entry if the limit is reached
|
||||
val watchHistory = Database.watchHistoryDao().getAll()
|
||||
if (watchHistory.size > maxHistorySize.toInt()) {
|
||||
Database.watchHistoryDao().delete(watchHistory.first())
|
||||
val historySize = Database.watchHistoryDao().getSize()
|
||||
if (historySize > maxHistorySize.toInt()) {
|
||||
Database.watchHistoryDao().delete(Database.watchHistoryDao().getOldest())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWatchHistoryPage(page: Int, pageSize: Int): List<WatchHistoryItem> {
|
||||
val watchHistoryDao = Database.watchHistoryDao()
|
||||
val historySize = watchHistoryDao.getSize()
|
||||
|
||||
if (historySize < pageSize * (page-1)) return emptyList()
|
||||
|
||||
val offset = historySize - (pageSize * page)
|
||||
val limit = if (offset < 0) {
|
||||
offset + pageSize
|
||||
} else {
|
||||
pageSize
|
||||
}
|
||||
return watchHistoryDao.getN(limit, maxOf(offset, 0)).reversed()
|
||||
}
|
||||
|
||||
suspend fun addToSearchHistory(searchHistoryItem: SearchHistoryItem) {
|
||||
Database.searchHistoryDao().insert(searchHistoryItem)
|
||||
|
||||
|
@ -12,6 +12,12 @@ interface WatchHistoryDao {
|
||||
@Query("SELECT * FROM watchHistoryItem")
|
||||
suspend fun getAll(): List<WatchHistoryItem>
|
||||
|
||||
@Query("SELECT * FROM watchHistoryItem LIMIT :limit OFFSET :offset")
|
||||
suspend fun getN(limit: Int, offset: Int): List<WatchHistoryItem>
|
||||
|
||||
@Query("SELECT COUNT(videoId) FROM watchHistoryItem")
|
||||
suspend fun getSize(): Int
|
||||
|
||||
@Query("SELECT * FROM watchHistoryItem WHERE videoId LIKE :videoId LIMIT 1")
|
||||
suspend fun findById(videoId: String): WatchHistoryItem?
|
||||
|
||||
@ -24,6 +30,9 @@ interface WatchHistoryDao {
|
||||
@Delete
|
||||
suspend fun delete(watchHistoryItem: WatchHistoryItem)
|
||||
|
||||
@Query("SELECT * FROM watchHistoryItem LIMIT 1 OFFSET 0")
|
||||
suspend fun getOldest(): WatchHistoryItem
|
||||
|
||||
@Query("DELETE FROM watchHistoryItem WHERE videoId = :id")
|
||||
suspend fun deleteByVideoId(id: String)
|
||||
|
||||
|
@ -24,9 +24,7 @@ class WatchHistoryAdapter(
|
||||
) :
|
||||
RecyclerView.Adapter<WatchHistoryViewHolder>() {
|
||||
|
||||
private var visibleCount = minOf(10, watchHistory.size)
|
||||
|
||||
override fun getItemCount() = visibleCount
|
||||
override fun getItemCount() = watchHistory.size
|
||||
|
||||
fun removeFromWatchHistory(position: Int) {
|
||||
val history = watchHistory[position]
|
||||
@ -34,16 +32,14 @@ class WatchHistoryAdapter(
|
||||
DatabaseHolder.Database.watchHistoryDao().delete(history)
|
||||
}
|
||||
watchHistory.removeAt(position)
|
||||
visibleCount--
|
||||
notifyItemRemoved(position)
|
||||
notifyItemRangeChanged(position, itemCount)
|
||||
}
|
||||
|
||||
fun showMoreItems() {
|
||||
val oldSize = visibleCount
|
||||
visibleCount += minOf(10, watchHistory.size - oldSize)
|
||||
if (visibleCount == oldSize) return
|
||||
notifyItemRangeInserted(oldSize, visibleCount)
|
||||
fun insertItems(items: List<WatchHistoryItem>) {
|
||||
val oldSize = itemCount
|
||||
this.watchHistory.addAll(items)
|
||||
notifyItemRangeInserted(oldSize, itemCount)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder {
|
||||
|
@ -40,6 +40,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.ceil
|
||||
|
||||
class WatchHistoryFragment : DynamicLayoutManagerFragment() {
|
||||
private var _binding: FragmentWatchHistoryBinding? = null
|
||||
@ -88,86 +90,86 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment() {
|
||||
_binding?.watchHistoryRecView?.updatePadding(bottom = if (it) 64f.dpToPx() else 0)
|
||||
}
|
||||
|
||||
val allHistory = runBlocking(Dispatchers.IO) {
|
||||
Database.watchHistoryDao().getAll().reversed()
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val history = withContext(Dispatchers.IO) {
|
||||
DatabaseHelper.getWatchHistoryPage(1, HISTORY_PAGE_SIZE)
|
||||
}
|
||||
|
||||
if (allHistory.isEmpty()) return
|
||||
if (history.isEmpty()) return@launch
|
||||
|
||||
binding.filterTypeTV.text =
|
||||
resources.getStringArray(R.array.filterOptions)[selectedTypeFilter]
|
||||
binding.filterStatusTV.text =
|
||||
resources.getStringArray(R.array.filterStatusOptions)[selectedStatusFilter]
|
||||
binding.filterTypeTV.text =
|
||||
resources.getStringArray(R.array.filterOptions)[selectedTypeFilter]
|
||||
binding.filterStatusTV.text =
|
||||
resources.getStringArray(R.array.filterStatusOptions)[selectedStatusFilter]
|
||||
|
||||
val watchPositionItem = arrayOf(getString(R.string.also_clear_watch_positions))
|
||||
val selected = booleanArrayOf(false)
|
||||
val watchPositionItem = arrayOf(getString(R.string.also_clear_watch_positions))
|
||||
val selected = booleanArrayOf(false)
|
||||
|
||||
binding.clear.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.clear_history)
|
||||
.setMultiChoiceItems(watchPositionItem, selected) { _, index, newValue ->
|
||||
selected[index] = newValue
|
||||
}
|
||||
.setPositiveButton(R.string.okay) { _, _ ->
|
||||
binding.historyContainer.isGone = true
|
||||
binding.historyEmpty.isVisible = true
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
Database.withTransaction {
|
||||
Database.watchHistoryDao().deleteAll()
|
||||
if (selected[0]) Database.watchPositionDao().deleteAll()
|
||||
binding.clear.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.clear_history)
|
||||
.setMultiChoiceItems(watchPositionItem, selected) { _, index, newValue ->
|
||||
selected[index] = newValue
|
||||
}
|
||||
.setPositiveButton(R.string.okay) { _, _ ->
|
||||
binding.historyContainer.isGone = true
|
||||
binding.historyEmpty.isVisible = true
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
Database.withTransaction {
|
||||
Database.watchHistoryDao().deleteAll()
|
||||
if (selected[0]) Database.watchPositionDao().deleteAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
binding.filterTypeTV.setOnClickListener {
|
||||
val filterOptions = resources.getStringArray(R.array.filterOptions)
|
||||
|
||||
BaseBottomSheet().apply {
|
||||
setSimpleItems(filterOptions.toList()) { index ->
|
||||
binding.filterTypeTV.text = filterOptions[index]
|
||||
selectedTypeFilter = index
|
||||
showWatchHistory(allHistory)
|
||||
}
|
||||
}.show(childFragmentManager)
|
||||
}
|
||||
|
||||
binding.filterStatusTV.setOnClickListener {
|
||||
val filterOptions = resources.getStringArray(R.array.filterStatusOptions)
|
||||
|
||||
BaseBottomSheet().apply {
|
||||
setSimpleItems(filterOptions.toList()) { index ->
|
||||
binding.filterStatusTV.text = filterOptions[index]
|
||||
selectedStatusFilter = index
|
||||
showWatchHistory(allHistory)
|
||||
}
|
||||
}.show(childFragmentManager)
|
||||
}
|
||||
|
||||
// manually restore the recyclerview state due to https://github.com/material-components/material-components-android/issues/3473
|
||||
binding.watchHistoryRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
recyclerViewState = binding.watchHistoryRecView.layoutManager?.onSaveInstanceState()
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
|
||||
showWatchHistory(allHistory)
|
||||
binding.filterTypeTV.setOnClickListener {
|
||||
val filterOptions = resources.getStringArray(R.array.filterOptions)
|
||||
|
||||
BaseBottomSheet().apply {
|
||||
setSimpleItems(filterOptions.toList()) { index ->
|
||||
binding.filterTypeTV.text = filterOptions[index]
|
||||
selectedTypeFilter = index
|
||||
showWatchHistory(history)
|
||||
}
|
||||
}.show(childFragmentManager)
|
||||
}
|
||||
|
||||
binding.filterStatusTV.setOnClickListener {
|
||||
val filterOptions = resources.getStringArray(R.array.filterStatusOptions)
|
||||
|
||||
BaseBottomSheet().apply {
|
||||
setSimpleItems(filterOptions.toList()) { index ->
|
||||
binding.filterStatusTV.text = filterOptions[index]
|
||||
selectedStatusFilter = index
|
||||
showWatchHistory(history)
|
||||
}
|
||||
}.show(childFragmentManager)
|
||||
}
|
||||
|
||||
// manually restore the recyclerview state due to https://github.com/material-components/material-components-android/issues/3473
|
||||
binding.watchHistoryRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
recyclerViewState = binding.watchHistoryRecView.layoutManager?.onSaveInstanceState()
|
||||
}
|
||||
})
|
||||
|
||||
showWatchHistory(history)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showWatchHistory(allHistory: List<WatchHistoryItem>) {
|
||||
val watchHistory = allHistory.filterByStatusAndWatchPosition()
|
||||
private fun showWatchHistory(history: List<WatchHistoryItem>) {
|
||||
val watchHistory = history.filterByStatusAndWatchPosition()
|
||||
|
||||
watchHistory.forEach {
|
||||
it.thumbnailUrl = ProxyHelper.rewriteUrl(it.thumbnailUrl)
|
||||
it.uploaderAvatar = ProxyHelper.rewriteUrl(it.uploaderAvatar)
|
||||
}
|
||||
|
||||
val watchHistoryAdapter = WatchHistoryAdapter(
|
||||
watchHistory.toMutableList()
|
||||
)
|
||||
val watchHistoryAdapter = WatchHistoryAdapter(watchHistory.toMutableList())
|
||||
|
||||
binding.playAll.setOnClickListener {
|
||||
PlayingQueue.resetToDefaults()
|
||||
@ -234,10 +236,17 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment() {
|
||||
|
||||
binding.watchHistoryRecView.addOnBottomReachedListener {
|
||||
if (isLoading) return@addOnBottomReachedListener
|
||||
|
||||
isLoading = true
|
||||
watchHistoryAdapter.showMoreItems()
|
||||
isLoading = false
|
||||
|
||||
lifecycleScope.launch {
|
||||
val newHistory = withContext(Dispatchers.IO) {
|
||||
val currentPage = ceil(watchHistoryAdapter.itemCount.toFloat() / HISTORY_PAGE_SIZE).toInt()
|
||||
DatabaseHelper.getWatchHistoryPage( currentPage + 1, HISTORY_PAGE_SIZE)
|
||||
}.filterByStatusAndWatchPosition()
|
||||
|
||||
watchHistoryAdapter.insertItems(newHistory)
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,4 +286,8 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val HISTORY_PAGE_SIZE = 10
|
||||
}
|
||||
}
|
||||
|
@ -112,10 +112,10 @@ class HomeViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private suspend fun loadWatchingFromDB(): List<StreamItem> {
|
||||
val videos = DatabaseHolder.Database.watchHistoryDao().getAll()
|
||||
val videos = DatabaseHelper.getWatchHistoryPage(1, 50)
|
||||
|
||||
return DatabaseHelper
|
||||
.filterUnwatched(videos.map { it.toStreamItem() })
|
||||
.reversed()
|
||||
.take(20)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user