Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle duplicate streams in the "Add to playlist" dialog #9538

Merged
merged 10 commits into from
Jan 29, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.schabi.newpipe.database.playlist;

import androidx.room.ColumnInfo;

/**
* This class adds a field to {@link PlaylistMetadataEntry} that contains an integer representing
* how many times a specific stream is already contained inside a local playlist. Used to be able
* to grey out playlists which already contain the current stream in the playlist append dialog.
* @see org.schabi.newpipe.local.playlist.LocalPlaylistManager#getPlaylistDuplicates(String)
*/
public class PlaylistDuplicatesEntry extends PlaylistMetadataEntry {
public static final String PLAYLIST_TIMES_STREAM_IS_CONTAINED = "timesStreamIsContained";
@ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED)
public final long timesStreamIsContained;

public PlaylistDuplicatesEntry(final long uid,
final String name,
final String thumbnailUrl,
final long streamCount,
final long timesStreamIsContained) {
super(uid, name, thumbnailUrl, streamCount);
this.timesStreamIsContained = timesStreamIsContained;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import androidx.room.Transaction;

import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
Expand All @@ -14,6 +15,7 @@

import io.reactivex.rxjava3.core.Flowable;

import static org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry.PLAYLIST_TIMES_STREAM_IS_CONTAINED;
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
Expand All @@ -26,6 +28,7 @@
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_THUMBNAIL_URL;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
Expand Down Expand Up @@ -93,4 +96,24 @@ default Flowable<List<PlaylistStreamEntity>> listByService(final int serviceId)
+ " GROUP BY " + PLAYLIST_ID
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();

@Transaction
@Query("SELECT " + PLAYLIST_TABLE + "." + PLAYLIST_ID + ", "
+ PLAYLIST_NAME + ", "
+ PLAYLIST_TABLE + "." + PLAYLIST_THUMBNAIL_URL + ", "
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT + ", "
+ "COALESCE(SUM(" + STREAM_URL + " = :streamUrl), 0) AS "
+ PLAYLIST_TIMES_STREAM_IS_CONTAINED

+ " FROM " + PLAYLIST_TABLE
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
+ " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID

+ " LEFT JOIN " + STREAM_TABLE
+ " ON " + STREAM_TABLE + "." + STREAM_ID + " = " + JOIN_STREAM_ID
+ " AND :streamUrl = :streamUrl"

+ " GROUP BY " + JOIN_PLAYLIST_ID
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicatesMetadata(String streamUrl);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
Expand All @@ -13,7 +14,7 @@

import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.local.LocalItemListAdapter;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
Expand All @@ -28,6 +29,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {

private RecyclerView playlistRecyclerView;
private LocalItemListAdapter playlistAdapter;
private TextView playlistDuplicateIndicator;

private final CompositeDisposable playlistDisposables = new CompositeDisposable();

Expand Down Expand Up @@ -63,19 +65,23 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved
playlistAdapter = new LocalItemListAdapter(getActivity());
playlistAdapter.setSelectedListener(selectedItem -> {
final List<StreamEntity> entities = getStreamEntities();
if (selectedItem instanceof PlaylistMetadataEntry && entities != null) {
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, entities);
if (selectedItem instanceof PlaylistDuplicatesEntry && entities != null) {
onPlaylistSelected(playlistManager,
(PlaylistDuplicatesEntry) selectedItem, entities);
}
});

playlistRecyclerView = view.findViewById(R.id.playlist_list);
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
playlistRecyclerView.setAdapter(playlistAdapter);

playlistDuplicateIndicator = view.findViewById(R.id.playlist_duplicate);

final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog());

playlistDisposables.add(playlistManager.getPlaylists()
playlistDisposables.add(playlistManager
.getPlaylistDuplicates(getStreamEntities().get(0).getUrl())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onPlaylistsReceived));
}
Expand Down Expand Up @@ -117,19 +123,36 @@ public void openCreatePlaylistDialog() {
requireDialog().dismiss();
}

private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) {
if (playlistAdapter != null && playlistRecyclerView != null) {
private void onPlaylistsReceived(@NonNull final List<PlaylistDuplicatesEntry> playlists) {
if (playlistAdapter != null
&& playlistRecyclerView != null
&& playlistDuplicateIndicator != null) {
playlistAdapter.clearStreamItemList();
playlistAdapter.addItems(playlists);
playlistRecyclerView.setVisibility(View.VISIBLE);
playlistDuplicateIndicator.setVisibility(
anyPlaylistContainsDuplicates(playlists) ? View.VISIBLE : View.GONE);
}
}

private boolean anyPlaylistContainsDuplicates(final List<PlaylistDuplicatesEntry> playlists) {
return playlists.stream()
.anyMatch(playlist -> playlist.timesStreamIsContained > 0);
}

private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
@NonNull final PlaylistMetadataEntry playlist,
@NonNull final PlaylistDuplicatesEntry playlist,
@NonNull final List<StreamEntity> streams) {
final Toast successToast = Toast.makeText(getContext(),
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);

final String toastText;
if (playlist.timesStreamIsContained > 0) {
toastText = getString(R.string.playlist_add_stream_success_duplicate,
playlist.timesStreamIsContained);
} else {
toastText = getString(R.string.playlist_add_stream_success);
}

final Toast successToast = Toast.makeText(getContext(), toastText, Toast.LENGTH_SHORT);

if (playlist.thumbnailUrl
.equals("drawable://" + R.drawable.placeholder_thumbnail_playlist)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.view.ViewGroup;

import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
Expand All @@ -13,6 +14,9 @@
import java.time.format.DateTimeFormatter;

public class LocalPlaylistItemHolder extends PlaylistItemHolder {

private static final float GRAYED_OUT_ALPHA = 0.6f;

public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, parent);
}
Expand All @@ -38,6 +42,13 @@ public void updateFromItem(final LocalItem localItem,

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

if (item instanceof PlaylistDuplicatesEntry
&& ((PlaylistDuplicatesEntry) item).timesStreamIsContained > 0) {
itemView.setAlpha(GRAYED_OUT_ALPHA);
} else {
itemView.setAlpha(1.0f);
}

super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.schabi.newpipe.R;
import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
Expand Down Expand Up @@ -87,6 +88,18 @@ public Flowable<List<PlaylistMetadataEntry>> getPlaylists() {
return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
}

/**
* Get playlists with attached information about how many times the provided stream is already
* contained in each playlist.
*
* @param streamUrl the stream url for which to check for duplicates
* @return a list of {@link PlaylistDuplicatesEntry}
*/
public Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicates(final String streamUrl) {
return playlistStreamTable.getPlaylistDuplicatesMetadata(streamUrl)
.subscribeOn(Schedulers.io());
}

public Flowable<List<PlaylistStreamEntry>> getPlaylistStreams(final long playlistId) {
return playlistStreamTable.getOrderedStreamsOf(playlistId).subscribeOn(Schedulers.io());
}
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/res/layout/dialog_playlists.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,26 @@
tools:ignore="RtlHardcoded" />
</RelativeLayout>

<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/playlist_duplicate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/newPlaylist"
android:layout_marginHorizontal="@dimen/video_item_search_padding"
android:layout_marginBottom="@dimen/video_item_search_padding"
android:gravity="center"
android:text="@string/duplicate_in_playlist"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="13sp"
android:visibility="gone"
tools:text="@tools:sample/lorem[20]"
tools:visibility="visible" />

<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/newPlaylist"
android:layout_below="@+id/playlist_duplicate"
android:layout_marginLeft="@dimen/video_item_search_padding"
android:layout_marginRight="@dimen/video_item_search_padding"
android:background="?attr/separator_color" />
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@
<string name="preferred_player_fetcher_notification_message">"Loading requested content"</string>
<!-- Local Playlist -->
<string name="create_playlist">New Playlist</string>
<string name="duplicate_in_playlist">The playlists that are grayed out already contain this item.</string>
Jared234 marked this conversation as resolved.
Show resolved Hide resolved
<string name="rename_playlist">Rename</string>
<string name="name">Name</string>
<string name="add_to_playlist">Add to playlist</string>
Expand All @@ -445,6 +446,7 @@
<string name="delete_playlist_prompt">Delete this playlist\?</string>
<string name="playlist_creation_success">Playlist created</string>
<string name="playlist_add_stream_success">Playlisted</string>
<string name="playlist_add_stream_success_duplicate">Duplicate added %d time(s)</string>
<string name="playlist_thumbnail_change_success">Playlist thumbnail changed.</string>
<string name="playlist_no_uploader">Auto-generated (no uploader found)</string>
<!-- Players -->
Expand Down