diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java new file mode 100644 index 000000000..0fcb4ced4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java @@ -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; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java index 2036dc205..f173ab0bc 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java @@ -6,6 +6,7 @@ import androidx.room.RewriteQueriesToDropUnusedColumns; 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; @@ -14,6 +15,7 @@ import java.util.List; 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; @@ -26,6 +28,7 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PL 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; @@ -93,4 +96,24 @@ public interface PlaylistStreamDAO extends BasicDAO { + " GROUP BY " + PLAYLIST_ID + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC") Flowable> 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> getPlaylistDuplicatesMetadata(String streamUrl); } diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index 88dec3911..5aeca06ed 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -13,7 +14,7 @@ import androidx.recyclerview.widget.RecyclerView; 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; @@ -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(); @@ -63,8 +65,9 @@ public final class PlaylistAppendDialog extends PlaylistDialog { playlistAdapter = new LocalItemListAdapter(getActivity()); playlistAdapter.setSelectedListener(selectedItem -> { final List entities = getStreamEntities(); - if (selectedItem instanceof PlaylistMetadataEntry && entities != null) { - onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, entities); + if (selectedItem instanceof PlaylistDuplicatesEntry && entities != null) { + onPlaylistSelected(playlistManager, + (PlaylistDuplicatesEntry) selectedItem, entities); } }); @@ -72,10 +75,13 @@ public final class PlaylistAppendDialog extends PlaylistDialog { 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)); } @@ -117,19 +123,36 @@ public final class PlaylistAppendDialog extends PlaylistDialog { requireDialog().dismiss(); } - private void onPlaylistsReceived(@NonNull final List playlists) { - if (playlistAdapter != null && playlistRecyclerView != null) { + private void onPlaylistsReceived(@NonNull final List 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 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 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)) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index f8c5176ec..240ca0462 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -4,6 +4,7 @@ import android.view.View; 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; @@ -13,6 +14,9 @@ import org.schabi.newpipe.util.Localization; 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); } @@ -38,6 +42,13 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder { 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); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java index 4007d0e09..8ea64f343 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; 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; @@ -87,6 +88,18 @@ public class LocalPlaylistManager { 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> getPlaylistDuplicates(final String streamUrl) { + return playlistStreamTable.getPlaylistDuplicatesMetadata(streamUrl) + .subscribeOn(Schedulers.io()); + } + public Flowable> getPlaylistStreams(final long playlistId) { return playlistStreamTable.getOrderedStreamsOf(playlistId).subscribeOn(Schedulers.io()); } diff --git a/app/src/main/res/layout/dialog_playlists.xml b/app/src/main/res/layout/dialog_playlists.xml index 18b08d93c..ab4691fa9 100644 --- a/app/src/main/res/layout/dialog_playlists.xml +++ b/app/src/main/res/layout/dialog_playlists.xml @@ -34,11 +34,26 @@ tools:ignore="RtlHardcoded" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5a4ce92f2..661f05c8c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -433,6 +433,7 @@ "Loading requested content" New Playlist + The playlists that are grayed out already contain this item. Rename Name Add to playlist @@ -446,6 +447,7 @@ Delete this playlist\? Playlist created Playlisted + Duplicate added %d time(s) Playlist thumbnail changed. Auto-generated (no uploader found)