Skip to content

Commit 86381f2

Browse files
Implement remaining playlist functionality
1 parent 14a0e5d commit 86381f2

21 files changed

Lines changed: 478 additions & 284 deletions

File tree

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ dependencies {
226226
implementation libs.androidx.media
227227
implementation libs.androidx.preference
228228
implementation libs.androidx.recyclerview
229-
implementation libs.androidx.room.runtime
229+
implementation libs.androidx.room.ktx
230230
implementation libs.androidx.room.rxjava3
231231
kapt libs.androidx.room.compiler
232232
implementation libs.androidx.swiperefreshlayout

app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import androidx.room.testing.MigrationTestHelper
77
import androidx.test.core.app.ApplicationProvider
88
import androidx.test.ext.junit.runners.AndroidJUnit4
99
import androidx.test.platform.app.InstrumentationRegistry
10+
import kotlinx.coroutines.reactive.awaitFirst
11+
import kotlinx.coroutines.runBlocking
1012
import org.junit.Assert.assertEquals
1113
import org.junit.Assert.assertNotEquals
1214
import org.junit.Assert.assertNull
@@ -226,7 +228,7 @@ class DatabaseMigrationTest {
226228
}
227229

228230
@Test
229-
fun migrateDatabaseFrom8to9() {
231+
fun migrateDatabaseFrom8to9() = runBlocking {
230232
val databaseInV8 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_8)
231233

232234
val localUid1: Long
@@ -283,8 +285,8 @@ class DatabaseMigrationTest {
283285
)
284286

285287
val migratedDatabaseV9 = getMigratedDatabase()
286-
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
287-
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
288+
var localListFromDB = migratedDatabaseV9.playlistDAO().getAll().awaitFirst()
289+
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().awaitFirst()
288290

289291
assertEquals(1, localListFromDB.size)
290292
assertEquals(localUid2, localListFromDB[0].uid)
@@ -303,8 +305,8 @@ class DatabaseMigrationTest {
303305
)
304306
)
305307

306-
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
307-
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
308+
localListFromDB = migratedDatabaseV9.playlistDAO().getAll().awaitFirst()
309+
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().awaitFirst()
308310
assertEquals(2, localListFromDB.size)
309311
assertEquals(localUid3, localListFromDB[1].uid)
310312
assertEquals(-1, localListFromDB[1].displayIndex)

app/src/main/java/org/schabi/newpipe/MainActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ private void handleIntent(final Intent intent) {
776776
break;
777777
case PLAYLIST:
778778
NavigationHelper.openPlaylistFragment(getSupportFragmentManager(),
779-
serviceId, url);
779+
serviceId, url, title);
780780
break;
781781
}
782782
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,44 @@
1-
package org.schabi.newpipe.database.playlist.dao;
2-
3-
import androidx.room.Dao;
4-
import androidx.room.Query;
5-
import androidx.room.Transaction;
6-
7-
import org.schabi.newpipe.database.BasicDAO;
8-
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
9-
10-
import java.util.List;
11-
12-
import io.reactivex.rxjava3.core.Flowable;
13-
14-
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_DISPLAY_INDEX;
15-
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
16-
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;
17-
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE;
18-
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
1+
package org.schabi.newpipe.database.playlist.dao
2+
3+
import androidx.room.Dao
4+
import androidx.room.Insert
5+
import androidx.room.Query
6+
import androidx.room.Transaction
7+
import androidx.room.Update
8+
import io.reactivex.rxjava3.core.Flowable
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.firstOrNull
11+
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
1912

2013
@Dao
21-
public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
22-
@Override
23-
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE)
24-
Flowable<List<PlaylistRemoteEntity>> getAll();
25-
26-
@Override
27-
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE)
28-
int deleteAll();
29-
30-
@Override
31-
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
32-
+ " WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
33-
Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
14+
interface PlaylistRemoteDAO {
15+
@Query("SELECT * FROM remote_playlists")
16+
fun getAll(): Flowable<List<PlaylistRemoteEntity>>
3417

35-
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
36-
+ REMOTE_PLAYLIST_ID + " = :playlistId")
37-
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);
18+
@Query("SELECT * FROM remote_playlists WHERE url = :url AND service_id = :serviceId")
19+
fun getPlaylist(serviceId: Int, url: String): Flow<PlaylistRemoteEntity?>
3820

39-
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
40-
+ REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
41-
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
21+
@Query("SELECT * FROM remote_playlists ORDER BY display_index")
22+
fun getPlaylists(): Flowable<List<PlaylistRemoteEntity>>
4223

43-
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
44-
+ " ORDER BY " + REMOTE_PLAYLIST_DISPLAY_INDEX)
45-
Flowable<List<PlaylistRemoteEntity>> getPlaylists();
24+
@Insert
25+
suspend fun insert(playlist: PlaylistRemoteEntity): Long
4626

47-
@Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE
48-
+ " WHERE " + REMOTE_PLAYLIST_URL + " = :url "
49-
+ "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
50-
Long getPlaylistIdInternal(long serviceId, String url);
27+
@Update
28+
suspend fun update(playlist: PlaylistRemoteEntity)
5129

5230
@Transaction
53-
default long upsert(final PlaylistRemoteEntity playlist) {
54-
final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl());
31+
suspend fun upsert(playlist: PlaylistRemoteEntity) {
32+
val dbPlaylist = getPlaylist(playlist.serviceId, playlist.url).firstOrNull()
5533

56-
if (playlistId == null) {
57-
return insert(playlist);
34+
if (dbPlaylist == null) {
35+
insert(playlist)
5836
} else {
59-
playlist.setUid(playlistId);
60-
update(playlist);
61-
return playlistId;
37+
playlist.uid = dbPlaylist.uid
38+
update(playlist)
6239
}
6340
}
6441

65-
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE
66-
+ " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId")
67-
int deletePlaylist(long playlistId);
42+
@Query("DELETE FROM remote_playlists WHERE uid = :playlistId")
43+
suspend fun deletePlaylist(playlistId: Long): Int
6844
}

app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
package org.schabi.newpipe.database.playlist.model;
22

3-
import android.text.TextUtils;
4-
53
import androidx.room.ColumnInfo;
64
import androidx.room.Entity;
75
import androidx.room.Ignore;
86
import androidx.room.Index;
97
import androidx.room.PrimaryKey;
108

119
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
12-
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
10+
import org.schabi.newpipe.ui.components.playlist.PlaylistScreenInfo;
1311
import org.schabi.newpipe.util.Constants;
1412
import org.schabi.newpipe.util.image.ImageStrategy;
1513

1614
import static org.schabi.newpipe.database.LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM;
17-
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME;
1815
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;
1916
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE;
2017
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
@@ -84,31 +81,14 @@ public PlaylistRemoteEntity(final int serviceId, final String name, final String
8481
}
8582

8683
@Ignore
87-
public PlaylistRemoteEntity(final PlaylistInfo info) {
84+
public PlaylistRemoteEntity(final PlaylistScreenInfo info) {
8885
this(info.getServiceId(), info.getName(), info.getUrl(),
8986
// use uploader avatar when no thumbnail is available
9087
ImageStrategy.imageListToDbUrl(info.getThumbnails().isEmpty()
9188
? info.getUploaderAvatars() : info.getThumbnails()),
9289
info.getUploaderName(), info.getStreamCount());
9390
}
9491

95-
@Ignore
96-
public boolean isIdenticalTo(final PlaylistInfo info) {
97-
/*
98-
* Returns boolean comparing the online playlist and the local copy.
99-
* (False if info changed such as playlist name or track count)
100-
*/
101-
return getServiceId() == info.getServiceId()
102-
&& getStreamCount() == info.getStreamCount()
103-
&& TextUtils.equals(getName(), info.getName())
104-
&& TextUtils.equals(getUrl(), info.getUrl())
105-
// we want to update the local playlist data even when either the remote thumbnail
106-
// URL changes, or the preferred image quality setting is changed by the user
107-
&& TextUtils.equals(getThumbnailUrl(),
108-
ImageStrategy.imageListToDbUrl(info.getThumbnails()))
109-
&& TextUtils.equals(getUploader(), info.getUploaderName());
110-
}
111-
11292
@Override
11393
public long getUid() {
11494
return uid;

app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public void held(final StreamInfoItem selectedItem) {
279279
try {
280280
onItemSelected(selectedItem);
281281
NavigationHelper.openPlaylistFragment(getFM(), selectedItem.getServiceId(),
282-
selectedItem.getUrl());
282+
selectedItem.getUrl(), selectedItem.getName());
283283
} catch (final Exception e) {
284284
ErrorUtil.showUiErrorSnackbar(this, "Opening playlist fragment", e);
285285
}

app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.kt

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,112 @@ package org.schabi.newpipe.fragments.list.playlist
22

33
import android.os.Bundle
44
import android.view.LayoutInflater
5+
import android.view.Menu
6+
import android.view.MenuInflater
7+
import android.view.MenuItem
8+
import android.view.View
59
import android.view.ViewGroup
10+
import androidx.appcompat.app.AppCompatActivity
11+
import androidx.compose.material3.Surface
612
import androidx.core.os.bundleOf
13+
import androidx.core.view.MenuProvider
714
import androidx.fragment.app.Fragment
15+
import androidx.fragment.app.viewModels
816
import androidx.fragment.compose.content
17+
import androidx.lifecycle.Lifecycle
18+
import androidx.lifecycle.lifecycleScope
19+
import androidx.lifecycle.repeatOnLifecycle
20+
import kotlinx.coroutines.flow.collectLatest
21+
import kotlinx.coroutines.launch
22+
import org.schabi.newpipe.R
923
import org.schabi.newpipe.ui.screens.PlaylistScreen
1024
import org.schabi.newpipe.ui.theme.AppTheme
1125
import org.schabi.newpipe.util.KEY_SERVICE_ID
26+
import org.schabi.newpipe.util.KEY_TITLE
1227
import org.schabi.newpipe.util.KEY_URL
28+
import org.schabi.newpipe.util.NavigationHelper
29+
import org.schabi.newpipe.util.external_communication.ShareUtils
30+
import org.schabi.newpipe.viewmodels.PlaylistViewModel
1331

1432
class PlaylistFragment : Fragment() {
33+
private val viewModel by viewModels<PlaylistViewModel>()
34+
1535
override fun onCreateView(
1636
inflater: LayoutInflater,
1737
container: ViewGroup?,
1838
savedInstanceState: Bundle?,
1939
) = content {
2040
AppTheme {
21-
PlaylistScreen()
41+
Surface {
42+
PlaylistScreen(viewModel)
43+
}
2244
}
2345
}
2446

47+
override fun onViewCreated(
48+
view: View,
49+
savedInstanceState: Bundle?,
50+
) {
51+
val activity = requireActivity()
52+
53+
(activity as? AppCompatActivity)?.supportActionBar?.let {
54+
it.setDisplayShowTitleEnabled(true)
55+
it.title = viewModel.playlistTitle
56+
}
57+
58+
activity.addMenuProvider(
59+
object : MenuProvider {
60+
override fun onCreateMenu(
61+
menu: Menu,
62+
menuInflater: MenuInflater,
63+
) {
64+
menuInflater.inflate(R.menu.menu_playlist, menu)
65+
66+
viewLifecycleOwner.lifecycleScope.launch {
67+
repeatOnLifecycle(Lifecycle.State.STARTED) {
68+
viewModel.bookmarkFlow.collectLatest {
69+
val bookmarkButton = menu.findItem(R.id.menu_item_bookmark)
70+
bookmarkButton.setIcon(if (it == null) R.drawable.ic_playlist_add else R.drawable.ic_playlist_add_check)
71+
bookmarkButton.setTitle(if (it == null) R.string.bookmark_playlist else R.string.unbookmark_playlist)
72+
}
73+
}
74+
}
75+
}
76+
77+
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
78+
when (menuItem.itemId) {
79+
R.id.action_settings -> {
80+
NavigationHelper.openSettings(activity)
81+
}
82+
R.id.menu_item_openInBrowser -> {
83+
ShareUtils.openUrlInBrowser(activity, viewModel.url)
84+
}
85+
R.id.menu_item_bookmark -> {
86+
viewModel.toggleBookmark()
87+
}
88+
R.id.menu_item_share -> {
89+
ShareUtils.shareText(activity, viewModel.playlistTitle, viewModel.url)
90+
}
91+
}
92+
return true
93+
}
94+
},
95+
viewLifecycleOwner,
96+
)
97+
}
98+
2599
companion object {
26100
@JvmStatic
27-
fun getInstance(serviceId: Int, url: String?) = PlaylistFragment().apply {
28-
arguments = bundleOf(KEY_SERVICE_ID to serviceId, KEY_URL to url)
101+
fun getInstance(
102+
serviceId: Int,
103+
url: String,
104+
playlistName: String,
105+
) = PlaylistFragment().apply {
106+
arguments = bundleOf(
107+
KEY_SERVICE_ID to serviceId,
108+
KEY_URL to url,
109+
KEY_TITLE to playlistName,
110+
)
29111
}
30112
}
31113
}

app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public void selected(final LocalItem selectedItem) {
147147
entry.name);
148148
} else if (selectedItem instanceof PlaylistRemoteEntity entry) {
149149
NavigationHelper.openPlaylistFragment(fragmentManager, entry.getServiceId(),
150-
entry.getUrl());
150+
entry.getUrl(), entry.getName());
151151
}
152152
}
153153

0 commit comments

Comments
 (0)