From 2698368aee72d0d759fd57c387f0c5bbabbc64f5 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Fri, 10 Jan 2025 15:42:59 +0100 Subject: [PATCH] refactor: bind playlist repositories to interfaces --- .../github/libretube/api/PlaylistsHelper.kt | 130 +++++------------- .../{api => repo}/LocalPlaylistsRepository.kt | 41 ++++-- .../libretube/repo/PipedPlaylistRepository.kt | 81 +++++++++++ .../libretube/repo/PlaylistRepository.kt | 19 +++ .../ui/dialogs/DeletePlaylistDialog.kt | 8 +- .../ui/sheets/PlaylistOptionsBottomSheet.kt | 3 +- 6 files changed, 162 insertions(+), 120 deletions(-) rename app/src/main/java/com/github/libretube/{api => repo}/LocalPlaylistsRepository.kt (81%) create mode 100644 app/src/main/java/com/github/libretube/repo/PipedPlaylistRepository.kt create mode 100644 app/src/main/java/com/github/libretube/repo/PlaylistRepository.kt diff --git a/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt b/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt index e54ffdcf2..c158a4601 100644 --- a/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt +++ b/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt @@ -1,16 +1,16 @@ package com.github.libretube.api import androidx.core.text.isDigitsOnly -import com.github.libretube.api.obj.EditPlaylistBody -import com.github.libretube.api.obj.Message import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.PreferenceKeys import com.github.libretube.enums.PlaylistType -import com.github.libretube.extensions.toID import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.obj.PipedImportPlaylist +import com.github.libretube.repo.LocalPlaylistsRepository +import com.github.libretube.repo.PipedPlaylistRepository +import com.github.libretube.repo.PlaylistRepository import com.github.libretube.util.deArrow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -24,14 +24,14 @@ object PlaylistsHelper { private val token get() = PreferenceHelper.getToken() val loggedIn: Boolean get() = token.isNotEmpty() - private fun Message.isOk() = this.message == "ok" + private val playlistsRepository: PlaylistRepository + get() = when { + loggedIn -> PipedPlaylistRepository() + else -> LocalPlaylistsRepository() + } suspend fun getPlaylists(): List = withContext(Dispatchers.IO) { - val playlists = if (loggedIn) { - RetrofitInstance.authApi.getUserPlaylists(token) - } else { - LocalPlaylistsRepository.getPlaylists() - } + val playlists = playlistsRepository.getPlaylists() sortPlaylists(playlists) } @@ -52,109 +52,41 @@ object PlaylistsHelper { suspend fun getPlaylist(playlistId: String): Playlist { // load locally stored playlists with the auth api return when (getPrivatePlaylistType(playlistId)) { - PlaylistType.PRIVATE -> RetrofitInstance.authApi.getPlaylist(playlistId) PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId) - PlaylistType.LOCAL -> LocalPlaylistsRepository.getPlaylist(playlistId) + else -> playlistsRepository.getPlaylist(playlistId) }.apply { relatedStreams = relatedStreams.deArrow() } } - suspend fun createPlaylist(playlistName: String): String? { - return if (!loggedIn) { - LocalPlaylistsRepository.createPlaylist(playlistName) - } else { - RetrofitInstance.authApi.createPlaylist( - token, - Playlists(name = playlistName) - ).playlistId - } - } - - suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean { - if (!loggedIn) { - LocalPlaylistsRepository.addToPlaylist(playlistId, *videos) - return true - } - - val playlist = EditPlaylistBody(playlistId, videoIds = videos.map { it.url!!.toID() }) - return RetrofitInstance.authApi.addToPlaylist(token, playlist).isOk() - } - - suspend fun renamePlaylist(playlistId: String, newName: String): Boolean { - if (!loggedIn) { - LocalPlaylistsRepository.renamePlaylist(playlistId, newName) - return true - } - - val playlist = EditPlaylistBody(playlistId, newName = newName) - return RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk() - } - - suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean { - if (!loggedIn) { - LocalPlaylistsRepository.changePlaylistDescription(playlistId, newDescription) - return true - } - - val playlist = EditPlaylistBody(playlistId, description = newDescription) - return RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk() - } - - suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean { - if (!loggedIn) { - LocalPlaylistsRepository.removeFromPlaylist(playlistId, index) - return true - } - - return RetrofitInstance.authApi.removeFromPlaylist( - PreferenceHelper.getToken(), - EditPlaylistBody(playlistId = playlistId, index = index) - ).isOk() - } - - suspend fun importPlaylists(playlists: List) = - withContext(Dispatchers.IO) { - if (!loggedIn) return@withContext LocalPlaylistsRepository.importPlaylists(playlists) - - for (playlist in playlists) { - val playlistId = createPlaylist(playlist.name!!) ?: return@withContext - val streams = playlist.videos.map { StreamItem(url = it) } - addToPlaylist(playlistId, *streams.toTypedArray()) - } - } - - suspend fun getAllPlaylistsWithVideos(playlistIds: List? = null): List = - withContext(Dispatchers.IO) { + suspend fun getAllPlaylistsWithVideos(playlistIds: List? = null): List { + return withContext(Dispatchers.IO) { (playlistIds ?: getPlaylists().map { it.id!! }) .map { async { getPlaylist(it) } } .awaitAll() } - - suspend fun clonePlaylist(playlistId: String): String? { - if (!loggedIn) { - return LocalPlaylistsRepository.clonePlaylist(playlistId) - } - - return RetrofitInstance.authApi.clonePlaylist( - token, - EditPlaylistBody(playlistId) - ).playlistId } - suspend fun deletePlaylist(playlistId: String, playlistType: PlaylistType): Boolean { - if (playlistType == PlaylistType.LOCAL) { - LocalPlaylistsRepository.deletePlaylist(playlistId) - return true - } + suspend fun createPlaylist(playlistName: String) = + playlistsRepository.createPlaylist(playlistName) - return runCatching { - RetrofitInstance.authApi.deletePlaylist( - PreferenceHelper.getToken(), - EditPlaylistBody(playlistId) - ).isOk() - }.getOrDefault(false) - } + suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) = + playlistsRepository.addToPlaylist(playlistId, *videos) + + suspend fun renamePlaylist(playlistId: String, newName: String) = + playlistsRepository.renamePlaylist(playlistId, newName) + + suspend fun changePlaylistDescription(playlistId: String, newDescription: String) = + playlistsRepository.changePlaylistDescription(playlistId, newDescription) + + suspend fun removeFromPlaylist(playlistId: String, index: Int) = + playlistsRepository.removeFromPlaylist(playlistId, index) + + suspend fun importPlaylists(playlists: List) = + playlistsRepository.importPlaylists(playlists) + + suspend fun clonePlaylist(playlistId: String) = playlistsRepository.clonePlaylist(playlistId) + suspend fun deletePlaylist(playlistId: String) = playlistsRepository.deletePlaylist(playlistId) fun getPrivatePlaylistType(): PlaylistType { return if (loggedIn) PlaylistType.PRIVATE else PlaylistType.LOCAL diff --git a/app/src/main/java/com/github/libretube/api/LocalPlaylistsRepository.kt b/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt similarity index 81% rename from app/src/main/java/com/github/libretube/api/LocalPlaylistsRepository.kt rename to app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt index 13f2dfa35..c99389003 100644 --- a/app/src/main/java/com/github/libretube/api/LocalPlaylistsRepository.kt +++ b/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt @@ -1,6 +1,9 @@ -package com.github.libretube.api +package com.github.libretube.repo +import com.github.libretube.api.PlaylistsHelper import com.github.libretube.api.PlaylistsHelper.MAX_CONCURRENT_IMPORT_CALLS +import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.StreamItem @@ -10,8 +13,8 @@ import com.github.libretube.extensions.parallelMap import com.github.libretube.helpers.ProxyHelper import com.github.libretube.obj.PipedImportPlaylist -object LocalPlaylistsRepository { - suspend fun getPlaylist(playlistId: String): Playlist { +class LocalPlaylistsRepository: PlaylistRepository { + override suspend fun getPlaylist(playlistId: String): Playlist { val relation = DatabaseHolder.Database.localPlaylistsDao().getAll() .first { it.playlist.id.toString() == playlistId } @@ -24,7 +27,7 @@ object LocalPlaylistsRepository { ) } - suspend fun getPlaylists(): List { + override suspend fun getPlaylists(): List { return DatabaseHolder.Database.localPlaylistsDao().getAll() .map { Playlists( @@ -37,7 +40,7 @@ object LocalPlaylistsRepository { } } - suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) { + override suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean { val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll() .first { it.playlist.id.toString() == playlistId } @@ -59,25 +62,31 @@ object LocalPlaylistsRepository { } } } + + return true } - suspend fun renamePlaylist(playlistId: String, newName: String) { + override suspend fun renamePlaylist(playlistId: String, newName: String): Boolean { val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll() .first { it.playlist.id.toString() == playlistId }.playlist playlist.name = newName DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist) + + return true } - suspend fun changePlaylistDescription(playlistId: String, newDescription: String) { + override suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean { val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll() .first { it.playlist.id.toString() == playlistId }.playlist playlist.description = newDescription DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist) + + return true } - suspend fun clonePlaylist(playlistId: String): String? { + override suspend fun clonePlaylist(playlistId: String): String { val playlist = RetrofitInstance.api.getPlaylist(playlistId) - val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null + val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") PlaylistsHelper.addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray()) @@ -93,7 +102,7 @@ object LocalPlaylistsRepository { return playlistId } - suspend fun removeFromPlaylist(playlistId: String, index: Int) { + override suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean { val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll() .first { it.playlist.id.toString() == playlistId } DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo( @@ -105,11 +114,13 @@ object LocalPlaylistsRepository { transaction.videos.getOrNull(1)?.thumbnailUrl.orEmpty() } DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist) + + return true } - suspend fun importPlaylists(playlists: List) { + override suspend fun importPlaylists(playlists: List) { for (playlist in playlists) { - val playlistId = createPlaylist(playlist.name!!) ?: return + val playlistId = createPlaylist(playlist.name!!) // if not logged in, all video information needs to become fetched manually // Only do so with `MAX_CONCURRENT_IMPORT_CALLS` videos at once to prevent performance issues @@ -125,13 +136,15 @@ object LocalPlaylistsRepository { } } - suspend fun createPlaylist(playlistName: String): String { + override suspend fun createPlaylist(playlistName: String): String { val playlist = LocalPlaylist(name = playlistName, thumbnailUrl = "") return DatabaseHolder.Database.localPlaylistsDao().createPlaylist(playlist).toString() } - suspend fun deletePlaylist(playlistId: String) { + override suspend fun deletePlaylist(playlistId: String): Boolean { DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId) DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId) + + return true } } \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/repo/PipedPlaylistRepository.kt b/app/src/main/java/com/github/libretube/repo/PipedPlaylistRepository.kt new file mode 100644 index 000000000..346bc8753 --- /dev/null +++ b/app/src/main/java/com/github/libretube/repo/PipedPlaylistRepository.kt @@ -0,0 +1,81 @@ +package com.github.libretube.repo + +import com.github.libretube.api.PlaylistsHelper +import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.obj.EditPlaylistBody +import com.github.libretube.api.obj.Message +import com.github.libretube.api.obj.Playlist +import com.github.libretube.api.obj.Playlists +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.extensions.toID +import com.github.libretube.helpers.PreferenceHelper +import com.github.libretube.obj.PipedImportPlaylist + +class PipedPlaylistRepository: PlaylistRepository { + private fun Message.isOk() = this.message == "ok" + private val token get() = PreferenceHelper.getToken() + + override suspend fun getPlaylist(playlistId: String): Playlist { + return RetrofitInstance.authApi.getPlaylist(playlistId) + } + + override suspend fun getPlaylists(): List { + return RetrofitInstance.authApi.getUserPlaylists(token) + } + + override suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean { + val playlist = EditPlaylistBody(playlistId, videoIds = videos.map { it.url!!.toID() }) + + return RetrofitInstance.authApi.addToPlaylist(token, playlist).isOk() + } + + override suspend fun renamePlaylist(playlistId: String, newName: String): Boolean { + val playlist = EditPlaylistBody(playlistId, newName = newName) + + return RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk() + } + + override suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean { + val playlist = EditPlaylistBody(playlistId, description = newDescription) + + return RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk() + } + + override suspend fun clonePlaylist(playlistId: String): String? { + return RetrofitInstance.authApi.clonePlaylist( + token, + EditPlaylistBody(playlistId) + ).playlistId + } + + override suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean { + return RetrofitInstance.authApi.removeFromPlaylist( + PreferenceHelper.getToken(), + EditPlaylistBody(playlistId = playlistId, index = index) + ).isOk() + } + + override suspend fun importPlaylists(playlists: List) { + for (playlist in playlists) { + val playlistId = PlaylistsHelper.createPlaylist(playlist.name!!) ?: return + val streams = playlist.videos.map { StreamItem(url = it) } + PlaylistsHelper.addToPlaylist(playlistId, *streams.toTypedArray()) + } + } + + override suspend fun createPlaylist(playlistName: String): String? { + return RetrofitInstance.authApi.createPlaylist( + token, + Playlists(name = playlistName) + ).playlistId + } + + override suspend fun deletePlaylist(playlistId: String): Boolean { + return runCatching { + RetrofitInstance.authApi.deletePlaylist( + PreferenceHelper.getToken(), + EditPlaylistBody(playlistId) + ).isOk() + }.getOrDefault(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/repo/PlaylistRepository.kt b/app/src/main/java/com/github/libretube/repo/PlaylistRepository.kt new file mode 100644 index 000000000..0c99457ad --- /dev/null +++ b/app/src/main/java/com/github/libretube/repo/PlaylistRepository.kt @@ -0,0 +1,19 @@ +package com.github.libretube.repo + +import com.github.libretube.api.obj.Playlist +import com.github.libretube.api.obj.Playlists +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.obj.PipedImportPlaylist + +interface PlaylistRepository { + suspend fun getPlaylist(playlistId: String): Playlist + suspend fun getPlaylists(): List + suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean + suspend fun renamePlaylist(playlistId: String, newName: String): Boolean + suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean + suspend fun clonePlaylist(playlistId: String): String? + suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean + suspend fun importPlaylists(playlists: List) + suspend fun createPlaylist(playlistName: String): String? + suspend fun deletePlaylist(playlistId: String): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/DeletePlaylistDialog.kt index 4bdf78d5a..fe5c4d6c6 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/DeletePlaylistDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/DeletePlaylistDialog.kt @@ -10,8 +10,6 @@ import androidx.lifecycle.lifecycleScope import com.github.libretube.R import com.github.libretube.api.PlaylistsHelper import com.github.libretube.constants.IntentData -import com.github.libretube.enums.PlaylistType -import com.github.libretube.extensions.serializable import com.github.libretube.extensions.toastFromMainDispatcher import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -21,14 +19,14 @@ import kotlinx.coroutines.withContext class DeletePlaylistDialog : DialogFragment() { private lateinit var playlistId: String - private lateinit var playlistType: PlaylistType + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { playlistId = it.getString(IntentData.playlistId)!! - playlistType = it.serializable(IntentData.playlistType)!! } } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.deletePlaylist) @@ -39,7 +37,7 @@ class DeletePlaylistDialog : DialogFragment() { .apply { getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { lifecycleScope.launch(Dispatchers.IO) { - val success = PlaylistsHelper.deletePlaylist(playlistId, playlistType) + val success = PlaylistsHelper.deletePlaylist(playlistId) context.toastFromMainDispatcher( if (success) R.string.success else R.string.fail ) diff --git a/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt index b09eed713..76f5b04f5 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt @@ -123,8 +123,7 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() { R.string.deletePlaylist -> { val newDeletePlaylistDialog = DeletePlaylistDialog() newDeletePlaylistDialog.arguments = bundleOf( - IntentData.playlistId to playlistId, - IntentData.playlistType to playlistType + IntentData.playlistId to playlistId ) newDeletePlaylistDialog.show(mFragmentManager, null) }