Skip to content

Commit 917ae03

Browse files
author
NullPointerException
committed
feat: Handle duplicate streams in the "Add to playlist" dialog
1 parent 959d633 commit 917ae03

7 files changed

Lines changed: 110 additions & 16 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.schabi.newpipe.database.playlist;
2+
3+
import androidx.room.ColumnInfo;
4+
5+
/**
6+
* This class adds a field to {@link PlaylistMetadataEntry} that contains an integer representing
7+
* how many times a specific stream is already contained inside a local playlist. Used to be able
8+
* to grey out playlists which already contain the current stream in the playlist append dialog.
9+
* @see org.schabi.newpipe.local.playlist.LocalPlaylistManager#getPlaylistDuplicates(String)
10+
*/
11+
public class PlaylistDuplicatesEntry extends PlaylistMetadataEntry {
12+
public static final String PLAYLIST_TIMES_STREAM_IS_CONTAINED = "timesStreamIsContained";
13+
@ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED)
14+
public final long timesStreamIsContained;
15+
16+
public PlaylistDuplicatesEntry(final long uid,
17+
final String name,
18+
final String thumbnailUrl,
19+
final long displayIndex,
20+
final long streamCount,
21+
final long timesStreamIsContained) {
22+
super(uid, name, thumbnailUrl,displayIndex, streamCount);
23+
this.timesStreamIsContained = timesStreamIsContained;
24+
}
25+
}

app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import androidx.room.Transaction;
77

88
import org.schabi.newpipe.database.BasicDAO;
9+
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
910
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
1011
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
1112
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
@@ -14,6 +15,7 @@
1415

1516
import io.reactivex.rxjava3.core.Flowable;
1617

18+
import static org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry.PLAYLIST_TIMES_STREAM_IS_CONTAINED;
1719
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
1820
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX;
1921
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
@@ -26,6 +28,8 @@
2628
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
2729
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
2830
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
31+
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_THUMBNAIL_URL;
32+
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
2933
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
3034
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS;
3135
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@@ -87,14 +91,23 @@ default Flowable<List<PlaylistStreamEntity>> listByService(final int serviceId)
8791
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
8892

8993
@Transaction
90-
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
94+
@Query("SELECT " + PLAYLIST_TABLE + "." + PLAYLIST_ID + ", "
95+
+ PLAYLIST_NAME + ", "
96+
+ PLAYLIST_TABLE + "." + PLAYLIST_THUMBNAIL_URL + ", "
9197
+ PLAYLIST_DISPLAY_INDEX + ", "
92-
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
98+
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT + ", "
99+
+ "COALESCE(SUM(" + STREAM_URL + " = :streamUrl), 0) AS "
100+
+ PLAYLIST_TIMES_STREAM_IS_CONTAINED
93101

94102
+ " FROM " + PLAYLIST_TABLE
95103
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
96-
+ " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
97-
+ " GROUP BY " + PLAYLIST_ID
104+
+ " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
105+
106+
+ " LEFT JOIN " + STREAM_TABLE
107+
+ " ON " + STREAM_TABLE + "." + STREAM_ID + " = " + JOIN_STREAM_ID
108+
+ " AND :streamUrl = :streamUrl"
109+
110+
+ " GROUP BY " + JOIN_PLAYLIST_ID
98111
+ " ORDER BY " + PLAYLIST_DISPLAY_INDEX)
99-
Flowable<List<PlaylistMetadataEntry>> getDisplayIndexOrderedPlaylistMetadata();
112+
Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicatesMetadata(String streamUrl);
100113
}

app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.view.LayoutInflater;
55
import android.view.View;
66
import android.view.ViewGroup;
7+
import android.widget.TextView;
78
import android.widget.Toast;
89

910
import androidx.annotation.NonNull;
@@ -15,6 +16,7 @@
1516
import org.schabi.newpipe.R;
1617
import org.schabi.newpipe.database.LocalItem;
1718
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
19+
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
1820
import org.schabi.newpipe.database.stream.model.StreamEntity;
1921
import org.schabi.newpipe.local.LocalItemListAdapter;
2022
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
@@ -30,6 +32,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
3032

3133
private RecyclerView playlistRecyclerView;
3234
private LocalItemListAdapter playlistAdapter;
35+
private TextView playlistDuplicateIndicator;
3336

3437
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
3538

@@ -66,13 +69,13 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved
6669
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
6770
@Override
6871
public void selected(final LocalItem selectedItem) {
69-
if (!(selectedItem instanceof PlaylistMetadataEntry)
72+
if (!(selectedItem instanceof PlaylistDuplicatesEntry)
7073
|| getStreamEntities() == null) {
7174
return;
7275
}
7376
onPlaylistSelected(
7477
playlistManager,
75-
(PlaylistMetadataEntry) selectedItem,
78+
(PlaylistDuplicatesEntry) selectedItem,
7679
getStreamEntities()
7780
);
7881
}
@@ -82,10 +85,13 @@ public void selected(final LocalItem selectedItem) {
8285
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
8386
playlistRecyclerView.setAdapter(playlistAdapter);
8487

88+
playlistDuplicateIndicator = view.findViewById(R.id.playlist_duplicate);
89+
8590
final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
8691
newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog());
8792

88-
playlistDisposables.add(playlistManager.getDisplayIndexOrderedPlaylists()
93+
playlistDisposables.add(playlistManager
94+
.getPlaylistDuplicates(getStreamEntities().get(0).getUrl())
8995
.observeOn(AndroidSchedulers.mainThread())
9096
.subscribe(this::onPlaylistsReceived));
9197
}
@@ -127,23 +133,38 @@ public void openCreatePlaylistDialog() {
127133
requireDialog().dismiss();
128134
}
129135

130-
private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) {
131-
if (playlistAdapter != null && playlistRecyclerView != null) {
136+
private void onPlaylistsReceived(@NonNull final List<PlaylistDuplicatesEntry> playlists) {
137+
if (playlistAdapter != null
138+
&& playlistRecyclerView != null
139+
&& playlistDuplicateIndicator != null) {
132140
playlistAdapter.clearStreamItemList();
133141
playlistAdapter.addItems(playlists);
134142
playlistRecyclerView.setVisibility(View.VISIBLE);
143+
playlistDuplicateIndicator.setVisibility(
144+
anyPlaylistContainsDuplicates(playlists) ? View.VISIBLE : View.GONE);
135145
}
136146
}
137147

148+
private boolean anyPlaylistContainsDuplicates(final List<PlaylistDuplicatesEntry> playlists) {
149+
return playlists.stream()
150+
.anyMatch(playlist -> playlist.timesStreamIsContained > 0);
151+
}
152+
138153
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
139-
@NonNull final PlaylistMetadataEntry playlist,
154+
@NonNull final PlaylistDuplicatesEntry playlist,
140155
@NonNull final List<StreamEntity> streams) {
141156
if (getStreamEntities() == null) {
142157
return;
143158
}
159+
final String toastText;
160+
if (playlist.timesStreamIsContained > 0) {
161+
toastText = getString(R.string.playlist_add_stream_success_duplicate,
162+
playlist.timesStreamIsContained);
163+
} else {
164+
toastText = getString(R.string.playlist_add_stream_success);
165+
}
144166

145-
final Toast successToast = Toast.makeText(getContext(),
146-
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
167+
final Toast successToast = Toast.makeText(getContext(), toastText, Toast.LENGTH_SHORT);
147168

148169
if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) {
149170
playlistDisposables.add(manager

app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.view.ViewGroup;
55

66
import org.schabi.newpipe.database.LocalItem;
7+
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
78
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
89
import org.schabi.newpipe.local.LocalItemBuilder;
910
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -14,6 +15,8 @@
1415

1516
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
1617

18+
private static final float GRAYED_OUT_ALPHA = 0.6f;
19+
1720
public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
1821
super(infoItemBuilder, parent);
1922
}
@@ -39,6 +42,13 @@ public void updateFromItem(final LocalItem localItem,
3942

4043
PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
4144

45+
if (item instanceof PlaylistDuplicatesEntry
46+
&& ((PlaylistDuplicatesEntry) item).timesStreamIsContained > 0) {
47+
itemView.setAlpha(GRAYED_OUT_ALPHA);
48+
} else {
49+
itemView.setAlpha(1.0f);
50+
}
51+
4252
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
4353
}
4454
}

app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import androidx.annotation.Nullable;
44

55
import org.schabi.newpipe.database.AppDatabase;
6+
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
67
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
78
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
89
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
@@ -106,8 +107,15 @@ public Flowable<List<PlaylistMetadataEntry>> getPlaylists() {
106107
return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
107108
}
108109

109-
public Flowable<List<PlaylistMetadataEntry>> getDisplayIndexOrderedPlaylists() {
110-
return playlistStreamTable.getDisplayIndexOrderedPlaylistMetadata()
110+
/**
111+
* Get playlists with attached information about how many times the provided stream is already
112+
* contained in each playlist.
113+
*
114+
* @param streamUrl the stream url for which to check for duplicates
115+
* @return a list of {@link PlaylistDuplicatesEntry}
116+
*/
117+
public Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicates(final String streamUrl) {
118+
return playlistStreamTable.getPlaylistDuplicatesMetadata(streamUrl)
111119
.subscribeOn(Schedulers.io());
112120
}
113121

app/src/main/res/layout/dialog_playlists.xml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,26 @@
3535
tools:ignore="RtlHardcoded" />
3636
</RelativeLayout>
3737

38+
<org.schabi.newpipe.views.NewPipeTextView
39+
android:id="@+id/playlist_duplicate"
40+
android:layout_width="match_parent"
41+
android:layout_height="wrap_content"
42+
android:layout_below="@+id/newPlaylist"
43+
android:layout_marginHorizontal="@dimen/video_item_search_padding"
44+
android:layout_marginBottom="@dimen/video_item_search_padding"
45+
android:gravity="center"
46+
android:text="@string/duplicate_in_playlist"
47+
android:textAppearance="?android:attr/textAppearanceMedium"
48+
android:textSize="13sp"
49+
android:visibility="gone"
50+
tools:text="@tools:sample/lorem[20]"
51+
tools:visibility="visible" />
52+
3853
<View
3954
android:id="@+id/separator"
4055
android:layout_width="match_parent"
4156
android:layout_height="1dp"
42-
android:layout_below="@+id/newPlaylist"
57+
android:layout_below="@+id/playlist_duplicate"
4358
android:layout_marginLeft="@dimen/video_item_search_padding"
4459
android:layout_marginRight="@dimen/video_item_search_padding"
4560
android:background="?attr/separator_color" />

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@
435435
<string name="preferred_player_fetcher_notification_message">"Loading requested content"</string>
436436
<!-- Local Playlist -->
437437
<string name="create_playlist">New Playlist</string>
438+
<string name="duplicate_in_playlist">The playlists that are grayed out already contain this item.</string>
438439
<string name="rename_playlist">Rename</string>
439440
<string name="name">Name</string>
440441
<string name="add_to_playlist">Add to playlist</string>
@@ -447,6 +448,7 @@
447448
<string name="delete_playlist_prompt">Delete this playlist\?</string>
448449
<string name="playlist_creation_success">Playlist created</string>
449450
<string name="playlist_add_stream_success">Playlisted</string>
451+
<string name="playlist_add_stream_success_duplicate">Duplicate added %d time(s)</string>
450452
<string name="playlist_thumbnail_change_success">Playlist thumbnail changed.</string>
451453
<string name="playlist_no_uploader">Auto-generated (no uploader found)</string>
452454
<!-- Players -->

0 commit comments

Comments
 (0)