Issue#7 Enable Deleting Pending Video by swiping

This commit is contained in:
Gergely Hegedus 2022-04-21 21:17:21 +03:00
parent 8d709206fd
commit 136530b927
6 changed files with 115 additions and 10 deletions

View file

@ -0,0 +1,13 @@
package org.fnives.tiktokdownloader.data.usecase
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
import org.fnives.tiktokdownloader.data.model.VideoInPending
class MoveVideoInQueue(
private val videoInPendingLocalSource: VideoInPendingLocalSource
) {
operator fun invoke(videoInPending: VideoInPending, to: VideoInPending) {
}
}

View file

@ -0,0 +1,13 @@
package org.fnives.tiktokdownloader.data.usecase
import org.fnives.tiktokdownloader.data.local.VideoInPendingLocalSource
import org.fnives.tiktokdownloader.data.model.VideoInPending
class RemoveVideoFromQueueUseCase(
private val videoInPendingLocalSource: VideoInPendingLocalSource
) {
operator fun invoke(videoInPending: VideoInPending) {
videoInPendingLocalSource.removeVideoFromQueue(videoInPending)
}
}

View file

@ -2,13 +2,16 @@ 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.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
import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase
class UseCaseModule( class UseCaseModule(
private val localSourceModule: LocalSourceModule, private val localSourceModule: LocalSourceModule,
private val networkModule: NetworkModule) { private val networkModule: NetworkModule
) {
val stateOfVideosObservableUseCase: StateOfVideosObservableUseCase val stateOfVideosObservableUseCase: StateOfVideosObservableUseCase
get() = StateOfVideosObservableUseCase( get() = StateOfVideosObservableUseCase(
@ -24,7 +27,18 @@ class UseCaseModule(
val addVideoToQueueUseCase: AddVideoToQueueUseCase val addVideoToQueueUseCase: AddVideoToQueueUseCase
get() = AddVideoToQueueUseCase( get() = AddVideoToQueueUseCase(
urlVerificationUseCase, urlVerificationUseCase,
localSourceModule.videoInPendingLocalSource) localSourceModule.videoInPendingLocalSource
)
val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase
get() = RemoveVideoFromQueueUseCase(
localSourceModule.videoInPendingLocalSource
)
val moveVideoInQueue: MoveVideoInQueue
get() = MoveVideoInQueue(
localSourceModule.videoInPendingLocalSource
)
val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase by lazy { val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase by lazy {
VideoDownloadingProcessorUseCase( VideoDownloadingProcessorUseCase(

View file

@ -24,6 +24,7 @@ class ViewModelModule(private val useCaseModule: UseCaseModule) {
get() = QueueViewModel( get() = QueueViewModel(
useCaseModule.stateOfVideosObservableUseCase, useCaseModule.stateOfVideosObservableUseCase,
useCaseModule.addVideoToQueueUseCase, useCaseModule.addVideoToQueueUseCase,
useCaseModule.removeVideoFromQueueUseCase,
useCaseModule.videoDownloadingProcessorUseCase useCaseModule.videoDownloadingProcessorUseCase
) )
} }

View file

@ -9,7 +9,7 @@ import android.widget.EditText
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.fnives.tiktokdownloader.R import org.fnives.tiktokdownloader.R
import org.fnives.tiktokdownloader.data.model.VideoState import org.fnives.tiktokdownloader.data.model.VideoState
@ -22,22 +22,21 @@ class QueueFragment : Fragment(R.layout.fragment_queue) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val recycler = view.findViewById<RecyclerView>(R.id.recycler) val recycler = view.findViewById<RecyclerView>(R.id.recycler)
val adapter = QueueItemAdapter( recyclerViewSetup(recycler)
itemClicked = viewModel::onItemClicked, navigationSetup()
urlClicked = viewModel::onUrlClicked
)
recycler.adapter = adapter
val saveUrlCta = view.findViewById<Button>(R.id.save_cta) val saveUrlCta = view.findViewById<Button>(R.id.save_cta)
val input = view.findViewById<EditText>(R.id.download_url_input) val input = view.findViewById<EditText>(R.id.download_url_input)
input.doAfterTextChanged { input.doAfterTextChanged {
saveUrlCta.isEnabled = it?.isNotBlank() == true saveUrlCta.isEnabled = it?.isNotBlank() == true
} }
saveUrlCta.setOnClickListener { saveUrlCta.setOnClickListener {
recycler.smoothScrollToPosition(0)
viewModel.onSaveClicked(input.text?.toString().orEmpty()) viewModel.onSaveClicked(input.text?.toString().orEmpty())
input.setText("") input.setText("")
} }
}
private fun navigationSetup() {
viewModel.navigationEvent.observe(viewLifecycleOwner) { viewModel.navigationEvent.observe(viewLifecycleOwner) {
val intent = when (val data = it.item) { val intent = when (val data = it.item) {
is QueueViewModel.NavigationEvent.OpenBrowser -> { is QueueViewModel.NavigationEvent.OpenBrowser -> {
@ -49,6 +48,21 @@ class QueueFragment : Fragment(R.layout.fragment_queue) {
} }
startActivity(intent) startActivity(intent)
} }
}
private fun recyclerViewSetup(recycler: RecyclerView) {
val adapter = QueueItemAdapter(
itemClicked = viewModel::onItemClicked,
urlClicked = viewModel::onUrlClicked
)
recycler.adapter = adapter
val touchHelper = ItemTouchHelper(PendingItemTouchHelper(
whichItem = { adapter.currentList.getOrNull(it.bindingAdapterPosition) },
onDeleteElement = viewModel::onElementDeleted,
onMovedElement = viewModel::onElementMoved
))
touchHelper.attachToRecyclerView(recycler)
viewModel.downloads.observe(viewLifecycleOwner) { videoStates -> viewModel.downloads.observe(viewLifecycleOwner) { videoStates ->
adapter.submitList(videoStates, Runnable { adapter.submitList(videoStates, Runnable {
@ -59,6 +73,36 @@ 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()

View file

@ -3,7 +3,10 @@ package org.fnives.tiktokdownloader.ui.main.queue
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
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.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.ui.shared.Event import org.fnives.tiktokdownloader.ui.shared.Event
@ -12,7 +15,9 @@ import org.fnives.tiktokdownloader.ui.shared.asLiveData
class QueueViewModel( class QueueViewModel(
stateOfVideosObservableUseCase: StateOfVideosObservableUseCase, stateOfVideosObservableUseCase: StateOfVideosObservableUseCase,
private val addVideoToQueueUseCase: AddVideoToQueueUseCase, private val addVideoToQueueUseCase: AddVideoToQueueUseCase,
private val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase private val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase,
private val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase,
private val moveVideoInQueue: MoveVideoInQueue
) : ViewModel() { ) : ViewModel() {
val downloads = asLiveData(stateOfVideosObservableUseCase()) val downloads = asLiveData(stateOfVideosObservableUseCase())
@ -32,6 +37,21 @@ class QueueViewModel(
_navigationEvent.value = Event(NavigationEvent.OpenBrowser(url)) _navigationEvent.value = Event(NavigationEvent.OpenBrowser(url))
} }
fun onElementDeleted(videoState: VideoState) {
when (videoState) {
is VideoState.InPending -> removeVideoFromQueueUseCase(videoState.videoInPending)
is VideoState.Downloaded,
is VideoState.InProcess -> Unit
}
}
fun onElementMoved(moved: VideoState, to: VideoState): Boolean {
if (moved !is VideoState.InPending) return false
if (to !is VideoState.InPending) return false
moveVideoInQueue(moved.videoInPending, to.videoInPending)
return true
}
sealed class NavigationEvent { sealed class NavigationEvent {
data class OpenBrowser(val url: String) : NavigationEvent() data class OpenBrowser(val url: String) : NavigationEvent()
data class OpenGallery(val uri: String) : NavigationEvent() data class OpenGallery(val uri: String) : NavigationEvent()