Merge remote-tracking branch 'origin/1473_remove_duplicates_from_playlist' into 1473_remove_duplicates_from_playlist
This commit is contained in:
commit
265de55a07
308 changed files with 5310 additions and 1395 deletions
|
|
@ -22,10 +22,11 @@ import org.schabi.newpipe.R;
|
|||
import org.schabi.newpipe.databinding.PignateFooterBinding;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.list.ListViewContract;
|
||||
import org.schabi.newpipe.info_list.ItemViewMode;
|
||||
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
|
||||
import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode;
|
||||
|
||||
/**
|
||||
* This fragment is design to be used with persistent data such as
|
||||
|
|
@ -77,16 +78,23 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||
super.onResume();
|
||||
if (updateFlags != 0) {
|
||||
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
||||
final boolean useGrid = shouldUseGridLayout(requireContext());
|
||||
itemsList.setLayoutManager(
|
||||
useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
itemListAdapter.setUseGridVariant(useGrid);
|
||||
itemListAdapter.notifyDataSetChanged();
|
||||
refreshItemViewMode();
|
||||
}
|
||||
updateFlags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the item view mode based on user preference.
|
||||
*/
|
||||
private void refreshItemViewMode() {
|
||||
final ItemViewMode itemViewMode = getItemViewMode(requireContext());
|
||||
itemsList.setLayoutManager((itemViewMode == ItemViewMode.GRID)
|
||||
? getGridLayoutManager() : getListLayoutManager());
|
||||
itemListAdapter.setItemViewMode(itemViewMode);
|
||||
itemListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle - View
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -120,11 +128,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||
|
||||
itemListAdapter = new LocalItemListAdapter(activity);
|
||||
|
||||
final boolean useGrid = shouldUseGridLayout(requireContext());
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
refreshItemViewMode();
|
||||
|
||||
itemListAdapter.setUseGridVariant(useGrid);
|
||||
headerRootBinding = getListHeader();
|
||||
if (headerRootBinding != null) {
|
||||
itemListAdapter.setHeader(headerRootBinding.getRoot());
|
||||
|
|
@ -255,7 +261,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||
final String key) {
|
||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||
if (getString(R.string.list_view_mode_key).equals(key)) {
|
||||
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,19 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.info_list.ItemViewMode;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.local.holder.LocalItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistCardItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistStreamCardItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalStatisticStreamCardItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
|
||||
import org.schabi.newpipe.local.holder.RemotePlaylistCardItemHolder;
|
||||
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
|
||||
import org.schabi.newpipe.util.FallbackViewHolder;
|
||||
|
|
@ -61,11 +66,17 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000;
|
||||
private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001;
|
||||
private static final int STREAM_STATISTICS_GRID_HOLDER_TYPE = 0x1002;
|
||||
private static final int STREAM_STATISTICS_CARD_HOLDER_TYPE = 0x1003;
|
||||
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
|
||||
private static final int STREAM_PLAYLIST_CARD_HOLDER_TYPE = 0x1005;
|
||||
|
||||
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
|
||||
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
|
||||
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
|
||||
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
|
||||
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2001;
|
||||
private static final int LOCAL_PLAYLIST_CARD_HOLDER_TYPE = 0x2002;
|
||||
|
||||
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x3000;
|
||||
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x3001;
|
||||
private static final int REMOTE_PLAYLIST_CARD_HOLDER_TYPE = 0x3002;
|
||||
|
||||
private final LocalItemBuilder localItemBuilder;
|
||||
private final ArrayList<LocalItem> localItems;
|
||||
|
|
@ -73,9 +84,9 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
private boolean showFooter = false;
|
||||
private boolean useGridVariant = false;
|
||||
private View header = null;
|
||||
private View footer = null;
|
||||
private ItemViewMode itemViewMode = ItemViewMode.LIST;
|
||||
|
||||
public LocalItemListAdapter(final Context context) {
|
||||
recordManager = new HistoryRecordManager(context);
|
||||
|
|
@ -165,8 +176,8 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setUseGridVariant(final boolean useGridVariant) {
|
||||
this.useGridVariant = useGridVariant;
|
||||
public void setItemViewMode(final ItemViewMode itemViewMode) {
|
||||
this.itemViewMode = itemViewMode;
|
||||
}
|
||||
|
||||
public void setHeader(final View header) {
|
||||
|
|
@ -244,21 +255,39 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
return FOOTER_TYPE;
|
||||
}
|
||||
final LocalItem item = localItems.get(position);
|
||||
|
||||
switch (item.getLocalItemType()) {
|
||||
case PLAYLIST_LOCAL_ITEM:
|
||||
return useGridVariant
|
||||
? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
|
||||
if (itemViewMode == ItemViewMode.CARD) {
|
||||
return LOCAL_PLAYLIST_CARD_HOLDER_TYPE;
|
||||
} else if (itemViewMode == ItemViewMode.GRID) {
|
||||
return LOCAL_PLAYLIST_GRID_HOLDER_TYPE;
|
||||
} else {
|
||||
return LOCAL_PLAYLIST_HOLDER_TYPE;
|
||||
}
|
||||
case PLAYLIST_REMOTE_ITEM:
|
||||
return useGridVariant
|
||||
? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
|
||||
|
||||
if (itemViewMode == ItemViewMode.CARD) {
|
||||
return REMOTE_PLAYLIST_CARD_HOLDER_TYPE;
|
||||
} else if (itemViewMode == ItemViewMode.GRID) {
|
||||
return REMOTE_PLAYLIST_GRID_HOLDER_TYPE;
|
||||
} else {
|
||||
return REMOTE_PLAYLIST_HOLDER_TYPE;
|
||||
}
|
||||
case PLAYLIST_STREAM_ITEM:
|
||||
return useGridVariant
|
||||
? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
|
||||
if (itemViewMode == ItemViewMode.CARD) {
|
||||
return STREAM_PLAYLIST_CARD_HOLDER_TYPE;
|
||||
} else if (itemViewMode == ItemViewMode.GRID) {
|
||||
return STREAM_PLAYLIST_GRID_HOLDER_TYPE;
|
||||
} else {
|
||||
return STREAM_PLAYLIST_HOLDER_TYPE;
|
||||
}
|
||||
case STATISTIC_STREAM_ITEM:
|
||||
return useGridVariant
|
||||
? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
|
||||
if (itemViewMode == ItemViewMode.CARD) {
|
||||
return STREAM_STATISTICS_CARD_HOLDER_TYPE;
|
||||
} else if (itemViewMode == ItemViewMode.GRID) {
|
||||
return STREAM_STATISTICS_GRID_HOLDER_TYPE;
|
||||
} else {
|
||||
return STREAM_STATISTICS_HOLDER_TYPE;
|
||||
}
|
||||
default:
|
||||
Log.e(TAG, "No holder type has been considered for item: ["
|
||||
+ item.getLocalItemType() + "]");
|
||||
|
|
@ -283,18 +312,26 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
return new LocalPlaylistItemHolder(localItemBuilder, parent);
|
||||
case LOCAL_PLAYLIST_GRID_HOLDER_TYPE:
|
||||
return new LocalPlaylistGridItemHolder(localItemBuilder, parent);
|
||||
case LOCAL_PLAYLIST_CARD_HOLDER_TYPE:
|
||||
return new LocalPlaylistCardItemHolder(localItemBuilder, parent);
|
||||
case REMOTE_PLAYLIST_HOLDER_TYPE:
|
||||
return new RemotePlaylistItemHolder(localItemBuilder, parent);
|
||||
case REMOTE_PLAYLIST_GRID_HOLDER_TYPE:
|
||||
return new RemotePlaylistGridItemHolder(localItemBuilder, parent);
|
||||
case REMOTE_PLAYLIST_CARD_HOLDER_TYPE:
|
||||
return new RemotePlaylistCardItemHolder(localItemBuilder, parent);
|
||||
case STREAM_PLAYLIST_HOLDER_TYPE:
|
||||
return new LocalPlaylistStreamItemHolder(localItemBuilder, parent);
|
||||
case STREAM_PLAYLIST_GRID_HOLDER_TYPE:
|
||||
return new LocalPlaylistStreamGridItemHolder(localItemBuilder, parent);
|
||||
case STREAM_PLAYLIST_CARD_HOLDER_TYPE:
|
||||
return new LocalPlaylistStreamCardItemHolder(localItemBuilder, parent);
|
||||
case STREAM_STATISTICS_HOLDER_TYPE:
|
||||
return new LocalStatisticStreamItemHolder(localItemBuilder, parent);
|
||||
case STREAM_STATISTICS_GRID_HOLDER_TYPE:
|
||||
return new LocalStatisticStreamGridItemHolder(localItemBuilder, parent);
|
||||
case STREAM_STATISTICS_CARD_HOLDER_TYPE:
|
||||
return new LocalStatisticStreamCardItemHolder(localItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "No view type has been considered for holder: [" + type + "]");
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.schabi.newpipe.local.bookmark;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.InputType;
|
||||
|
|
@ -31,6 +32,7 @@ import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
|||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
|
|
@ -256,6 +258,41 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
}
|
||||
|
||||
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
|
||||
final String rename = getString(R.string.rename);
|
||||
final String delete = getString(R.string.delete);
|
||||
final String unsetThumbnail = getString(R.string.unset_playlist_thumbnail);
|
||||
final boolean isThumbnailPermanent = localPlaylistManager
|
||||
.getIsPlaylistThumbnailPermanent(selectedItem.uid);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
|
||||
final ArrayList<String> items = new ArrayList<>();
|
||||
items.add(rename);
|
||||
items.add(delete);
|
||||
if (isThumbnailPermanent) {
|
||||
items.add(unsetThumbnail);
|
||||
}
|
||||
|
||||
final DialogInterface.OnClickListener action = (d, index) -> {
|
||||
if (items.get(index).equals(rename)) {
|
||||
showRenameDialog(selectedItem);
|
||||
} else if (items.get(index).equals(delete)) {
|
||||
showDeleteDialog(selectedItem.name,
|
||||
localPlaylistManager.deletePlaylist(selectedItem.uid));
|
||||
} else if (isThumbnailPermanent && items.get(index).equals(unsetThumbnail)) {
|
||||
final String thumbnailUrl = localPlaylistManager
|
||||
.getAutomaticPlaylistThumbnail(selectedItem.uid);
|
||||
localPlaylistManager
|
||||
.changePlaylistThumbnail(selectedItem.uid, thumbnailUrl, false)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
}
|
||||
};
|
||||
|
||||
builder.setItems(items.toArray(new String[0]), action).create().show();
|
||||
}
|
||||
|
||||
private void showRenameDialog(final PlaylistMetadataEntry selectedItem) {
|
||||
final DialogEditTextBinding dialogBinding =
|
||||
DialogEditTextBinding.inflate(getLayoutInflater());
|
||||
dialogBinding.dialogEditText.setHint(R.string.name);
|
||||
|
|
@ -269,11 +306,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
selectedItem.uid,
|
||||
dialogBinding.dialogEditText.getText().toString()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
||||
showDeleteDialog(selectedItem.name,
|
||||
localPlaylistManager.deletePlaylist(selectedItem.uid));
|
||||
dialog.dismiss();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -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,24 +123,41 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||
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)) {
|
||||
playlistDisposables.add(manager
|
||||
.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl())
|
||||
.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl(), false)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> successToast.show()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.edit
|
||||
|
|
@ -69,6 +68,7 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment
|
||||
import org.schabi.newpipe.info_list.ItemViewMode
|
||||
import org.schabi.newpipe.info_list.dialog.InfoItemDialog
|
||||
import org.schabi.newpipe.ktx.animate
|
||||
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
|
||||
|
|
@ -80,6 +80,7 @@ import org.schabi.newpipe.util.DeviceUtils
|
|||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
|
||||
import org.schabi.newpipe.util.ThemeHelper.getItemViewMode
|
||||
import org.schabi.newpipe.util.ThemeHelper.resolveDrawable
|
||||
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
|
||||
import java.time.OffsetDateTime
|
||||
|
|
@ -120,7 +121,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
|
||||
|
||||
onSettingsChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||
if (getString(R.string.list_view_mode_key).equals(key)) {
|
||||
updateListViewModeOnResume = true
|
||||
}
|
||||
}
|
||||
|
|
@ -416,11 +417,10 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
|
||||
@SuppressLint("StringFormatMatches")
|
||||
private fun handleLoadedState(loadedState: FeedState.LoadedState) {
|
||||
|
||||
val itemVersion = if (shouldUseGridLayout(context)) {
|
||||
StreamItem.ItemVersion.GRID
|
||||
} else {
|
||||
StreamItem.ItemVersion.NORMAL
|
||||
val itemVersion = when (getItemViewMode(requireContext())) {
|
||||
ItemViewMode.GRID -> StreamItem.ItemVersion.GRID
|
||||
ItemViewMode.CARD -> StreamItem.ItemVersion.CARD
|
||||
else -> StreamItem.ItemVersion.NORMAL
|
||||
}
|
||||
loadedState.items.forEach { it.itemVersion = itemVersion }
|
||||
|
||||
|
|
@ -499,7 +499,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
|
||||
private fun handleFeedNotAvailable(
|
||||
subscriptionEntity: SubscriptionEntity,
|
||||
@Nullable cause: Throwable?,
|
||||
cause: Throwable?,
|
||||
nextItemsErrors: List<Throwable>
|
||||
) {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
|
|
|||
|
|
@ -42,12 +42,13 @@ data class StreamItem(
|
|||
|
||||
override fun getId(): Long = stream.uid
|
||||
|
||||
enum class ItemVersion { NORMAL, MINI, GRID }
|
||||
enum class ItemVersion { NORMAL, MINI, GRID, CARD }
|
||||
|
||||
override fun getLayout(): Int = when (itemVersion) {
|
||||
ItemVersion.NORMAL -> R.layout.list_stream_item
|
||||
ItemVersion.MINI -> R.layout.list_stream_mini_item
|
||||
ItemVersion.GRID -> R.layout.list_stream_grid_item
|
||||
ItemVersion.CARD -> R.layout.list_stream_card_item
|
||||
}
|
||||
|
||||
override fun initializeViewBinding(view: View) = ListStreamItemBinding.bind(view)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
/**
|
||||
* Playlist card layout.
|
||||
*/
|
||||
public class LocalPlaylistCardItemHolder extends LocalPlaylistItemHolder {
|
||||
|
||||
public LocalPlaylistCardItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||
final ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_playlist_card_item, parent);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
/**
|
||||
* Local playlist stream UI. This also includes a handle to rearrange the videos.
|
||||
*/
|
||||
public class LocalPlaylistStreamCardItemHolder extends LocalPlaylistStreamItemHolder {
|
||||
|
||||
public LocalPlaylistStreamCardItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||
final ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_playlist_card_item, parent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
public class LocalStatisticStreamCardItemHolder extends LocalStatisticStreamItemHolder {
|
||||
public LocalStatisticStreamCardItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||
final ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_card_item, parent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
/**
|
||||
* Playlist card UI for list item.
|
||||
*/
|
||||
public class RemotePlaylistCardItemHolder extends RemotePlaylistItemHolder {
|
||||
|
||||
public RemotePlaylistCardItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||
final ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_playlist_card_item, parent);
|
||||
}
|
||||
}
|
||||
|
|
@ -408,7 +408,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
.firstElement()
|
||||
.zipWith(historyIdsMaybe, (playlist, historyStreamIds) -> {
|
||||
// Remove Watched, Functionality data
|
||||
final List<PlaylistStreamEntry> notWatchedItems = new ArrayList<>();
|
||||
final List<PlaylistStreamEntry> itemsToKeep = new ArrayList<>();
|
||||
final boolean isThumbnailPermanent = playlistManager
|
||||
.getIsPlaylistThumbnailPermanent(playlistId);
|
||||
boolean thumbnailVideoRemoved = false;
|
||||
|
||||
if (removePartiallyWatched) {
|
||||
|
|
@ -417,8 +419,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
playlistItem.getStreamId());
|
||||
|
||||
if (indexInHistory < 0) {
|
||||
notWatchedItems.add(playlistItem);
|
||||
} else if (!thumbnailVideoRemoved
|
||||
itemsToKeep.add(playlistItem);
|
||||
} else if (!isThumbnailPermanent && !thumbnailVideoRemoved
|
||||
&& playlistManager.getPlaylistThumbnail(playlistId)
|
||||
.equals(playlistItem.getStreamEntity().getThumbnailUrl())) {
|
||||
thumbnailVideoRemoved = true;
|
||||
|
|
@ -438,8 +440,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
|
||||
if (indexInHistory < 0 || (streamStateEntity != null
|
||||
&& !streamStateEntity.isFinished(duration))) {
|
||||
notWatchedItems.add(playlistItem);
|
||||
} else if (!thumbnailVideoRemoved
|
||||
itemsToKeep.add(playlistItem);
|
||||
} else if (!isThumbnailPermanent && !thumbnailVideoRemoved
|
||||
&& playlistManager.getPlaylistThumbnail(playlistId)
|
||||
.equals(playlistItem.getStreamEntity().getThumbnailUrl())) {
|
||||
thumbnailVideoRemoved = true;
|
||||
|
|
@ -447,17 +449,17 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
}
|
||||
|
||||
return new Pair<>(notWatchedItems, thumbnailVideoRemoved);
|
||||
return new Pair<>(itemsToKeep, thumbnailVideoRemoved);
|
||||
});
|
||||
|
||||
disposables.add(streamsMaybe.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(flow -> {
|
||||
final List<PlaylistStreamEntry> notWatchedItems = flow.first;
|
||||
final List<PlaylistStreamEntry> itemsToKeep = flow.first;
|
||||
final boolean thumbnailVideoRemoved = flow.second;
|
||||
|
||||
itemListAdapter.clearStreamItemList();
|
||||
itemListAdapter.addItems(notWatchedItems);
|
||||
itemListAdapter.addItems(itemsToKeep);
|
||||
saveChanges();
|
||||
|
||||
if (thumbnailVideoRemoved) {
|
||||
|
|
@ -589,8 +591,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
disposables.add(disposable);
|
||||
}
|
||||
|
||||
private void changeThumbnailUrl(final String thumbnailUrl) {
|
||||
if (playlistManager == null) {
|
||||
private void changeThumbnailUrl(final String thumbnailUrl, final boolean isPermanent) {
|
||||
if (playlistManager == null || (!isPermanent && playlistManager
|
||||
.getIsPlaylistThumbnailPermanent(playlistId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -604,7 +607,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
final Disposable disposable = playlistManager
|
||||
.changePlaylistThumbnail(playlistId, thumbnailUrl)
|
||||
.changePlaylistThumbnail(playlistId, thumbnailUrl, isPermanent)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignore -> successToast.show(), throwable ->
|
||||
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||
|
|
@ -613,6 +616,10 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
private void updateThumbnailUrl() {
|
||||
if (playlistManager.getIsPlaylistThumbnailPermanent(playlistId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String newThumbnailUrl;
|
||||
|
||||
if (!itemListAdapter.getItemsList().isEmpty()) {
|
||||
|
|
@ -622,7 +629,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
newThumbnailUrl = "drawable://" + R.drawable.placeholder_thumbnail_playlist;
|
||||
}
|
||||
|
||||
changeThumbnailUrl(newThumbnailUrl);
|
||||
changeThumbnailUrl(newThumbnailUrl, false);
|
||||
}
|
||||
|
||||
private void openRemoveDuplicatesDialog() {
|
||||
|
|
@ -828,7 +835,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
.setAction(
|
||||
StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
|
||||
(f, i) ->
|
||||
changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl()))
|
||||
changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl(),
|
||||
true))
|
||||
.setAction(
|
||||
StreamDialogDefaultEntry.DELETE,
|
||||
(f, i) -> deleteItem(item))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package org.schabi.newpipe.local.playlist;
|
|||
|
||||
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;
|
||||
|
|
@ -41,7 +43,7 @@ public class LocalPlaylistManager {
|
|||
}
|
||||
final StreamEntity defaultStream = streams.get(0);
|
||||
final PlaylistEntity newPlaylist =
|
||||
new PlaylistEntity(name, defaultStream.getThumbnailUrl());
|
||||
new PlaylistEntity(name, defaultStream.getThumbnailUrl(), false);
|
||||
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() ->
|
||||
upsertStreams(playlistTable.insert(newPlaylist), streams, 0))
|
||||
|
|
@ -91,6 +93,18 @@ public class LocalPlaylistManager {
|
|||
.getStreamsWithoutDuplicates(playlistId).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());
|
||||
}
|
||||
|
|
@ -101,21 +115,33 @@ public class LocalPlaylistManager {
|
|||
}
|
||||
|
||||
public Maybe<Integer> renamePlaylist(final long playlistId, final String name) {
|
||||
return modifyPlaylist(playlistId, name, null);
|
||||
return modifyPlaylist(playlistId, name, null, false);
|
||||
}
|
||||
|
||||
public Maybe<Integer> changePlaylistThumbnail(final long playlistId,
|
||||
final String thumbnailUrl) {
|
||||
return modifyPlaylist(playlistId, null, thumbnailUrl);
|
||||
final String thumbnailUrl,
|
||||
final boolean isPermanent) {
|
||||
return modifyPlaylist(playlistId, null, thumbnailUrl, isPermanent);
|
||||
}
|
||||
|
||||
public String getPlaylistThumbnail(final long playlistId) {
|
||||
return playlistTable.getPlaylist(playlistId).blockingFirst().get(0).getThumbnailUrl();
|
||||
}
|
||||
|
||||
public boolean getIsPlaylistThumbnailPermanent(final long playlistId) {
|
||||
return playlistTable.getPlaylist(playlistId).blockingFirst().get(0)
|
||||
.getIsThumbnailPermanent();
|
||||
}
|
||||
|
||||
public String getAutomaticPlaylistThumbnail(final long playlistId) {
|
||||
final String def = "drawable://" + R.drawable.placeholder_thumbnail_playlist;
|
||||
return playlistStreamTable.getAutomaticThumbnailUrl(playlistId, def).blockingFirst();
|
||||
}
|
||||
|
||||
private Maybe<Integer> modifyPlaylist(final long playlistId,
|
||||
@Nullable final String name,
|
||||
@Nullable final String thumbnailUrl) {
|
||||
@Nullable final String thumbnailUrl,
|
||||
final boolean isPermanent) {
|
||||
return playlistTable.getPlaylist(playlistId)
|
||||
.firstElement()
|
||||
.filter(playlistEntities -> !playlistEntities.isEmpty())
|
||||
|
|
@ -126,6 +152,7 @@ public class LocalPlaylistManager {
|
|||
}
|
||||
if (thumbnailUrl != null) {
|
||||
playlist.setThumbnailUrl(thumbnailUrl);
|
||||
playlist.setIsThumbnailPermanent(isPermanent);
|
||||
}
|
||||
return playlistTable.update(playlist);
|
||||
}).subscribeOn(Schedulers.io());
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ enum class FeedGroupIcon(
|
|||
WORLD(34, R.drawable.ic_public),
|
||||
STAR(35, R.drawable.ic_stars),
|
||||
SUN(36, R.drawable.ic_wb_sunny),
|
||||
RSS(37, R.drawable.ic_rss_feed);
|
||||
RSS(37, R.drawable.ic_rss_feed),
|
||||
WHATS_NEW(38, R.drawable.ic_subscriptions);
|
||||
|
||||
@DrawableRes
|
||||
fun getDrawableRes(): Int {
|
||||
|
|
|
|||
|
|
@ -433,10 +433,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
clear()
|
||||
if (listViewMode) {
|
||||
add(FeedGroupAddNewItem())
|
||||
add(FeedGroupCardItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.RSS))
|
||||
add(FeedGroupCardItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.WHATS_NEW))
|
||||
} else {
|
||||
add(FeedGroupAddNewGridItem())
|
||||
add(FeedGroupCardGridItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.RSS))
|
||||
add(FeedGroupCardGridItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.WHATS_NEW))
|
||||
}
|
||||
addAll(groups)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue