Issue#7 Enable Deleting Downloaded Video by swiping and add possibility to reordering pending videos when not loading
This commit is contained in:
parent
136530b927
commit
db58234f74
20 changed files with 532 additions and 67 deletions
|
|
@ -58,6 +58,12 @@ class VideoDownloadedLocalSource(
|
||||||
.plus(videoDownloaded.asString().addTimeAtStart())
|
.plus(videoDownloaded.asString().addTimeAtStart())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeVideo(videoDownloaded: VideoDownloaded) {
|
||||||
|
sharedPreferencesManagerImpl.downloadedVideos = sharedPreferencesManagerImpl.downloadedVideos
|
||||||
|
.filterNot { it.getTimeAndOriginal().second.asVideoDownloaded() == videoDownloaded }
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private fun VideoDownloaded.asString(): String =
|
private fun VideoDownloaded.asString(): String =
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import org.fnives.tiktokdownloader.data.local.persistent.getTimeAndOriginal
|
||||||
import org.fnives.tiktokdownloader.data.local.persistent.joinNormalized
|
import org.fnives.tiktokdownloader.data.local.persistent.joinNormalized
|
||||||
import org.fnives.tiktokdownloader.data.local.persistent.separateIntoDenormalized
|
import org.fnives.tiktokdownloader.data.local.persistent.separateIntoDenormalized
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
class VideoInPendingLocalSource(
|
class VideoInPendingLocalSource(
|
||||||
private val sharedPreferencesManager: SharedPreferencesManager,
|
private val sharedPreferencesManager: SharedPreferencesManager,
|
||||||
|
|
@ -33,6 +34,27 @@ class VideoInPendingLocalSource(
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun moveBy(videoInPending: VideoInPending, positionDifference: Int) {
|
||||||
|
if (positionDifference == 0) return
|
||||||
|
|
||||||
|
val mutableOrdered = sharedPreferencesManager.pendingVideos
|
||||||
|
.map { it.getTimeAndOriginal() }
|
||||||
|
.sortedBy { it.first }
|
||||||
|
.toMutableList()
|
||||||
|
|
||||||
|
val index = mutableOrdered.indexOfFirst { it.second.asVideoInPending() == videoInPending }
|
||||||
|
val endTime = mutableOrdered[index + positionDifference].first
|
||||||
|
|
||||||
|
val direction = -positionDifference / abs(positionDifference)
|
||||||
|
val range = IntProgression.fromClosedRange(index + positionDifference, index - direction, direction)
|
||||||
|
range.forEach {
|
||||||
|
mutableOrdered[it] = mutableOrdered[it + direction].first to mutableOrdered[it].second
|
||||||
|
}
|
||||||
|
mutableOrdered[index] = endTime to mutableOrdered[index].second
|
||||||
|
|
||||||
|
sharedPreferencesManager.pendingVideos = mutableOrdered.map { it.second.addTimeAtStart(it.first) }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun VideoInPending.asString(): String = listOf(id, url).joinNormalized()
|
private fun VideoInPending.asString(): String = listOf(id, url).joinNormalized()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ fun List<String>.joinNormalized() : String =
|
||||||
fun String.separateIntoDenormalized() : List<String> =
|
fun String.separateIntoDenormalized() : List<String> =
|
||||||
split("$SEPARATOR$SEPARATOR").map { it.denormalize() }
|
split("$SEPARATOR$SEPARATOR").map { it.denormalize() }
|
||||||
|
|
||||||
fun String.addTimeAtStart(index: Int = 0) =
|
fun String.addTimeAtStart(time: Long = System.currentTimeMillis()) =
|
||||||
"${System.currentTimeMillis() + index}$TIME_SEPARATOR$this"
|
"${time}$TIME_SEPARATOR$this"
|
||||||
|
|
||||||
fun String.getTimeAndOriginal(): Pair<Long, String> {
|
fun String.getTimeAndOriginal(): Pair<Long, String> {
|
||||||
val time = takeWhile { it != TIME_SEPARATOR }.toLong()
|
val time = takeWhile { it != TIME_SEPARATOR }.toLong()
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ package org.fnives.tiktokdownloader.data.usecase
|
||||||
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
|
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
|
|
||||||
class MoveVideoInQueue(
|
class MoveVideoInQueueUseCase(
|
||||||
private val videoInPendingLocalSource: VideoInPendingLocalSource
|
private val videoInPendingLocalSource: VideoInPendingLocalSource
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(videoInPending: VideoInPending, to: VideoInPending) {
|
operator fun invoke(videoInPending: VideoInPending, positionDifference: Int) {
|
||||||
|
videoInPendingLocalSource.moveBy(videoInPending, positionDifference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
package org.fnives.tiktokdownloader.data.usecase
|
package org.fnives.tiktokdownloader.data.usecase
|
||||||
|
|
||||||
|
import org.fnives.tiktokdownloader.data.local.VideoDownloadedLocalSource
|
||||||
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
|
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoState
|
||||||
|
|
||||||
class RemoveVideoFromQueueUseCase(
|
class RemoveVideoFromQueueUseCase(
|
||||||
private val videoInPendingLocalSource: VideoInPendingLocalSource
|
private val videoInPendingLocalSource: VideoInPendingLocalSource,
|
||||||
|
private val videoDownloadedLocalSource: VideoDownloadedLocalSource
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(videoInPending: VideoInPending) {
|
operator fun invoke(videoState: VideoState) {
|
||||||
videoInPendingLocalSource.removeVideoFromQueue(videoInPending)
|
when(videoState) {
|
||||||
|
is VideoState.Downloaded -> videoDownloadedLocalSource.removeVideo(videoState.videoDownloaded)
|
||||||
|
is VideoState.InPending -> videoInPendingLocalSource.removeVideoFromQueue(videoState.videoInPending)
|
||||||
|
is VideoState.InProcess -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,17 +8,17 @@ import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class LocalSourceModule(private val androidFileManagementModule: AndroidFileManagementModule) {
|
class LocalSourceModule(private val androidFileManagementModule: AndroidFileManagementModule) {
|
||||||
|
|
||||||
val videoDownloadedLocalSource: VideoDownloadedLocalSource
|
val videoDownloadedLocalSource: VideoDownloadedLocalSource by lazy {
|
||||||
get() = VideoDownloadedLocalSource(
|
VideoDownloadedLocalSource(
|
||||||
saveVideoFile = androidFileManagementModule.saveVideoFile,
|
saveVideoFile = androidFileManagementModule.saveVideoFile,
|
||||||
sharedPreferencesManagerImpl = androidFileManagementModule.sharedPreferencesManager,
|
sharedPreferencesManagerImpl = androidFileManagementModule.sharedPreferencesManager,
|
||||||
verifyFileForUriExists = androidFileManagementModule.verifyFileForUriExists
|
verifyFileForUriExists = androidFileManagementModule.verifyFileForUriExists
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val videoInPendingLocalSource: VideoInPendingLocalSource
|
val videoInPendingLocalSource: VideoInPendingLocalSource by lazy {
|
||||||
get() = VideoInPendingLocalSource(
|
VideoInPendingLocalSource(sharedPreferencesManager = androidFileManagementModule.sharedPreferencesManager)
|
||||||
sharedPreferencesManager = androidFileManagementModule.sharedPreferencesManager
|
}
|
||||||
)
|
|
||||||
|
|
||||||
val videoInProgressLocalSource: VideoInProgressLocalSource by lazy { VideoInProgressLocalSource() }
|
val videoInProgressLocalSource: VideoInProgressLocalSource by lazy { VideoInProgressLocalSource() }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package org.fnives.tiktokdownloader.di.module
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
|
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.MoveVideoInQueue
|
import org.fnives.tiktokdownloader.data.usecase.MoveVideoInQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.RemoveVideoFromQueueUseCase
|
import org.fnives.tiktokdownloader.data.usecase.RemoveVideoFromQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.StateOfVideosObservableUseCase
|
import org.fnives.tiktokdownloader.data.usecase.StateOfVideosObservableUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.UrlVerificationUseCase
|
import org.fnives.tiktokdownloader.data.usecase.UrlVerificationUseCase
|
||||||
|
|
@ -32,11 +32,12 @@ class UseCaseModule(
|
||||||
|
|
||||||
val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase
|
val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase
|
||||||
get() = RemoveVideoFromQueueUseCase(
|
get() = RemoveVideoFromQueueUseCase(
|
||||||
localSourceModule.videoInPendingLocalSource
|
localSourceModule.videoInPendingLocalSource,
|
||||||
|
localSourceModule.videoDownloadedLocalSource
|
||||||
)
|
)
|
||||||
|
|
||||||
val moveVideoInQueue: MoveVideoInQueue
|
val moveVideoInQueueUseCase: MoveVideoInQueueUseCase
|
||||||
get() = MoveVideoInQueue(
|
get() = MoveVideoInQueueUseCase(
|
||||||
localSourceModule.videoInPendingLocalSource
|
localSourceModule.videoInPendingLocalSource
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class ViewModelModule(private val useCaseModule: UseCaseModule) {
|
||||||
useCaseModule.stateOfVideosObservableUseCase,
|
useCaseModule.stateOfVideosObservableUseCase,
|
||||||
useCaseModule.addVideoToQueueUseCase,
|
useCaseModule.addVideoToQueueUseCase,
|
||||||
useCaseModule.removeVideoFromQueueUseCase,
|
useCaseModule.removeVideoFromQueueUseCase,
|
||||||
useCaseModule.videoDownloadingProcessorUseCase
|
useCaseModule.videoDownloadingProcessorUseCase,
|
||||||
|
useCaseModule.moveVideoInQueueUseCase
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.fnives.tiktokdownloader.ui.main.queue
|
||||||
|
|
||||||
|
interface MovingItemCallback {
|
||||||
|
|
||||||
|
fun onMovingStart()
|
||||||
|
|
||||||
|
fun onMovingEnd()
|
||||||
|
}
|
||||||
|
|
@ -57,14 +57,18 @@ class QueueFragment : Fragment(R.layout.fragment_queue) {
|
||||||
)
|
)
|
||||||
recycler.adapter = adapter
|
recycler.adapter = adapter
|
||||||
|
|
||||||
val touchHelper = ItemTouchHelper(PendingItemTouchHelper(
|
val callback = VideoStateItemTouchHelper(
|
||||||
whichItem = { adapter.currentList.getOrNull(it.bindingAdapterPosition) },
|
whichItem = { adapter.currentList.getOrNull(it.bindingAdapterPosition) },
|
||||||
onDeleteElement = viewModel::onElementDeleted,
|
onDeleteElement = viewModel::onElementDeleted,
|
||||||
onMovedElement = viewModel::onElementMoved
|
onUIMoveElement = adapter::swap,
|
||||||
))
|
onMoveElement = viewModel::onElementMoved
|
||||||
|
)
|
||||||
|
val touchHelper = ItemTouchHelper(callback)
|
||||||
touchHelper.attachToRecyclerView(recycler)
|
touchHelper.attachToRecyclerView(recycler)
|
||||||
|
|
||||||
viewModel.downloads.observe(viewLifecycleOwner) { videoStates ->
|
viewModel.downloads.observe(viewLifecycleOwner) { videoStates ->
|
||||||
|
callback.dragEnabled = videoStates.none { it is VideoState.InProcess }
|
||||||
|
|
||||||
adapter.submitList(videoStates, Runnable {
|
adapter.submitList(videoStates, Runnable {
|
||||||
val indexToScrollTo = videoStates.indexOfFirst { it is VideoState.InProcess }
|
val indexToScrollTo = videoStates.indexOfFirst { it is VideoState.InProcess }
|
||||||
.takeIf { it != -1 } ?: return@Runnable
|
.takeIf { it != -1 } ?: return@Runnable
|
||||||
|
|
@ -73,36 +77,6 @@ class QueueFragment : Fragment(R.layout.fragment_queue) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PendingItemTouchHelper(
|
|
||||||
private val whichItem: (RecyclerView.ViewHolder) -> VideoState?,
|
|
||||||
private val onDeleteElement: (VideoState) -> Unit,
|
|
||||||
private val onMovedElement: (VideoState, VideoState) -> Boolean
|
|
||||||
) : ItemTouchHelper.Callback() {
|
|
||||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
|
||||||
val item = whichItem(viewHolder) ?: return 0
|
|
||||||
when (item) {
|
|
||||||
is VideoState.InPending -> Unit
|
|
||||||
is VideoState.Downloaded,
|
|
||||||
is VideoState.InProcess -> return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
|
||||||
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
|
|
||||||
return makeMovementFlags(dragFlags, swipeFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
|
||||||
val dragged = whichItem(target) ?: return false
|
|
||||||
val movedTo = whichItem(viewHolder) ?: return false
|
|
||||||
return onMovedElement(dragged, movedTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
|
||||||
whichItem(viewHolder)?.let { onDeleteElement(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newInstance(): QueueFragment = QueueFragment()
|
fun newInstance(): QueueFragment = QueueFragment()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
@ -53,12 +55,38 @@ class QueueItemAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadActionsViewHolder(parent: ViewGroup) :
|
fun swap(from: VideoState, to: VideoState) {
|
||||||
|
val mutable = currentList.toMutableList()
|
||||||
|
val fromIndex = currentList.indexOf(from)
|
||||||
|
val toIndex = currentList.indexOf(to)
|
||||||
|
mutable.removeAt(fromIndex)
|
||||||
|
mutable.add(fromIndex, to)
|
||||||
|
mutable.removeAt(toIndex)
|
||||||
|
mutable.add(toIndex, from)
|
||||||
|
|
||||||
|
submitList(mutable)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadActionsViewHolder(parent: ViewGroup) : MovingItemCallback,
|
||||||
RecyclerView.ViewHolder(parent.inflate(R.layout.item_downloaded)) {
|
RecyclerView.ViewHolder(parent.inflate(R.layout.item_downloaded)) {
|
||||||
|
val cardView: CardView = itemView as CardView
|
||||||
|
val content: ConstraintLayout = itemView.findViewById(R.id.content)
|
||||||
val urlView: TextView = itemView.findViewById(R.id.url)
|
val urlView: TextView = itemView.findViewById(R.id.url)
|
||||||
val statusView: TextView = itemView.findViewById(R.id.status)
|
val statusView: TextView = itemView.findViewById(R.id.status)
|
||||||
val thumbNailView: ImageView = itemView.findViewById(R.id.thumbnail)
|
val thumbNailView: ImageView = itemView.findViewById(R.id.thumbnail)
|
||||||
val progress: ProgressBar = itemView.findViewById(R.id.progress)
|
val progress: ProgressBar = itemView.findViewById(R.id.progress)
|
||||||
|
|
||||||
|
override fun onMovingEnd() {
|
||||||
|
cardView.isEnabled = false
|
||||||
|
cardView.isPressed = false
|
||||||
|
content.isPressed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMovingStart() {
|
||||||
|
cardView.isEnabled = true
|
||||||
|
cardView.isPressed = true
|
||||||
|
content.isPressed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiffUtilItemCallback : DiffUtil.ItemCallback<VideoState>() {
|
class DiffUtilItemCallback : DiffUtil.ItemCallback<VideoState>() {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoState
|
import org.fnives.tiktokdownloader.data.model.VideoState
|
||||||
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
|
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.MoveVideoInQueue
|
import org.fnives.tiktokdownloader.data.usecase.MoveVideoInQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.RemoveVideoFromQueueUseCase
|
import org.fnives.tiktokdownloader.data.usecase.RemoveVideoFromQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.StateOfVideosObservableUseCase
|
import org.fnives.tiktokdownloader.data.usecase.StateOfVideosObservableUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase
|
import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase
|
||||||
|
|
@ -17,7 +17,7 @@ class QueueViewModel(
|
||||||
private val addVideoToQueueUseCase: AddVideoToQueueUseCase,
|
private val addVideoToQueueUseCase: AddVideoToQueueUseCase,
|
||||||
private val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase,
|
private val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase,
|
||||||
private val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase,
|
private val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase,
|
||||||
private val moveVideoInQueue: MoveVideoInQueue
|
private val moveVideoInQueueUseCase: MoveVideoInQueueUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val downloads = asLiveData(stateOfVideosObservableUseCase())
|
val downloads = asLiveData(stateOfVideosObservableUseCase())
|
||||||
|
|
@ -38,17 +38,12 @@ class QueueViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onElementDeleted(videoState: VideoState) {
|
fun onElementDeleted(videoState: VideoState) {
|
||||||
when (videoState) {
|
removeVideoFromQueueUseCase(videoState)
|
||||||
is VideoState.InPending -> removeVideoFromQueueUseCase(videoState.videoInPending)
|
|
||||||
is VideoState.Downloaded,
|
|
||||||
is VideoState.InProcess -> Unit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onElementMoved(moved: VideoState, to: VideoState): Boolean {
|
fun onElementMoved(moved: VideoState, positionDifference: Int): Boolean {
|
||||||
if (moved !is VideoState.InPending) return false
|
if (moved !is VideoState.InPending) return false
|
||||||
if (to !is VideoState.InPending) return false
|
moveVideoInQueueUseCase(moved.videoInPending, positionDifference)
|
||||||
moveVideoInQueue(moved.videoInPending, to.videoInPending)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.fnives.tiktokdownloader.ui.main.queue
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoState
|
||||||
|
|
||||||
|
class VideoStateItemTouchHelper(
|
||||||
|
private val whichItem: (RecyclerView.ViewHolder) -> VideoState?,
|
||||||
|
private val onDeleteElement: (VideoState) -> Unit,
|
||||||
|
private val onUIMoveElement: (VideoState, VideoState) -> Unit,
|
||||||
|
private val onMoveElement: (VideoState, Int) -> Unit
|
||||||
|
) : ItemTouchHelper.Callback() {
|
||||||
|
|
||||||
|
var dragEnabled: Boolean = true
|
||||||
|
var swipeEnabled: Boolean = true
|
||||||
|
private var index: Int? = null
|
||||||
|
|
||||||
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||||
|
val item = whichItem(viewHolder) ?: return DISABLED_FLAG
|
||||||
|
var dragEnabled = dragEnabled
|
||||||
|
var swipeEnabled = swipeEnabled
|
||||||
|
when (item) {
|
||||||
|
is VideoState.InPending -> Unit
|
||||||
|
is VideoState.Downloaded -> {
|
||||||
|
dragEnabled = false
|
||||||
|
}
|
||||||
|
is VideoState.InProcess -> {
|
||||||
|
dragEnabled = false
|
||||||
|
swipeEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dragFlags = if (dragEnabled) ItemTouchHelper.UP or ItemTouchHelper.DOWN else DISABLED_FLAG
|
||||||
|
val swipeFlags = if (swipeEnabled) ItemTouchHelper.START or ItemTouchHelper.END else DISABLED_FLAG
|
||||||
|
return makeMovementFlags(dragFlags, swipeFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||||
|
val dragged = whichItem(target)?.takeIf { it is VideoState.InPending } ?: return false
|
||||||
|
val movedTo = whichItem(viewHolder)?.takeIf { it is VideoState.InPending } ?: return false
|
||||||
|
onUIMoveElement(dragged, movedTo)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
whichItem(viewHolder)?.let { onDeleteElement(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||||
|
super.onSelectedChanged(viewHolder, actionState)
|
||||||
|
(viewHolder as? MovingItemCallback)?.onMovingStart()
|
||||||
|
index = when(actionState) {
|
||||||
|
ItemTouchHelper.ACTION_STATE_DRAG -> viewHolder?.bindingAdapterPosition
|
||||||
|
ItemTouchHelper.ACTION_STATE_IDLE -> index
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder)
|
||||||
|
(viewHolder as? MovingItemCallback)?.onMovingEnd()
|
||||||
|
val videoState = whichItem(viewHolder) ?: return
|
||||||
|
val startIndex = index ?: return
|
||||||
|
|
||||||
|
val endIndex = viewHolder.bindingAdapterPosition
|
||||||
|
onMoveElement(videoState, endIndex - startIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DISABLED_FLAG = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
app:layoutDescription="@xml/queue_motion_description">
|
app:layoutDescription="@xml/queue_motion_description">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|
@ -91,6 +92,8 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:itemCount="3"
|
||||||
|
tools:listitem="@layout/item_downloaded"
|
||||||
app:layout_constraintTop_toBottomOf="@id/toolbar_background" />
|
app:layout_constraintTop_toBottomOf="@id/toolbar_background" />
|
||||||
|
|
||||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:id="@+id/content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:padding="@dimen/medium_padding">
|
android:padding="@dimen/medium_padding">
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import org.mockito.kotlin.whenever
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
@Timeout(value = 2)
|
@Timeout(value = 2)
|
||||||
class VideoDownloadedLocalSourceTest {
|
class VideoDownloadedLocalSourceTest {
|
||||||
|
|
||||||
|
|
@ -195,6 +196,50 @@ class VideoDownloadedLocalSourceTest {
|
||||||
Assertions.assertEquals(expected, actual.await())
|
Assertions.assertEquals(expected, actual.await())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_two_videos_WHEN_deleting_THEN_observer_is_updated() = runBlocking {
|
||||||
|
val videoInSavingIntoFile1 = VideoInSavingIntoFile(
|
||||||
|
id = "id1",
|
||||||
|
url = "alma1",
|
||||||
|
contentType = VideoInSavingIntoFile.ContentType("a1", "b1"),
|
||||||
|
byteStream = FalseInputStream()
|
||||||
|
)
|
||||||
|
val videoInSavingIntoFile2 = VideoInSavingIntoFile(
|
||||||
|
id = "id2",
|
||||||
|
url = "alma2",
|
||||||
|
contentType = VideoInSavingIntoFile.ContentType("a2", "b2"),
|
||||||
|
byteStream = FalseInputStream()
|
||||||
|
)
|
||||||
|
whenever(mockVerifyFileForUriExists.invoke(anyOrNull())).doReturn(true)
|
||||||
|
whenever(mockSaveVideoFile.invoke(anyOrNull(), anyOrNull(), anyOrNull())).then {
|
||||||
|
"uri: " + (it.arguments[1] as String)
|
||||||
|
}
|
||||||
|
val expectedModel1 = VideoDownloaded(id = "id1", url = "alma1", uri = "uri: id1.b1")
|
||||||
|
val expectedModel2 = VideoDownloaded(id = "id2", url = "alma2", uri = "uri: id2.b2")
|
||||||
|
val expected = listOf(
|
||||||
|
emptyList(),
|
||||||
|
listOf(expectedModel1),
|
||||||
|
listOf(expectedModel2, expectedModel1),
|
||||||
|
listOf(expectedModel2),
|
||||||
|
emptyList(),
|
||||||
|
)
|
||||||
|
val actual = async(coroutineContext) { sut.savedVideos.take(expected.size).toList() }
|
||||||
|
|
||||||
|
yield()
|
||||||
|
sut.saveVideo(videoInSavingIntoFile1)
|
||||||
|
delay(100)
|
||||||
|
yield()
|
||||||
|
sut.saveVideo(videoInSavingIntoFile2)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
sut.removeVideo(expectedModel1)
|
||||||
|
yield()
|
||||||
|
sut.removeVideo(expectedModel2)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
Assertions.assertIterableEquals(expected, actual.await())
|
||||||
|
}
|
||||||
|
|
||||||
class FalseInputStream : InputStream() {
|
class FalseInputStream : InputStream() {
|
||||||
override fun read(): Int = 0
|
override fun read(): Int = 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.Timeout
|
import org.junit.jupiter.api.Timeout
|
||||||
|
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
@Timeout(value = 2)
|
@Timeout(value = 2)
|
||||||
class VideoInPendingLocalSourceTest {
|
class VideoInPendingLocalSourceTest {
|
||||||
|
|
||||||
|
|
@ -76,8 +76,7 @@ class VideoInPendingLocalSourceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun GIVEN_observing_PendingVideos_WHEN_2_video_marked_as_pending_THEN_both_of_them_are_sent_out_in_correct_order() =
|
fun GIVEN_observing_PendingVideos_WHEN_2_video_marked_as_pending_THEN_both_of_them_are_sent_out_in_correct_order() = runBlocking<Unit> {
|
||||||
runBlocking<Unit> {
|
|
||||||
val videoInPending1 = VideoInPending("id1", "alma1")
|
val videoInPending1 = VideoInPending("id1", "alma1")
|
||||||
val videoInPending2 = VideoInPending("id2", "alma2")
|
val videoInPending2 = VideoInPending("id2", "alma2")
|
||||||
val expected = listOf(emptyList(), listOf(videoInPending1), listOf(videoInPending1, videoInPending2))
|
val expected = listOf(emptyList(), listOf(videoInPending1), listOf(videoInPending1, videoInPending2))
|
||||||
|
|
@ -94,4 +93,92 @@ class VideoInPendingLocalSourceTest {
|
||||||
Assertions.assertEquals(expected, actual.await())
|
Assertions.assertEquals(expected, actual.await())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_2_videos_WHEN_moving_first_one_down_by_one_THEN_it_is_moved_properly() = runBlocking<Unit> {
|
||||||
|
val videoInPending1 = VideoInPending("id1", "alma1")
|
||||||
|
val videoInPending2 = VideoInPending("id2", "alma2")
|
||||||
|
|
||||||
|
val expected = listOf(videoInPending2, videoInPending1)
|
||||||
|
|
||||||
|
yield()
|
||||||
|
sut.saveUrlIntoQueue(videoInPending1)
|
||||||
|
delay(10)
|
||||||
|
sut.saveUrlIntoQueue(videoInPending2)
|
||||||
|
delay(10)
|
||||||
|
|
||||||
|
sut.moveBy(videoInPending1, 1)
|
||||||
|
|
||||||
|
Assertions.assertEquals(expected, sut.pendingVideos.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_2_videos_WHEN_moving_second_one_up_by_one_THEN_it_is_moved_properly() = runBlocking<Unit> {
|
||||||
|
val videoInPending1 = VideoInPending("id1", "alma1")
|
||||||
|
val videoInPending2 = VideoInPending("id2", "alma2")
|
||||||
|
|
||||||
|
val expected = listOf(videoInPending2, videoInPending1)
|
||||||
|
|
||||||
|
yield()
|
||||||
|
sut.saveUrlIntoQueue(videoInPending1)
|
||||||
|
delay(10)
|
||||||
|
sut.saveUrlIntoQueue(videoInPending2)
|
||||||
|
delay(10)
|
||||||
|
|
||||||
|
sut.moveBy(videoInPending2, -1)
|
||||||
|
|
||||||
|
Assertions.assertEquals(expected, sut.pendingVideos.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_3_videos_WHEN_moving_first_moving_around_is_moved_properly() = runBlocking<Unit> {
|
||||||
|
val videoInPending1 = VideoInPending("id1", "alma1")
|
||||||
|
val videoInPending2 = VideoInPending("id2", "alma2")
|
||||||
|
val videoInPending3 = VideoInPending("id3", "alma3")
|
||||||
|
|
||||||
|
val expected = listOf(
|
||||||
|
listOf(videoInPending1, videoInPending2, videoInPending3), // start
|
||||||
|
listOf(videoInPending2, videoInPending1, videoInPending3), // down 1
|
||||||
|
listOf(videoInPending2, videoInPending3, videoInPending1), // down 1
|
||||||
|
listOf(videoInPending2, videoInPending1, videoInPending3), // ++up 1
|
||||||
|
listOf(videoInPending1, videoInPending2, videoInPending3), // ++up 1
|
||||||
|
listOf(videoInPending2, videoInPending3, videoInPending1), // down 2
|
||||||
|
listOf(videoInPending2, videoInPending1, videoInPending3), // ++up 1
|
||||||
|
listOf(videoInPending1, videoInPending2, videoInPending3), // ++up 1
|
||||||
|
listOf(videoInPending2, videoInPending3, videoInPending1), // down 2
|
||||||
|
listOf(videoInPending1, videoInPending2, videoInPending3), // ++up 1
|
||||||
|
)
|
||||||
|
|
||||||
|
yield()
|
||||||
|
sut.saveUrlIntoQueue(videoInPending1)
|
||||||
|
delay(10)
|
||||||
|
sut.saveUrlIntoQueue(videoInPending2)
|
||||||
|
delay(10)
|
||||||
|
sut.saveUrlIntoQueue(videoInPending3)
|
||||||
|
|
||||||
|
val actual = async(coroutineContext) {
|
||||||
|
sut.pendingVideos.take(expected.size).toList()
|
||||||
|
}
|
||||||
|
yield()
|
||||||
|
|
||||||
|
sut.moveBy(videoInPending1, 1)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, 1)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, -1)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, -1)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, 2)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, -1)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, -1)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, 2)
|
||||||
|
yield()
|
||||||
|
sut.moveBy(videoInPending1, -2)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
Assertions.assertIterableEquals(expected, actual.await())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.fnives.tiktokdownloader.data.usecase
|
||||||
|
|
||||||
|
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.Timeout
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.verifyNoInteractions
|
||||||
|
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||||
|
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
|
@Timeout(value = 2)
|
||||||
|
class MoveVideoInQueueUseCaseTest {
|
||||||
|
|
||||||
|
private lateinit var sut: MoveVideoInQueueUseCase
|
||||||
|
private lateinit var mockVideoInPendingLocalSource: VideoInPendingLocalSource
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
mockVideoInPendingLocalSource = mock()
|
||||||
|
sut = MoveVideoInQueueUseCase(mockVideoInPendingLocalSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun WHEN_no_action_THEN_no_delegation() {
|
||||||
|
verifyNoInteractions(mockVideoInPendingLocalSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_video_and_position_change_WHEN_invoked_THEN_delegated() {
|
||||||
|
val video = VideoInPending("1", "url1")
|
||||||
|
sut.invoke(video, 100)
|
||||||
|
|
||||||
|
verify(mockVideoInPendingLocalSource).moveBy(video, 100)
|
||||||
|
verifyNoMoreInteractions(mockVideoInPendingLocalSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.fnives.tiktokdownloader.data.usecase
|
||||||
|
|
||||||
|
import org.fnives.tiktokdownloader.data.local.VideoDownloadedLocalSource
|
||||||
|
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoDownloaded
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoInProgress
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoState
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.Timeout
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.verifyNoInteractions
|
||||||
|
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||||
|
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
|
@Timeout(value = 2)
|
||||||
|
class RemoveVideoFromQueueUseCaseTest {
|
||||||
|
|
||||||
|
private lateinit var sut: RemoveVideoFromQueueUseCase
|
||||||
|
private lateinit var mockVideoInPendingLocalSource: VideoInPendingLocalSource
|
||||||
|
private lateinit var mockVideoDownloadedLocalSource: VideoDownloadedLocalSource
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
mockVideoInPendingLocalSource = mock()
|
||||||
|
mockVideoDownloadedLocalSource = mock()
|
||||||
|
sut = RemoveVideoFromQueueUseCase(mockVideoInPendingLocalSource, mockVideoDownloadedLocalSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun WHEN_no_action_THEN_no_delegation() {
|
||||||
|
verifyNoInteractions(mockVideoInPendingLocalSource)
|
||||||
|
verifyNoInteractions(mockVideoDownloadedLocalSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_pending_video_WHEN_invoked_THEN_delegated() {
|
||||||
|
val video = VideoState.InPending(VideoInPending("1", "url1"))
|
||||||
|
sut.invoke(video)
|
||||||
|
|
||||||
|
verify(mockVideoInPendingLocalSource).removeVideoFromQueue(video.videoInPending)
|
||||||
|
verifyNoMoreInteractions(mockVideoInPendingLocalSource)
|
||||||
|
verifyNoInteractions(mockVideoDownloadedLocalSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_downloaded_video_WHEN_invoked_THEN_delegated() {
|
||||||
|
val video = VideoState.Downloaded(VideoDownloaded("1", "url1", "img1"))
|
||||||
|
sut.invoke(video)
|
||||||
|
|
||||||
|
verifyNoInteractions(mockVideoInPendingLocalSource)
|
||||||
|
verify(mockVideoDownloadedLocalSource).removeVideo(video.videoDownloaded)
|
||||||
|
verifyNoMoreInteractions(mockVideoDownloadedLocalSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_in_process_WHEN_invoked_THEN_no_delegation() {
|
||||||
|
val video = VideoState.InProcess(VideoInProgress("1", "url1"))
|
||||||
|
sut.invoke(video)
|
||||||
|
|
||||||
|
verifyNoInteractions(mockVideoInPendingLocalSource)
|
||||||
|
verifyNoInteractions(mockVideoDownloadedLocalSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,13 @@ package org.fnives.tiktokdownloader.ui.main.queue
|
||||||
|
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoDownloaded
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
import org.fnives.tiktokdownloader.data.model.VideoInPending
|
||||||
|
import org.fnives.tiktokdownloader.data.model.VideoInProgress
|
||||||
import org.fnives.tiktokdownloader.data.model.VideoState
|
import org.fnives.tiktokdownloader.data.model.VideoState
|
||||||
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
|
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
|
||||||
|
import org.fnives.tiktokdownloader.data.usecase.MoveVideoInQueueUseCase
|
||||||
|
import org.fnives.tiktokdownloader.data.usecase.RemoveVideoFromQueueUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.StateOfVideosObservableUseCase
|
import org.fnives.tiktokdownloader.data.usecase.StateOfVideosObservableUseCase
|
||||||
import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase
|
import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase
|
||||||
import org.fnives.tiktokdownloader.helper.junit.rule.InstantExecutorExtension
|
import org.fnives.tiktokdownloader.helper.junit.rule.InstantExecutorExtension
|
||||||
|
|
@ -28,7 +32,9 @@ class QueueViewModelTest {
|
||||||
private lateinit var stateOfVideosFlow: MutableSharedFlow<List<VideoState>>
|
private lateinit var stateOfVideosFlow: MutableSharedFlow<List<VideoState>>
|
||||||
private lateinit var mockStateOfVideosObservableUseCase: StateOfVideosObservableUseCase
|
private lateinit var mockStateOfVideosObservableUseCase: StateOfVideosObservableUseCase
|
||||||
private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase
|
private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase
|
||||||
|
private lateinit var mockRemoveVideoFromQueueUseCase: RemoveVideoFromQueueUseCase
|
||||||
private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase
|
private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase
|
||||||
|
private lateinit var mockMoveVideoInQueueUseCase: MoveVideoInQueueUseCase
|
||||||
private lateinit var sut: QueueViewModel
|
private lateinit var sut: QueueViewModel
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|
@ -37,8 +43,16 @@ class QueueViewModelTest {
|
||||||
mockStateOfVideosObservableUseCase = mock()
|
mockStateOfVideosObservableUseCase = mock()
|
||||||
whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosFlow)
|
whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosFlow)
|
||||||
mockAddVideoToQueueUseCase = mock()
|
mockAddVideoToQueueUseCase = mock()
|
||||||
|
mockRemoveVideoFromQueueUseCase = mock()
|
||||||
mockVideoDownloadingProcessorUseCase = mock()
|
mockVideoDownloadingProcessorUseCase = mock()
|
||||||
sut = QueueViewModel(mockStateOfVideosObservableUseCase, mockAddVideoToQueueUseCase, mockVideoDownloadingProcessorUseCase)
|
mockMoveVideoInQueueUseCase = mock()
|
||||||
|
sut = QueueViewModel(
|
||||||
|
mockStateOfVideosObservableUseCase,
|
||||||
|
mockAddVideoToQueueUseCase,
|
||||||
|
mockRemoveVideoFromQueueUseCase,
|
||||||
|
mockVideoDownloadingProcessorUseCase,
|
||||||
|
mockMoveVideoInQueueUseCase
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -48,6 +62,8 @@ class QueueViewModelTest {
|
||||||
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -60,6 +76,8 @@ class QueueViewModelTest {
|
||||||
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -77,6 +95,8 @@ class QueueViewModelTest {
|
||||||
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -89,6 +109,8 @@ class QueueViewModelTest {
|
||||||
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
|
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
|
||||||
verify(mockVideoDownloadingProcessorUseCase, times(1)).fetchVideoInState()
|
verify(mockVideoDownloadingProcessorUseCase, times(1)).fetchVideoInState()
|
||||||
verifyNoMoreInteractions(mockVideoDownloadingProcessorUseCase)
|
verifyNoMoreInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -103,6 +125,8 @@ class QueueViewModelTest {
|
||||||
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
|
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
|
||||||
verify(mockVideoDownloadingProcessorUseCase, times(2)).fetchVideoInState()
|
verify(mockVideoDownloadingProcessorUseCase, times(2)).fetchVideoInState()
|
||||||
verifyNoMoreInteractions(mockVideoDownloadingProcessorUseCase)
|
verifyNoMoreInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -125,4 +149,86 @@ class QueueViewModelTest {
|
||||||
.assertHistorySize(1)
|
.assertHistorySize(1)
|
||||||
.assertValue(Event(QueueViewModel.NavigationEvent.OpenGallery("alma.com")))
|
.assertValue(Event(QueueViewModel.NavigationEvent.OpenGallery("alma.com")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_initialized_WHEN_inProgress_onElementDeleted_THEN_remove_called() {
|
||||||
|
val arg = VideoState.InProcess(VideoInProgress("1","2"))
|
||||||
|
sut.onElementDeleted(arg)
|
||||||
|
|
||||||
|
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
|
||||||
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verify(mockRemoveVideoFromQueueUseCase, times(1)).invoke(arg)
|
||||||
|
verifyNoMoreInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_initialized_WHEN_pending_onElementDeleted_THEN_remove_called() {
|
||||||
|
val arg = VideoState.InPending(VideoInPending("1","2"))
|
||||||
|
sut.onElementDeleted(arg)
|
||||||
|
|
||||||
|
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
|
||||||
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verify(mockRemoveVideoFromQueueUseCase, times(1)).invoke(arg)
|
||||||
|
verifyNoMoreInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_initialized_WHEN_download_onElementDeleted_THEN_remove_called() {
|
||||||
|
val arg = VideoState.Downloaded(VideoDownloaded("1","2","3"))
|
||||||
|
sut.onElementDeleted(arg)
|
||||||
|
|
||||||
|
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
|
||||||
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verify(mockRemoveVideoFromQueueUseCase, times(1)).invoke(arg)
|
||||||
|
verifyNoMoreInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_initialized_WHEN_in_progress_moved_THEN_nothing_changes() {
|
||||||
|
val arg = VideoState.InProcess(VideoInProgress("1","2"))
|
||||||
|
sut.onElementMoved(arg, 100)
|
||||||
|
|
||||||
|
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
|
||||||
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_initialized_WHEN_done_moved_THEN_nothing_changes() {
|
||||||
|
val arg = VideoState.Downloaded(VideoDownloaded("1","2","3"))
|
||||||
|
sut.onElementMoved(arg, 100)
|
||||||
|
|
||||||
|
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
|
||||||
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verifyNoInteractions(mockMoveVideoInQueueUseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun GIVEN_initialized_WHEN_pending_moved_THEN_nothing_changes() {
|
||||||
|
val arg = VideoState.InPending(VideoInPending("1","2"))
|
||||||
|
sut.onElementMoved(arg, 100)
|
||||||
|
|
||||||
|
verify(mockStateOfVideosObservableUseCase, times(1)).invoke()
|
||||||
|
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
|
||||||
|
verifyNoInteractions(mockAddVideoToQueueUseCase)
|
||||||
|
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
|
||||||
|
verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
|
||||||
|
verify(mockMoveVideoInQueueUseCase, times(1)).invoke(arg.videoInPending, 100)
|
||||||
|
verifyNoMoreInteractions(mockMoveVideoInQueueUseCase)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue