diff --git a/app/build.gradle b/app/build.gradle
index f6c3faf..6ce5d91 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -36,6 +36,7 @@ android {
buildTypes {
debug {
versionNameSuffix "-dev"
+ applicationIdSuffix ".debug"
debuggable true
shrinkResources false
minifyEnabled false
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSource.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSource.kt
index 0941333..40cb5a2 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSource.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSource.kt
@@ -58,6 +58,12 @@ class VideoDownloadedLocalSource(
.plus(videoDownloaded.asString().addTimeAtStart())
}
+ fun removeVideo(videoDownloaded: VideoDownloaded) {
+ sharedPreferencesManagerImpl.downloadedVideos = sharedPreferencesManagerImpl.downloadedVideos
+ .filterNot { it.getTimeAndOriginal().second.asVideoDownloaded() == videoDownloaded }
+ .toSet()
+ }
+
companion object {
private fun VideoDownloaded.asString(): String =
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSource.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSource.kt
index ed4d856..6161d2a 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSource.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSource.kt
@@ -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.separateIntoDenormalized
import org.fnives.tiktokdownloader.data.model.VideoInPending
+import kotlin.math.abs
class VideoInPendingLocalSource(
private val sharedPreferencesManager: SharedPreferencesManager,
@@ -16,7 +17,7 @@ class VideoInPendingLocalSource(
get() = sharedPreferencesManager.pendingVideosFlow
.map { stringSet ->
stringSet.asSequence().map { timeThenUrl -> timeThenUrl.getTimeAndOriginal() }
- .sortedByDescending { it.first }
+ .sortedBy { it.first }
.map { it.second }
.map { it.asVideoInPending() }
.toList()
@@ -33,6 +34,27 @@ class VideoInPendingLocalSource(
.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 {
private fun VideoInPending.asString(): String = listOf(id, url).joinNormalized()
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SerializationHelper.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SerializationHelper.kt
index d34d35b..6340bcb 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SerializationHelper.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/data/local/persistent/SerializationHelper.kt
@@ -12,8 +12,8 @@ fun List.joinNormalized() : String =
fun String.separateIntoDenormalized() : List =
split("$SEPARATOR$SEPARATOR").map { it.denormalize() }
-fun String.addTimeAtStart(index: Int = 0) =
- "${System.currentTimeMillis() + index}$TIME_SEPARATOR$this"
+fun String.addTimeAtStart(time: Long = System.currentTimeMillis()) =
+ "${time}$TIME_SEPARATOR$this"
fun String.getTimeAndOriginal(): Pair {
val time = takeWhile { it != TIME_SEPARATOR }.toLong()
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/MoveVideoInQueueUseCase.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/MoveVideoInQueueUseCase.kt
new file mode 100644
index 0000000..2b1e275
--- /dev/null
+++ b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/MoveVideoInQueueUseCase.kt
@@ -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 MoveVideoInQueueUseCase(
+ private val videoInPendingLocalSource: VideoInPendingLocalSource
+) {
+
+ operator fun invoke(videoInPending: VideoInPending, positionDifference: Int) {
+ videoInPendingLocalSource.moveBy(videoInPending, positionDifference)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/RemoveVideoFromQueueUseCase.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/RemoveVideoFromQueueUseCase.kt
new file mode 100644
index 0000000..e84fc4a
--- /dev/null
+++ b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/RemoveVideoFromQueueUseCase.kt
@@ -0,0 +1,20 @@
+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.VideoInPending
+import org.fnives.tiktokdownloader.data.model.VideoState
+
+class RemoveVideoFromQueueUseCase(
+ private val videoInPendingLocalSource: VideoInPendingLocalSource,
+ private val videoDownloadedLocalSource: VideoDownloadedLocalSource
+) {
+
+ operator fun invoke(videoState: VideoState) {
+ when(videoState) {
+ is VideoState.Downloaded -> videoDownloadedLocalSource.removeVideo(videoState.videoDownloaded)
+ is VideoState.InPending -> videoInPendingLocalSource.removeVideoFromQueue(videoState.videoInPending)
+ is VideoState.InProcess -> Unit
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt
index b96bade..155df97 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/data/usecase/VideoDownloadingProcessorUseCase.kt
@@ -43,7 +43,7 @@ class VideoDownloadingProcessorUseCase(
private val fetch = MutableStateFlow(ProcessingState.RUNNING)
private val _processState by lazy {
- combineIntoPair(fetch, videoInPendingLocalSource.observeLastPendingVideo())
+ combineIntoPair(fetch, videoInPendingLocalSource.observeFirstPendingVideo())
.filter { it.first == ProcessingState.RUNNING }
.map { it.second }
.debounce(WORK_FLOW_DEBOUNCE)
@@ -134,7 +134,7 @@ class VideoDownloadingProcessorUseCase(
private fun combineIntoPair(flow1: Flow, flow2: Flow): Flow> =
combine(flow1, flow2) { item1, item2 -> item1 to item2 }
- private fun VideoInPendingLocalSource.observeLastPendingVideo(): Flow =
- pendingVideos.map { it.lastOrNull() }.distinctUntilChanged()
+ private fun VideoInPendingLocalSource.observeFirstPendingVideo(): Flow =
+ pendingVideos.map { it.firstOrNull() }.distinctUntilChanged()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/di/module/LocalSourceModule.kt b/app/src/main/java/org/fnives/tiktokdownloader/di/module/LocalSourceModule.kt
index 4c311b0..f0ce819 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/di/module/LocalSourceModule.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/di/module/LocalSourceModule.kt
@@ -8,17 +8,17 @@ import java.util.concurrent.TimeUnit
class LocalSourceModule(private val androidFileManagementModule: AndroidFileManagementModule) {
- val videoDownloadedLocalSource: VideoDownloadedLocalSource
- get() = VideoDownloadedLocalSource(
+ val videoDownloadedLocalSource: VideoDownloadedLocalSource by lazy {
+ VideoDownloadedLocalSource(
saveVideoFile = androidFileManagementModule.saveVideoFile,
sharedPreferencesManagerImpl = androidFileManagementModule.sharedPreferencesManager,
verifyFileForUriExists = androidFileManagementModule.verifyFileForUriExists
)
+ }
- val videoInPendingLocalSource: VideoInPendingLocalSource
- get() = VideoInPendingLocalSource(
- sharedPreferencesManager = androidFileManagementModule.sharedPreferencesManager
- )
+ val videoInPendingLocalSource: VideoInPendingLocalSource by lazy {
+ VideoInPendingLocalSource(sharedPreferencesManager = androidFileManagementModule.sharedPreferencesManager)
+ }
val videoInProgressLocalSource: VideoInProgressLocalSource by lazy { VideoInProgressLocalSource() }
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/di/module/UseCaseModule.kt b/app/src/main/java/org/fnives/tiktokdownloader/di/module/UseCaseModule.kt
index c2a3977..95261f7 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/di/module/UseCaseModule.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/di/module/UseCaseModule.kt
@@ -2,13 +2,16 @@ package org.fnives.tiktokdownloader.di.module
import kotlinx.coroutines.Dispatchers
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.UrlVerificationUseCase
import org.fnives.tiktokdownloader.data.usecase.VideoDownloadingProcessorUseCase
class UseCaseModule(
private val localSourceModule: LocalSourceModule,
- private val networkModule: NetworkModule) {
+ private val networkModule: NetworkModule
+) {
val stateOfVideosObservableUseCase: StateOfVideosObservableUseCase
get() = StateOfVideosObservableUseCase(
@@ -24,7 +27,19 @@ class UseCaseModule(
val addVideoToQueueUseCase: AddVideoToQueueUseCase
get() = AddVideoToQueueUseCase(
urlVerificationUseCase,
- localSourceModule.videoInPendingLocalSource)
+ localSourceModule.videoInPendingLocalSource
+ )
+
+ val removeVideoFromQueueUseCase: RemoveVideoFromQueueUseCase
+ get() = RemoveVideoFromQueueUseCase(
+ localSourceModule.videoInPendingLocalSource,
+ localSourceModule.videoDownloadedLocalSource
+ )
+
+ val moveVideoInQueueUseCase: MoveVideoInQueueUseCase
+ get() = MoveVideoInQueueUseCase(
+ localSourceModule.videoInPendingLocalSource
+ )
val videoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase by lazy {
VideoDownloadingProcessorUseCase(
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/di/module/ViewModelModule.kt b/app/src/main/java/org/fnives/tiktokdownloader/di/module/ViewModelModule.kt
index e12b6bf..123b183 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/di/module/ViewModelModule.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/di/module/ViewModelModule.kt
@@ -24,6 +24,8 @@ class ViewModelModule(private val useCaseModule: UseCaseModule) {
get() = QueueViewModel(
useCaseModule.stateOfVideosObservableUseCase,
useCaseModule.addVideoToQueueUseCase,
- useCaseModule.videoDownloadingProcessorUseCase
+ useCaseModule.removeVideoFromQueueUseCase,
+ useCaseModule.videoDownloadingProcessorUseCase,
+ useCaseModule.moveVideoInQueueUseCase
)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/MovingItemCallback.kt b/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/MovingItemCallback.kt
new file mode 100644
index 0000000..b9f5b20
--- /dev/null
+++ b/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/MovingItemCallback.kt
@@ -0,0 +1,8 @@
+package org.fnives.tiktokdownloader.ui.main.queue
+
+interface MovingItemCallback {
+
+ fun onMovingStart()
+
+ fun onMovingEnd()
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/QueueFragment.kt b/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/QueueFragment.kt
index 9baaf19..1502e10 100644
--- a/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/QueueFragment.kt
+++ b/app/src/main/java/org/fnives/tiktokdownloader/ui/main/queue/QueueFragment.kt
@@ -9,7 +9,7 @@ import android.widget.EditText
import androidx.core.net.toUri
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
-import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.fnives.tiktokdownloader.R
import org.fnives.tiktokdownloader.data.model.VideoState
@@ -22,41 +22,59 @@ class QueueFragment : Fragment(R.layout.fragment_queue) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recycler = view.findViewById(R.id.recycler)
- val adapter = QueueItemAdapter(
- itemClicked = viewModel::onItemClicked,
- urlClicked = viewModel::onUrlClicked
- )
- recycler.adapter = adapter
+ recyclerViewSetup(recycler)
+ navigationSetup()
+
val saveUrlCta = view.findViewById
+ \n\nNote:
+ \n- Clicking on video will open the Gallery app.
+ \n- Clicking on a video URL will open in TikTok.
+ \n- Swiping away a video will remove it from the list.
+ \n- When not loading, Pending videos can be reordered by dragging.
How To Use From TikTok
- In TikTok press share.
\n- Select Other.
diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt
index f8c629b..5b5d0f9 100644
--- a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt
+++ b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoDownloadedLocalSourceTest.kt
@@ -29,6 +29,7 @@ import org.mockito.kotlin.whenever
import java.io.InputStream
+@Suppress("TestFunctionName")
@Timeout(value = 2)
class VideoDownloadedLocalSourceTest {
@@ -195,6 +196,50 @@ class VideoDownloadedLocalSourceTest {
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() {
override fun read(): Int = 0
}
diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt
index 03a10b2..28299fe 100644
--- a/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt
+++ b/app/src/test/java/org/fnives/tiktokdownloader/data/local/VideoInPendingLocalSourceTest.kt
@@ -17,7 +17,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
-
+@Suppress("TestFunctionName")
@Timeout(value = 2)
class VideoInPendingLocalSourceTest {
@@ -76,11 +76,10 @@ class VideoInPendingLocalSourceTest {
}
@Test
- fun GIVEN_observing_PendingVideos_WHEN_2_video_marked_as_pending_THEN_both_of_them_are_sent_out_in_correct_reverse_order() =
- runBlocking {
+ fun GIVEN_observing_PendingVideos_WHEN_2_video_marked_as_pending_THEN_both_of_them_are_sent_out_in_correct_order() = runBlocking {
val videoInPending1 = VideoInPending("id1", "alma1")
val videoInPending2 = VideoInPending("id2", "alma2")
- val expected = listOf(emptyList(), listOf(videoInPending1), listOf(videoInPending2, videoInPending1))
+ val expected = listOf(emptyList(), listOf(videoInPending1), listOf(videoInPending1, videoInPending2))
val actual = async(coroutineContext) {
sut.pendingVideos.take(3).toList()
@@ -94,4 +93,92 @@ class VideoInPendingLocalSourceTest {
Assertions.assertEquals(expected, actual.await())
}
+ @Test
+ fun GIVEN_2_videos_WHEN_moving_first_one_down_by_one_THEN_it_is_moved_properly() = runBlocking {
+ 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 {
+ 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 {
+ 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())
+ }
}
\ No newline at end of file
diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/MoveVideoInQueueUseCaseTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/MoveVideoInQueueUseCaseTest.kt
new file mode 100644
index 0000000..e050581
--- /dev/null
+++ b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/MoveVideoInQueueUseCaseTest.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/RemoveVideoFromQueueUseCaseTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/RemoveVideoFromQueueUseCaseTest.kt
new file mode 100644
index 0000000..1190fa5
--- /dev/null
+++ b/app/src/test/java/org/fnives/tiktokdownloader/data/usecase/RemoveVideoFromQueueUseCaseTest.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt b/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt
index 71e7c88..b813765 100644
--- a/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt
+++ b/app/src/test/java/org/fnives/tiktokdownloader/ui/main/queue/QueueViewModelTest.kt
@@ -2,9 +2,13 @@ package org.fnives.tiktokdownloader.ui.main.queue
import com.jraska.livedata.test
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.VideoInProgress
import org.fnives.tiktokdownloader.data.model.VideoState
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.VideoDownloadingProcessorUseCase
import org.fnives.tiktokdownloader.helper.junit.rule.InstantExecutorExtension
@@ -28,7 +32,9 @@ class QueueViewModelTest {
private lateinit var stateOfVideosFlow: MutableSharedFlow>
private lateinit var mockStateOfVideosObservableUseCase: StateOfVideosObservableUseCase
private lateinit var mockAddVideoToQueueUseCase: AddVideoToQueueUseCase
+ private lateinit var mockRemoveVideoFromQueueUseCase: RemoveVideoFromQueueUseCase
private lateinit var mockVideoDownloadingProcessorUseCase: VideoDownloadingProcessorUseCase
+ private lateinit var mockMoveVideoInQueueUseCase: MoveVideoInQueueUseCase
private lateinit var sut: QueueViewModel
@BeforeEach
@@ -37,8 +43,16 @@ class QueueViewModelTest {
mockStateOfVideosObservableUseCase = mock()
whenever(mockStateOfVideosObservableUseCase.invoke()).doReturn(stateOfVideosFlow)
mockAddVideoToQueueUseCase = mock()
+ mockRemoveVideoFromQueueUseCase = mock()
mockVideoDownloadingProcessorUseCase = mock()
- sut = QueueViewModel(mockStateOfVideosObservableUseCase, mockAddVideoToQueueUseCase, mockVideoDownloadingProcessorUseCase)
+ mockMoveVideoInQueueUseCase = mock()
+ sut = QueueViewModel(
+ mockStateOfVideosObservableUseCase,
+ mockAddVideoToQueueUseCase,
+ mockRemoveVideoFromQueueUseCase,
+ mockVideoDownloadingProcessorUseCase,
+ mockMoveVideoInQueueUseCase
+ )
}
@Test
@@ -48,6 +62,8 @@ class QueueViewModelTest {
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
verifyNoInteractions(mockAddVideoToQueueUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
+ verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
+ verifyNoInteractions(mockMoveVideoInQueueUseCase)
}
@Test
@@ -60,6 +76,8 @@ class QueueViewModelTest {
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
verifyNoInteractions(mockAddVideoToQueueUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
+ verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
+ verifyNoInteractions(mockMoveVideoInQueueUseCase)
}
@Test
@@ -77,6 +95,8 @@ class QueueViewModelTest {
verifyNoMoreInteractions(mockStateOfVideosObservableUseCase)
verifyNoInteractions(mockAddVideoToQueueUseCase)
verifyNoInteractions(mockVideoDownloadingProcessorUseCase)
+ verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
+ verifyNoInteractions(mockMoveVideoInQueueUseCase)
}
@Test
@@ -89,6 +109,8 @@ class QueueViewModelTest {
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
verify(mockVideoDownloadingProcessorUseCase, times(1)).fetchVideoInState()
verifyNoMoreInteractions(mockVideoDownloadingProcessorUseCase)
+ verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
+ verifyNoInteractions(mockMoveVideoInQueueUseCase)
}
@Test
@@ -103,6 +125,8 @@ class QueueViewModelTest {
verifyNoMoreInteractions(mockAddVideoToQueueUseCase)
verify(mockVideoDownloadingProcessorUseCase, times(2)).fetchVideoInState()
verifyNoMoreInteractions(mockVideoDownloadingProcessorUseCase)
+ verifyNoInteractions(mockRemoveVideoFromQueueUseCase)
+ verifyNoInteractions(mockMoveVideoInQueueUseCase)
}
@Test
@@ -125,4 +149,86 @@ class QueueViewModelTest {
.assertHistorySize(1)
.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)
+ }
}
\ No newline at end of file