Skip to content

Commit 7dde8ff

Browse files
committed
feat: add support for syncing playlist bookmarks
1 parent 30806f1 commit 7dde8ff

7 files changed

Lines changed: 249 additions & 139 deletions

File tree

app/src/main/java/com/github/libretube/repo/LibreTubeSyncServerUserDataRepository.kt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,55 @@ class LibreTubeSyncServerUserDataRepository : UserDataRepository {
267267
api.removeFromSubscriptionGroup(subscriptionGroupId, channelId)
268268
}
269269
}
270+
271+
override suspend fun getPlaylistBookmarks(): List<PlaylistBookmark> {
272+
return tryHttpOrRaiseError {
273+
api.getPlaylistBookmarks().map {
274+
it.toPlaylistBookmark()
275+
}
276+
}
277+
}
278+
279+
override suspend fun getPlaylistBookmark(playlistId: String): PlaylistBookmark? {
280+
return tryHttpOrRaiseError {
281+
try {
282+
api.getPlaylistBookmark(playlistId).toPlaylistBookmark()
283+
} catch (e: HttpException) {
284+
// The API returns 404 Not Found if the playlist is not bookmarked
285+
if (e.code() == 404) return@tryHttpOrRaiseError null
286+
else throw e
287+
}
288+
}
289+
}
290+
291+
private fun PlaylistBookmark.toExtendedPublicPlaylist(): ExtendedPublicPlaylist =
292+
ExtendedPublicPlaylist(
293+
playlist = ExtendedPlaylist(
294+
id = playlistId,
295+
title = playlistName.orEmpty(),
296+
description = "", // TODO: also store and support playlist descriptions
297+
thumbnailUrl = thumbnailUrl,
298+
videoCount = videos.toLong()
299+
),
300+
uploader = Channel(
301+
id = uploaderUrl.orEmpty(),
302+
avatar = uploaderAvatar.orEmpty(),
303+
name = uploader.orEmpty(),
304+
verified = false
305+
)
306+
)
307+
308+
override suspend fun createPlaylistBookmark(playlist: PlaylistBookmark) {
309+
tryHttpOrRaiseError {
310+
api.createPlaylistBookmark(
311+
playlist.toExtendedPublicPlaylist()
312+
)
313+
}
314+
}
315+
316+
override suspend fun deletePlaylistBookmark(playlistId: String) {
317+
tryHttpOrRaiseError {
318+
api.deletePlaylistBookmark(playlistId)
319+
}
320+
}
270321
}

app/src/main/java/com/github/libretube/repo/LocalUserDataRepository.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,20 @@ class LocalUserDataRepository : UserDataRepository {
224224
group.channels = group.channels.filter { it != channelId }
225225
Database.subscriptionGroupsDao().updateGroup(group)
226226
}
227+
228+
override suspend fun getPlaylistBookmarks(): List<PlaylistBookmark> {
229+
return Database.playlistBookmarkDao().getAll()
230+
}
231+
232+
override suspend fun getPlaylistBookmark(playlistId: String): PlaylistBookmark? {
233+
return Database.playlistBookmarkDao().findById(playlistId)
234+
}
235+
236+
override suspend fun createPlaylistBookmark(playlist: PlaylistBookmark) {
237+
Database.playlistBookmarkDao().insert(playlist)
238+
}
239+
240+
override suspend fun deletePlaylistBookmark(playlistId: String) {
241+
Database.playlistBookmarkDao().deleteById(playlistId)
242+
}
227243
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import androidx.recyclerview.widget.ListAdapter
99
import com.github.libretube.R
1010
import com.github.libretube.constants.IntentData
1111
import com.github.libretube.databinding.PlaylistsRowBinding
12-
import com.github.libretube.db.DatabaseHolder
1312
import com.github.libretube.db.obj.PlaylistBookmark
1413
import com.github.libretube.enums.PlaylistType
1514
import com.github.libretube.helpers.ImageHelper
1615
import com.github.libretube.helpers.NavigationHelper
16+
import com.github.libretube.repo.UserDataRepositoryHelper
1717
import com.github.libretube.ui.adapters.callbacks.DiffUtilItemCallback
1818
import com.github.libretube.ui.base.BaseActivity
1919
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
@@ -64,10 +64,11 @@ class PlaylistBookmarkAdapter: ListAdapter<PlaylistBookmark, PlaylistBookmarkVie
6464
)
6565
CoroutineScope(Dispatchers.IO).launch {
6666
if (!isBookmarked) {
67-
DatabaseHolder.Database.playlistBookmarkDao()
68-
.deleteById(bookmark.playlistId)
67+
UserDataRepositoryHelper.userDataRepository
68+
.deletePlaylistBookmark(bookmark.playlistId)
6969
} else {
70-
DatabaseHolder.Database.playlistBookmarkDao().insert(bookmark)
70+
UserDataRepositoryHelper.userDataRepository
71+
.createPlaylistBookmark(bookmark)
7172
}
7273
}
7374
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import com.github.libretube.api.obj.Playlists
2121
import com.github.libretube.constants.IntentData
2222
import com.github.libretube.constants.PreferenceKeys
2323
import com.github.libretube.databinding.FragmentLibraryBinding
24-
import com.github.libretube.db.DatabaseHolder
2524
import com.github.libretube.extensions.TAG
2625
import com.github.libretube.extensions.ceilHalf
2726
import com.github.libretube.extensions.dpToPx
2827
import com.github.libretube.helpers.NavBarHelper
2928
import com.github.libretube.helpers.PreferenceHelper
29+
import com.github.libretube.repo.UserDataRepositoryHelper
3030
import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
3131
import com.github.libretube.ui.adapters.PlaylistsAdapter
3232
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
@@ -145,7 +145,7 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library)
145145
private fun initBookmarks() {
146146
lifecycleScope.launch {
147147
val bookmarks = withContext(Dispatchers.IO) {
148-
DatabaseHolder.Database.playlistBookmarkDao().getAll()
148+
UserDataRepositoryHelper.userDataRepository.getPlaylistBookmarks()
149149
}
150150

151151
val binding = _binding ?: return@launch

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

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.github.libretube.extensions.toastFromMainDispatcher
3838
import com.github.libretube.helpers.ImageHelper
3939
import com.github.libretube.helpers.NavigationHelper
4040
import com.github.libretube.helpers.PreferenceHelper
41+
import com.github.libretube.repo.UserDataRepositoryHelper
4142
import com.github.libretube.ui.adapters.PlaylistAdapter
4243
import com.github.libretube.ui.adapters.PlaylistItem
4344
import com.github.libretube.ui.base.BaseActivity
@@ -54,7 +55,6 @@ import kotlinx.coroutines.Dispatchers
5455
import kotlinx.coroutines.launch
5556
import kotlinx.coroutines.runBlocking
5657
import kotlinx.coroutines.withContext
57-
import org.schabi.newpipe.extractor.timeago.patterns.it
5858

5959
class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist) {
6060
private var _binding: FragmentPlaylistBinding? = null
@@ -100,10 +100,14 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
100100

101101
binding.playlistProgress.isVisible = true
102102

103-
isBookmarked = runBlocking(Dispatchers.IO) {
104-
DatabaseHolder.Database.playlistBookmarkDao().includes(playlistId)
103+
lifecycleScope.launch(Dispatchers.IO) {
104+
isBookmarked =
105+
UserDataRepositoryHelper.userDataRepository.getPlaylistBookmark(playlistId) != null
106+
107+
withContext(Dispatchers.Main) {
108+
updateBookmarkRes()
109+
}
105110
}
106-
updateBookmarkRes()
107111

108112
commonPlayerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) {
109113
binding.playlistRecView.updatePadding(bottom = if (it) 64f.dpToPx() else 0)
@@ -185,7 +189,8 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
185189
)
186190
}
187191

188-
binding.playlistInfo.text = getChannelAndVideoString(response, playlistFeed.size)
192+
binding.playlistInfo.text =
193+
getChannelAndVideoString(response, playlistFeed.size)
189194
}
190195
})
191196

@@ -266,11 +271,12 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
266271
updateBookmarkRes()
267272
lifecycleScope.launch(Dispatchers.IO) {
268273
if (!isBookmarked) {
269-
DatabaseHolder.Database.playlistBookmarkDao()
270-
.deleteById(playlistId)
274+
UserDataRepositoryHelper.userDataRepository
275+
.deletePlaylistBookmark(playlistId)
271276
} else {
272-
DatabaseHolder.Database.playlistBookmarkDao()
273-
.insert(response.toPlaylistBookmark(playlistId))
277+
val bookmark = response.toPlaylistBookmark(playlistId)
278+
UserDataRepositoryHelper.userDataRepository
279+
.createPlaylistBookmark(bookmark)
274280
}
275281
}
276282
}
@@ -339,14 +345,14 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
339345
withContext(Dispatchers.IO) {
340346
// update the playlist thumbnail and title if bookmarked
341347
val playlistBookmark =
342-
DatabaseHolder.Database.playlistBookmarkDao().findById(playlistId)
348+
UserDataRepositoryHelper.userDataRepository.getPlaylistBookmark(playlistId)
343349
?: return@withContext
344350
if (playlistBookmark.thumbnailUrl != playlist.thumbnailUrl ||
345351
playlistBookmark.playlistName != playlist.name ||
346352
playlistBookmark.videos != playlist.videos
347353
) {
348-
DatabaseHolder.Database.playlistBookmarkDao()
349-
.update(playlist.toPlaylistBookmark(playlistBookmark.playlistId))
354+
val bookmark = playlist.toPlaylistBookmark(playlistBookmark.playlistId)
355+
UserDataRepositoryHelper.userDataRepository.createPlaylistBookmark(bookmark)
350356
}
351357
}
352358
}
@@ -402,7 +408,11 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
402408
// try to remove the video from the playlist and show an undo snackbar if successful
403409
lifecycleScope.launch(Dispatchers.IO) {
404410
try {
405-
PlaylistsHelper.removeFromPlaylist(playlistId, video.url!!.toID(), originalPlaylistPosition)
411+
PlaylistsHelper.removeFromPlaylist(
412+
playlistId,
413+
video.url!!.toID(),
414+
originalPlaylistPosition
415+
)
406416

407417
val shortTitle = TextUtils.limitTextToLength(video.title.orEmpty(), 50)
408418
val snackBarText = getString(
@@ -462,7 +472,11 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
462472
*
463473
* I.e., this method adds the given offset to all videos with an originalPlaylistIndex > modifiedPosition.
464474
*/
465-
private fun fixItemIndices(items: List<PlaylistItem>, modifiedPosition: Int, offset: Int): List<PlaylistItem> {
475+
private fun fixItemIndices(
476+
items: List<PlaylistItem>,
477+
modifiedPosition: Int,
478+
offset: Int
479+
): List<PlaylistItem> {
466480
return items.map {
467481
if (it.originalPlaylistIndex > modifiedPosition) {
468482
it.copy(originalPlaylistIndex = it.originalPlaylistIndex + offset)

app/src/main/java/com/github/libretube/ui/models/HomeViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import com.github.libretube.api.obj.Playlists
1212
import com.github.libretube.api.obj.StreamItem
1313
import com.github.libretube.constants.PreferenceKeys
1414
import com.github.libretube.db.DatabaseHelper
15-
import com.github.libretube.db.DatabaseHolder
1615
import com.github.libretube.db.obj.PlaylistBookmark
1716
import com.github.libretube.extensions.runSafely
1817
import com.github.libretube.extensions.updateIfChanged
1918
import com.github.libretube.helpers.PlayerHelper
2019
import com.github.libretube.helpers.PreferenceHelper
20+
import com.github.libretube.repo.UserDataRepositoryHelper
2121
import kotlinx.coroutines.Dispatchers
2222
import kotlinx.coroutines.Job
2323
import kotlinx.coroutines.async
@@ -105,7 +105,7 @@ class HomeViewModel : ViewModel() {
105105
private suspend fun loadBookmarks() {
106106
runSafely(
107107
onSuccess = { newBookmarks -> bookmarks.updateIfChanged(newBookmarks) },
108-
ioBlock = { DatabaseHolder.Database.playlistBookmarkDao().getAll() }
108+
ioBlock = { UserDataRepositoryHelper.userDataRepository.getPlaylistBookmarks() }
109109
)
110110
}
111111

0 commit comments

Comments
 (0)