Skip to content

Commit 70bd87a

Browse files
authored
Merge pull request #7169 from Bnyro/master
refactor: move watch history logic to view model
2 parents a33c99e + 3108361 commit 70bd87a

File tree

5 files changed

+191
-161
lines changed

5 files changed

+191
-161
lines changed

app/src/main/java/com/github/libretube/db/DatabaseHelper.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,10 @@ object DatabaseHelper {
9090
}
9191

9292
suspend fun filterByWatchStatus(
93-
streams: List<WatchHistoryItem>,
93+
watchHistoryItem: WatchHistoryItem,
9494
unfinished: Boolean = true
95-
): List<WatchHistoryItem> {
96-
return streams.filter {
97-
unfinished xor isVideoWatched(it.videoId, it.duration ?: 0)
98-
}
95+
): Boolean {
96+
return unfinished xor isVideoWatched(watchHistoryItem.videoId, watchHistoryItem.duration ?: 0)
9997
}
10098

10199
fun filterByStatusAndWatchPosition(

app/src/main/java/com/github/libretube/ui/adapters/WatchHistoryAdapter.kt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,11 @@ import com.github.libretube.util.TextUtils
2222
import kotlinx.coroutines.CoroutineScope
2323
import kotlinx.coroutines.Dispatchers
2424
import kotlinx.coroutines.launch
25-
import kotlinx.coroutines.runBlocking
2625
import kotlinx.coroutines.withContext
2726

2827
class WatchHistoryAdapter :
2928
ListAdapter<WatchHistoryItem, WatchHistoryViewHolder>(DiffUtilItemCallback()) {
3029

31-
fun removeFromWatchHistory(position: Int) {
32-
val history = getItem(position)
33-
runBlocking(Dispatchers.IO) {
34-
DatabaseHolder.Database.watchHistoryDao().delete(history)
35-
}
36-
val updatedList = currentList.toMutableList().also {
37-
it.removeAt(position)
38-
}
39-
submitList(updatedList)
40-
}
41-
42-
fun insertItems(items: List<WatchHistoryItem>) {
43-
val updatedList = currentList.toMutableList().also {
44-
it.addAll(items)
45-
}
46-
submitList(updatedList)
47-
}
48-
4930
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder {
5031
val layoutInflater = LayoutInflater.from(parent.context)
5132
val binding = VideoRowBinding.inflate(layoutInflater, parent, false)

app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt

Lines changed: 61 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,48 @@ package com.github.libretube.ui.fragments
22

33
import android.content.res.Configuration
44
import android.os.Bundle
5-
import android.os.Handler
6-
import android.os.Looper
75
import android.os.Parcelable
86
import android.view.View
9-
import androidx.core.os.postDelayed
107
import androidx.core.view.isGone
118
import androidx.core.view.isVisible
129
import androidx.core.view.updatePadding
1310
import androidx.fragment.app.activityViewModels
11+
import androidx.fragment.app.viewModels
1412
import androidx.lifecycle.lifecycleScope
1513
import androidx.recyclerview.widget.GridLayoutManager
1614
import androidx.recyclerview.widget.RecyclerView
1715
import androidx.room.withTransaction
1816
import com.github.libretube.R
19-
import com.github.libretube.constants.PreferenceKeys
2017
import com.github.libretube.databinding.FragmentWatchHistoryBinding
21-
import com.github.libretube.db.DatabaseHelper
2218
import com.github.libretube.db.DatabaseHolder.Database
2319
import com.github.libretube.db.obj.WatchHistoryItem
2420
import com.github.libretube.extensions.ceilHalf
2521
import com.github.libretube.extensions.dpToPx
2622
import com.github.libretube.extensions.setOnDismissListener
2723
import com.github.libretube.helpers.NavBarHelper
2824
import com.github.libretube.helpers.NavigationHelper
29-
import com.github.libretube.helpers.PreferenceHelper
3025
import com.github.libretube.ui.adapters.WatchHistoryAdapter
3126
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
3227
import com.github.libretube.ui.extensions.addOnBottomReachedListener
3328
import com.github.libretube.ui.extensions.setupFragmentAnimation
3429
import com.github.libretube.ui.models.CommonPlayerViewModel
30+
import com.github.libretube.ui.models.WatchHistoryModel
3531
import com.github.libretube.ui.sheets.BaseBottomSheet
3632
import com.github.libretube.util.PlayingQueue
3733
import com.google.android.material.dialog.MaterialAlertDialogBuilder
3834
import kotlinx.coroutines.Dispatchers
3935
import kotlinx.coroutines.launch
40-
import kotlinx.coroutines.runBlocking
41-
import kotlinx.coroutines.withContext
42-
import kotlin.math.ceil
4336

4437
class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watch_history) {
4538
private var _binding: FragmentWatchHistoryBinding? = null
4639
private val binding get() = _binding!!
4740

48-
private val handler = Handler(Looper.getMainLooper())
4941
private val commonPlayerViewModel: CommonPlayerViewModel by activityViewModels()
50-
private var isLoading = false
5142
private var recyclerViewState: Parcelable? = null
5243

44+
private val viewModel: WatchHistoryModel by viewModels()
5345
private val watchHistoryAdapter = WatchHistoryAdapter()
5446

55-
private var selectedStatusFilter = PreferenceHelper.getInt(
56-
PreferenceKeys.SELECTED_HISTORY_STATUS_FILTER,
57-
0
58-
)
59-
set(value) {
60-
PreferenceHelper.putInt(PreferenceKeys.SELECTED_HISTORY_STATUS_FILTER, value)
61-
field = value
62-
}
63-
private var selectedTypeFilter = PreferenceHelper.getInt(
64-
PreferenceKeys.SELECTED_HISTORY_TYPE_FILTER,
65-
0
66-
)
67-
set(value) {
68-
PreferenceHelper.putInt(PreferenceKeys.SELECTED_HISTORY_TYPE_FILTER, value)
69-
field = value
70-
}
71-
7247
override fun setLayoutManagers(gridItems: Int) {
7348
_binding?.watchHistoryRecView?.layoutManager =
7449
GridLayoutManager(context, gridItems.ceilHalf())
@@ -83,7 +58,8 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
8358
}
8459

8560
binding.watchHistoryRecView.setOnDismissListener { position ->
86-
watchHistoryAdapter.removeFromWatchHistory(position)
61+
val item = viewModel.filteredWatchHistory.value?.getOrNull(position) ?: return@setOnDismissListener
62+
viewModel.removeFromHistory(item)
8763
}
8864

8965
// observe changes to indicate if the history is empty
@@ -107,133 +83,85 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
10783
}
10884
})
10985

110-
lifecycleScope.launch {
111-
val history = withContext(Dispatchers.IO) {
112-
DatabaseHelper.getWatchHistoryPage(1, HISTORY_PAGE_SIZE)
113-
}
114-
115-
if (history.isEmpty()) return@launch
86+
binding.filterTypeTV.text =
87+
resources.getStringArray(R.array.filterOptions)[viewModel.selectedTypeFilter]
88+
binding.filterStatusTV.text =
89+
resources.getStringArray(R.array.filterStatusOptions)[viewModel.selectedStatusFilter]
11690

117-
binding.filterTypeTV.text =
118-
resources.getStringArray(R.array.filterOptions)[selectedTypeFilter]
119-
binding.filterStatusTV.text =
120-
resources.getStringArray(R.array.filterStatusOptions)[selectedStatusFilter]
91+
val watchPositionItem = arrayOf(getString(R.string.also_clear_watch_positions))
92+
val selected = booleanArrayOf(false)
12193

122-
val watchPositionItem = arrayOf(getString(R.string.also_clear_watch_positions))
123-
val selected = booleanArrayOf(false)
124-
125-
binding.clear.setOnClickListener {
126-
MaterialAlertDialogBuilder(requireContext())
127-
.setTitle(R.string.clear_history)
128-
.setMultiChoiceItems(watchPositionItem, selected) { _, index, newValue ->
129-
selected[index] = newValue
130-
}
131-
.setPositiveButton(R.string.okay) { _, _ ->
132-
binding.historyContainer.isGone = true
133-
binding.historyEmpty.isVisible = true
134-
lifecycleScope.launch(Dispatchers.IO) {
135-
Database.withTransaction {
136-
Database.watchHistoryDao().deleteAll()
137-
if (selected[0]) Database.watchPositionDao().deleteAll()
138-
}
94+
binding.clear.setOnClickListener {
95+
MaterialAlertDialogBuilder(requireContext())
96+
.setTitle(R.string.clear_history)
97+
.setMultiChoiceItems(watchPositionItem, selected) { _, index, newValue ->
98+
selected[index] = newValue
99+
}
100+
.setPositiveButton(R.string.okay) { _, _ ->
101+
binding.historyContainer.isGone = true
102+
binding.historyEmpty.isVisible = true
103+
lifecycleScope.launch(Dispatchers.IO) {
104+
Database.withTransaction {
105+
Database.watchHistoryDao().deleteAll()
106+
if (selected[0]) Database.watchPositionDao().deleteAll()
139107
}
140108
}
141-
.setNegativeButton(R.string.cancel, null)
142-
.show()
143-
}
144-
145-
binding.filterTypeTV.setOnClickListener {
146-
val filterOptions = resources.getStringArray(R.array.filterOptions)
147-
148-
BaseBottomSheet().apply {
149-
setSimpleItems(filterOptions.toList()) { index ->
150-
binding.filterTypeTV.text = filterOptions[index]
151-
selectedTypeFilter = index
152-
showWatchHistory(history)
153-
}
154-
}.show(childFragmentManager)
155-
}
156-
157-
binding.filterStatusTV.setOnClickListener {
158-
val filterOptions = resources.getStringArray(R.array.filterStatusOptions)
109+
}
110+
.setNegativeButton(R.string.cancel, null)
111+
.show()
112+
}
159113

160-
BaseBottomSheet().apply {
161-
setSimpleItems(filterOptions.toList()) { index ->
162-
binding.filterStatusTV.text = filterOptions[index]
163-
selectedStatusFilter = index
164-
showWatchHistory(history)
165-
}
166-
}.show(childFragmentManager)
167-
}
114+
binding.filterTypeTV.setOnClickListener {
115+
val filterOptions = resources.getStringArray(R.array.filterOptions)
168116

169-
showWatchHistory(history)
117+
BaseBottomSheet().apply {
118+
setSimpleItems(filterOptions.toList()) { index ->
119+
binding.filterTypeTV.text = filterOptions[index]
120+
viewModel.selectedTypeFilter = index
121+
}
122+
}.show(childFragmentManager)
170123
}
171124

172-
if (NavBarHelper.getStartFragmentId(requireContext()) != R.id.watchHistoryFragment) {
173-
setupFragmentAnimation(binding.root)
174-
}
175-
}
125+
binding.filterStatusTV.setOnClickListener {
126+
val filterOptions = resources.getStringArray(R.array.filterStatusOptions)
176127

177-
private fun showWatchHistory(history: List<WatchHistoryItem>) {
178-
val watchHistory = history.filterByStatusAndWatchPosition()
128+
BaseBottomSheet().apply {
129+
setSimpleItems(filterOptions.toList()) { index ->
130+
binding.filterStatusTV.text = filterOptions[index]
131+
viewModel.selectedStatusFilter = index
132+
}
133+
}.show(childFragmentManager)
134+
}
179135

180136
binding.playAll.setOnClickListener {
137+
val history = viewModel.filteredWatchHistory.value.orEmpty()
138+
if (history.isEmpty()) return@setOnClickListener
139+
181140
PlayingQueue.add(
182-
*watchHistory.reversed().map(WatchHistoryItem::toStreamItem).toTypedArray()
141+
*history.reversed().map(WatchHistoryItem::toStreamItem).toTypedArray()
183142
)
184143
NavigationHelper.navigateVideo(
185144
requireContext(),
186-
watchHistory.last().videoId,
145+
history.last().videoId,
187146
keepQueue = true
188147
)
189148
}
190-
watchHistoryAdapter.submitList(history)
191-
binding.historyEmpty.isGone = true
192-
binding.historyContainer.isVisible = true
193-
194-
// add a listener for scroll end, delay needed to prevent loading new ones the first time
195-
handler.postDelayed(200) {
196-
if (_binding == null) return@postDelayed
197-
198-
binding.watchHistoryRecView.addOnBottomReachedListener {
199-
if (isLoading) return@addOnBottomReachedListener
200-
isLoading = true
201-
202-
lifecycleScope.launch {
203-
val newHistory = withContext(Dispatchers.IO) {
204-
val currentPage = ceil(watchHistoryAdapter.itemCount.toFloat() / HISTORY_PAGE_SIZE).toInt()
205-
DatabaseHelper.getWatchHistoryPage( currentPage + 1, HISTORY_PAGE_SIZE)
206-
}.filterByStatusAndWatchPosition()
207-
208-
watchHistoryAdapter.insertItems(newHistory)
209-
isLoading = false
210-
}
211-
}
212-
}
213-
}
214149

215-
private fun List<WatchHistoryItem>.filterByStatusAndWatchPosition(): List<WatchHistoryItem> {
216-
val watchHistoryItem = this.filter {
217-
val isLive = (it.duration ?: -1L) < 0L
218-
when (selectedTypeFilter) {
219-
0 -> true
220-
1 -> !it.isShort && !isLive
221-
2 -> it.isShort // where is the StreamItem converted to watchHistoryItem?
222-
3 -> isLive
223-
else -> throw IllegalArgumentException()
224-
}
150+
viewModel.filteredWatchHistory.observe(viewLifecycleOwner) { history ->
151+
binding.historyEmpty.isGone = history.isNotEmpty()
152+
binding.historyContainer.isVisible = history.isNotEmpty()
153+
154+
watchHistoryAdapter.submitList(history)
225155
}
226156

227-
if (selectedStatusFilter == 0) {
228-
return watchHistoryItem
157+
viewModel.fetchNextPage()
158+
159+
binding.watchHistoryRecView.addOnBottomReachedListener {
160+
viewModel.fetchNextPage()
229161
}
230162

231-
return runBlocking {
232-
when (selectedStatusFilter) {
233-
1 -> DatabaseHelper.filterByWatchStatus(watchHistoryItem)
234-
2 -> DatabaseHelper.filterByWatchStatus(watchHistoryItem, false)
235-
else -> throw IllegalArgumentException()
236-
}
163+
if (NavBarHelper.getStartFragmentId(requireContext()) != R.id.watchHistoryFragment) {
164+
setupFragmentAnimation(binding.root)
237165
}
238166
}
239167

@@ -247,8 +175,4 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
247175
super.onDestroyView()
248176
_binding = null
249177
}
250-
251-
companion object {
252-
private const val HISTORY_PAGE_SIZE = 10
253-
}
254178
}

0 commit comments

Comments
 (0)