Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions app/src/main/java/com/github/libretube/db/DatabaseHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,22 @@ 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]
runBlocking(Dispatchers.IO) {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
.setNegativeButton(R.string.cancel, null)
.show()
}

binding.filterTypeTV.setOnClickListener {
val filterOptions = resources.getStringArray(R.array.filterOptions)
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.filterTypeTV.text = filterOptions[index]
selectedTypeFilter = index
showWatchHistory(history)
}
}.show(childFragmentManager)
}

BaseBottomSheet().apply {
setSimpleItems(filterOptions.toList()) { index ->
binding.filterStatusTV.text = filterOptions[index]
selectedStatusFilter = index
showWatchHistory(allHistory)
}
}.show(childFragmentManager)
}
binding.filterStatusTV.setOnClickListener {
val filterOptions = resources.getStringArray(R.array.filterStatusOptions)

// 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()
BaseBottomSheet().apply {
setSimpleItems(filterOptions.toList()) { index ->
binding.filterStatusTV.text = filterOptions[index]
selectedStatusFilter = index
showWatchHistory(history)
}
}.show(childFragmentManager)
}
})

showWatchHistory(allHistory)
// 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()
Expand Down Expand Up @@ -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
}
}
}
}
Expand Down Expand Up @@ -277,4 +286,8 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment() {
super.onDestroyView()
_binding = null
}

companion object {
private const val HISTORY_PAGE_SIZE = 10
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down