-Revamped local items to display more information such as service name, etc.
-Enabled reordering, renaming, removing of items on playlist fragment. -Enabled removal of dangling streams entries when history is cleared. -Changed playlist append menu item to icon on service player activity. -Added adapter and builder for local items, removed dependency on infoitem and existing infolist for database entry items. -Removed watch history entity and DAO. -Extracted info item selected listener to remove adding boilerplate code when long click functionality is optional. -Fixed query returning no record on left join when right table is empty.
This commit is contained in:
parent
17d77aa31f
commit
84c5d27416
38 changed files with 1224 additions and 506 deletions
11
app/src/main/java/org/schabi/newpipe/database/LocalItem.java
Normal file
11
app/src/main/java/org/schabi/newpipe/database/LocalItem.java
Normal file
|
@ -0,0 +1,11 @@
|
|||
package org.schabi.newpipe.database;
|
||||
|
||||
public interface LocalItem {
|
||||
enum LocalItemType {
|
||||
PLAYLIST_ITEM,
|
||||
PLAYLIST_STREAM_ITEM,
|
||||
STATISTIC_STREAM_ITEM
|
||||
}
|
||||
|
||||
LocalItemType getLocalItemType();
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package org.schabi.newpipe.database.history.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
|
||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
|
||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.CREATION_DATE;
|
||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.ID;
|
||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.SERVICE_ID;
|
||||
import static org.schabi.newpipe.database.history.model.WatchHistoryEntry.TABLE_NAME;
|
||||
|
||||
@Dao
|
||||
public interface WatchHistoryDAO extends HistoryDAO<WatchHistoryEntry> {
|
||||
|
||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
||||
@Override
|
||||
WatchHistoryEntry getLatestEntry();
|
||||
|
||||
@Query("DELETE FROM " + TABLE_NAME)
|
||||
@Override
|
||||
int deleteAll();
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE)
|
||||
@Override
|
||||
Flowable<List<WatchHistoryEntry>> getAll();
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
||||
@Override
|
||||
Flowable<List<WatchHistoryEntry>> listByService(int serviceId);
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package org.schabi.newpipe.database.history.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Entity(tableName = WatchHistoryEntry.TABLE_NAME)
|
||||
public class WatchHistoryEntry extends HistoryEntry {
|
||||
|
||||
public static final String TABLE_NAME = "watch_history";
|
||||
public static final String TITLE = "title";
|
||||
public static final String URL = "url";
|
||||
public static final String STREAM_ID = "stream_id";
|
||||
public static final String THUMBNAIL_URL = "thumbnail_url";
|
||||
public static final String UPLOADER = "uploader";
|
||||
public static final String DURATION = "duration";
|
||||
|
||||
@ColumnInfo(name = TITLE)
|
||||
private String title;
|
||||
|
||||
@ColumnInfo(name = URL)
|
||||
private String url;
|
||||
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
private String streamId;
|
||||
|
||||
@ColumnInfo(name = THUMBNAIL_URL)
|
||||
private String thumbnailURL;
|
||||
|
||||
@ColumnInfo(name = UPLOADER)
|
||||
private String uploader;
|
||||
|
||||
@ColumnInfo(name = DURATION)
|
||||
private long duration;
|
||||
|
||||
public WatchHistoryEntry(Date creationDate, int serviceId, String title, String url, String streamId, String thumbnailURL, String uploader, long duration) {
|
||||
super(creationDate, serviceId);
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
this.streamId = streamId;
|
||||
this.thumbnailURL = thumbnailURL;
|
||||
this.uploader = uploader;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public WatchHistoryEntry(StreamInfo streamInfo) {
|
||||
this(new Date(), streamInfo.getServiceId(), streamInfo.getName(), streamInfo.getUrl(),
|
||||
streamInfo.id, streamInfo.thumbnail_url, streamInfo.uploader_name, streamInfo.duration);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getStreamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
public void setStreamId(String streamId) {
|
||||
this.streamId = streamId;
|
||||
}
|
||||
|
||||
public String getThumbnailURL() {
|
||||
return thumbnailURL;
|
||||
}
|
||||
|
||||
public void setThumbnailURL(String thumbnailURL) {
|
||||
this.thumbnailURL = thumbnailURL;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Override
|
||||
public boolean hasEqualValues(HistoryEntry otherEntry) {
|
||||
return otherEntry instanceof WatchHistoryEntry && super.hasEqualValues(otherEntry)
|
||||
&& getUrl().equals(((WatchHistoryEntry) otherEntry).getUrl());
|
||||
}
|
||||
}
|
|
@ -2,13 +2,14 @@ package org.schabi.newpipe.database.playlist;
|
|||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem;
|
||||
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
|
||||
|
||||
public class PlaylistMetadataEntry {
|
||||
public class PlaylistMetadataEntry implements LocalItem {
|
||||
final public static String PLAYLIST_STREAM_COUNT = "streamCount";
|
||||
|
||||
@ColumnInfo(name = PLAYLIST_ID)
|
||||
|
@ -33,4 +34,9 @@ public class PlaylistMetadataEntry {
|
|||
storedPlaylistInfoItem.setStreamCount(streamCount);
|
||||
return storedPlaylistInfoItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalItemType getLocalItemType() {
|
||||
return LocalItemType.PLAYLIST_ITEM;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
public class PlaylistStreamEntry implements LocalItem {
|
||||
@ColumnInfo(name = StreamEntity.STREAM_ID)
|
||||
final public long uid;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_SERVICE_ID)
|
||||
final public int serviceId;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_URL)
|
||||
final public String url;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TITLE)
|
||||
final public String title;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||
final public StreamType streamType;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||
final public long duration;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOADER)
|
||||
final public String uploader;
|
||||
@ColumnInfo(name = StreamEntity.STREAM_THUMBNAIL_URL)
|
||||
final public String thumbnailUrl;
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||
final public long streamId;
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||
final public int joinIndex;
|
||||
|
||||
public PlaylistStreamEntry(long uid, int serviceId, String url, String title,
|
||||
StreamType streamType, long duration, String uploader,
|
||||
String thumbnailUrl, long streamId, int joinIndex) {
|
||||
this.uid = uid;
|
||||
this.serviceId = serviceId;
|
||||
this.url = url;
|
||||
this.title = title;
|
||||
this.streamType = streamType;
|
||||
this.duration = duration;
|
||||
this.uploader = uploader;
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
this.streamId = streamId;
|
||||
this.joinIndex = joinIndex;
|
||||
}
|
||||
|
||||
public StreamInfoItem toStreamInfoItem() throws IllegalArgumentException {
|
||||
StreamInfoItem item = new StreamInfoItem(serviceId, url, title, streamType);
|
||||
item.setThumbnailUrl(thumbnailUrl);
|
||||
item.setUploaderName(uploader);
|
||||
item.setDuration(duration);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalItemType getLocalItemType() {
|
||||
return LocalItemType.PLAYLIST_STREAM_ITEM;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import android.arch.persistence.room.Transaction;
|
|||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
||||
|
@ -37,17 +38,13 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
|
|||
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||
public abstract void deleteBatch(final long playlistId);
|
||||
|
||||
@Query("SELECT MAX(" + JOIN_INDEX + ")" +
|
||||
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" +
|
||||
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||
public abstract Flowable<Integer> getMaximumIndexOf(final long playlistId);
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT " + STREAM_ID + ", " + STREAM_SERVICE_ID + ", " + STREAM_URL + ", " +
|
||||
STREAM_TITLE + ", " + STREAM_TYPE + ", " + STREAM_UPLOADER + ", " +
|
||||
STREAM_DURATION + ", " + STREAM_THUMBNAIL_URL +
|
||||
|
||||
" FROM " + STREAM_TABLE + " INNER JOIN " +
|
||||
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " +
|
||||
// get ids of streams of the given playlist
|
||||
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX +
|
||||
" FROM " + PLAYLIST_STREAM_JOIN_TABLE + " WHERE "
|
||||
|
@ -56,14 +53,16 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
|
|||
// then merge with the stream metadata
|
||||
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
||||
" ORDER BY " + JOIN_INDEX + " ASC")
|
||||
public abstract Flowable<List<StreamEntity>> getOrderedStreamsOf(long playlistId);
|
||||
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " +
|
||||
PLAYLIST_THUMBNAIL_URL + ", COUNT(*) AS " + PLAYLIST_STREAM_COUNT +
|
||||
PLAYLIST_THUMBNAIL_URL + ", " +
|
||||
"COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT +
|
||||
|
||||
" FROM " + PLAYLIST_TABLE + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||
" ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + PLAYLIST_STREAM_JOIN_TABLE + "." + JOIN_PLAYLIST_ID +
|
||||
" FROM " + PLAYLIST_TABLE +
|
||||
" LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||
" ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID +
|
||||
" GROUP BY " + JOIN_PLAYLIST_ID)
|
||||
public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ package org.schabi.newpipe.database.stream;
|
|||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class StreamStatisticsEntry {
|
||||
public class StreamStatisticsEntry implements LocalItem {
|
||||
final public static String STREAM_LATEST_DATE = "latestAccess";
|
||||
final public static String STREAM_WATCH_COUNT = "watchCount";
|
||||
|
||||
|
@ -53,14 +54,16 @@ public class StreamStatisticsEntry {
|
|||
this.watchCount = watchCount;
|
||||
}
|
||||
|
||||
public StreamStatisticsInfoItem toStreamStatisticsInfoItem() {
|
||||
StreamStatisticsInfoItem item =
|
||||
new StreamStatisticsInfoItem(uid, serviceId, url, title, streamType);
|
||||
public StreamInfoItem toStreamInfoItem() {
|
||||
StreamInfoItem item = new StreamInfoItem(serviceId, url, title, streamType);
|
||||
item.setDuration(duration);
|
||||
item.setUploaderName(uploader);
|
||||
item.setThumbnailUrl(thumbnailUrl);
|
||||
item.setLatestAccessDate(latestAccessDate);
|
||||
item.setWatchCount(watchCount);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalItemType getLocalItemType() {
|
||||
return LocalItemType.STATISTIC_STREAM_ITEM;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.arch.persistence.room.PrimaryKey;
|
|||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.info_list.stored.StreamEntityInfoItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
|
||||
|
@ -88,16 +87,6 @@ public class StreamEntity implements Serializable {
|
|||
item.getThumbnailUrl(), item.getUploader(), item.getDuration());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
public StreamEntityInfoItem toStreamEntityInfoItem() throws IllegalArgumentException {
|
||||
StreamEntityInfoItem item = new StreamEntityInfoItem(getUid(), getServiceId(),
|
||||
getUrl(), getTitle(), getStreamType());
|
||||
item.setThumbnailUrl(getThumbnailUrl());
|
||||
item.setUploaderName(getUploader());
|
||||
item.setDuration(getDuration());
|
||||
return item;
|
||||
}
|
||||
|
||||
public long getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.schabi.newpipe.extractor.kiosk.KioskList;
|
|||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.fragments.list.feed.FeedFragment;
|
||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||
import org.schabi.newpipe.fragments.local.BookmarkFragment;
|
||||
import org.schabi.newpipe.fragments.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.fragments.subscription.SubscriptionFragment;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.list.ListViewContract;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
implements ListViewContract<I, N>, StateSaver.WriteRead {
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected LocalItemListAdapter itemListAdapter;
|
||||
protected RecyclerView itemsList;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
itemListAdapter = new LocalItemListAdapter(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
StateSaver.onDestroy(savedState);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected StateSaver.SavedState savedState;
|
||||
|
||||
@Override
|
||||
public String generateSuffix() {
|
||||
// Naive solution, but it's good for now (the items don't change)
|
||||
return "." + itemListAdapter.getItemsList().size() + ".list";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Queue<Object> objectsToSave) {
|
||||
objectsToSave.add(itemListAdapter.getItemsList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
||||
itemListAdapter.getItemsList().clear();
|
||||
itemListAdapter.getItemsList().addAll((List<LocalItem>) savedObjects.poll());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle bundle) {
|
||||
super.onSaveInstanceState(bundle);
|
||||
savedState = StateSaver.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
|
||||
super.onRestoreInstanceState(bundle);
|
||||
savedState = StateSaver.tryToRestore(bundle, this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected View getListHeader() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected View getListFooter() {
|
||||
return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false);
|
||||
}
|
||||
|
||||
protected RecyclerView.LayoutManager getListLayoutManager() {
|
||||
return new LinearLayoutManager(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(getListLayoutManager());
|
||||
|
||||
itemListAdapter.setFooter(getListFooter());
|
||||
itemListAdapter.setHeader(getListHeader());
|
||||
|
||||
itemsList.setAdapter(itemListAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||
if(useAsFrontPage) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
} else {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
// animateView(itemsList, false, 400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLoading() {
|
||||
super.hideLoading();
|
||||
animateView(itemsList, true, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(String message, boolean showRetryButton) {
|
||||
super.showError(message, showRetryButton);
|
||||
showListFooter(false);
|
||||
animateView(itemsList, false, 200);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showEmptyState() {
|
||||
super.showEmptyState();
|
||||
showListFooter(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showListFooter(final boolean show) {
|
||||
itemsList.post(() -> itemListAdapter.showFooter(show));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(N result) {
|
||||
isLoading.set(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
public class HeaderFooterHolder extends RecyclerView.ViewHolder {
|
||||
public View view;
|
||||
|
||||
public HeaderFooterHolder(View v) {
|
||||
super(v);
|
||||
view = v;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 26.09.16.
|
||||
* <p>
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemBuilder.java is part of NewPipe.
|
||||
* <p>
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class LocalItemBuilder {
|
||||
private static final String TAG = LocalItemBuilder.class.toString();
|
||||
|
||||
private final Context context;
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
private OnCustomItemGesture<LocalItem> onSelectedListener;
|
||||
|
||||
public LocalItemBuilder(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public ImageLoader getImageLoader() {
|
||||
return imageLoader;
|
||||
}
|
||||
|
||||
public OnCustomItemGesture<LocalItem> getOnItemSelectedListener() {
|
||||
return onSelectedListener;
|
||||
}
|
||||
|
||||
public void setOnItemSelectedListener(OnCustomItemGesture<LocalItem> listener) {
|
||||
this.onSelectedListener = listener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.fragments.local.holder.LocalItemHolder;
|
||||
import org.schabi.newpipe.fragments.local.holder.LocalPlaylistItemHolder;
|
||||
import org.schabi.newpipe.fragments.local.holder.LocalPlaylistStreamItemHolder;
|
||||
import org.schabi.newpipe.fragments.local.holder.LocalStatisticStreamItemHolder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* InfoListAdapter.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private static final String TAG = LocalItemListAdapter.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int HEADER_TYPE = 0;
|
||||
private static final int FOOTER_TYPE = 1;
|
||||
|
||||
private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000;
|
||||
private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001;
|
||||
private static final int PLAYLIST_HOLDER_TYPE = 0x2000;
|
||||
|
||||
private final LocalItemBuilder localItemBuilder;
|
||||
private final ArrayList<LocalItem> localItems;
|
||||
private final DateFormat dateFormat;
|
||||
|
||||
private boolean showFooter = false;
|
||||
private View header = null;
|
||||
private View footer = null;
|
||||
|
||||
|
||||
public LocalItemListAdapter(Activity activity) {
|
||||
localItemBuilder = new LocalItemBuilder(activity);
|
||||
localItems = new ArrayList<>();
|
||||
dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,
|
||||
Localization.getPreferredLocale(activity));
|
||||
}
|
||||
|
||||
public void setSelectedListener(OnCustomItemGesture<LocalItem> listener) {
|
||||
localItemBuilder.setOnItemSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void addInfoItemList(List<? extends LocalItem> data) {
|
||||
if (data != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addInfoItemList() before > localItems.size() = " +
|
||||
localItems.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
|
||||
int offsetStart = sizeConsideringHeader();
|
||||
localItems.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart +
|
||||
", localItems.size() = " + localItems.size() +
|
||||
", header = " + header + ", footer = " + footer +
|
||||
", showFooter = " + showFooter);
|
||||
}
|
||||
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeader();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart +
|
||||
" to " + footerNow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addInfoItem(LocalItem data) {
|
||||
addInfoItemList(Collections.singletonList(data));
|
||||
}
|
||||
|
||||
public void removeItemAt(final int infoListPosition) {
|
||||
if (infoListPosition < 0 || infoListPosition >= localItems.size()) return;
|
||||
|
||||
localItems.remove(infoListPosition);
|
||||
|
||||
notifyItemRemoved(infoListPosition + (header != null ? 1 : 0));
|
||||
}
|
||||
|
||||
public boolean swapItems(int fromAdapterPosition, int toAdapterPosition) {
|
||||
final int actualFrom = offsetWithoutHeader(fromAdapterPosition);
|
||||
final int actualTo = offsetWithoutHeader(toAdapterPosition);
|
||||
|
||||
if (actualFrom < 0 || actualTo < 0) return false;
|
||||
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) return false;
|
||||
|
||||
localItems.add(actualTo, localItems.remove(actualFrom));
|
||||
notifyItemMoved(fromAdapterPosition, toAdapterPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void clearStreamItemList() {
|
||||
if (localItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
localItems.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setHeader(View header) {
|
||||
boolean changed = header != this.header;
|
||||
this.header = header;
|
||||
if (changed) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setFooter(View view) {
|
||||
this.footer = view;
|
||||
}
|
||||
|
||||
public void showFooter(boolean show) {
|
||||
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]");
|
||||
if (show == showFooter) return;
|
||||
|
||||
showFooter = show;
|
||||
if (show) notifyItemInserted(sizeConsideringHeader());
|
||||
else notifyItemRemoved(sizeConsideringHeader());
|
||||
}
|
||||
|
||||
private int offsetWithoutHeader(final int offset) {
|
||||
return offset - (header != null ? 1 : 0);
|
||||
}
|
||||
|
||||
private int sizeConsideringHeader() {
|
||||
return localItems.size() + (header != null ? 1 : 0);
|
||||
}
|
||||
|
||||
public ArrayList<LocalItem> getItemsList() {
|
||||
return localItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int count = localItems.size();
|
||||
if (header != null) count++;
|
||||
if (footer != null && showFooter) count++;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "getItemCount() called, count = " + count +
|
||||
", localItems.size() = " + localItems.size() +
|
||||
", header = " + header + ", footer = " + footer +
|
||||
", showFooter = " + showFooter);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
|
||||
|
||||
if (header != null && position == 0) {
|
||||
return HEADER_TYPE;
|
||||
} else if (header != null) {
|
||||
position--;
|
||||
}
|
||||
if (footer != null && position == localItems.size() && showFooter) {
|
||||
return FOOTER_TYPE;
|
||||
}
|
||||
final LocalItem item = localItems.get(position);
|
||||
|
||||
switch (item.getLocalItemType()) {
|
||||
case PLAYLIST_ITEM: return PLAYLIST_HOLDER_TYPE;
|
||||
case PLAYLIST_STREAM_ITEM: return STREAM_PLAYLIST_HOLDER_TYPE;
|
||||
case STATISTIC_STREAM_ITEM: return STREAM_STATISTICS_HOLDER_TYPE;
|
||||
default:
|
||||
Log.e(TAG, "No holder type has been considered for item: [" +
|
||||
item.getLocalItemType() + "]");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" +
|
||||
parent + "], type = [" + type + "]");
|
||||
switch (type) {
|
||||
case HEADER_TYPE:
|
||||
return new HeaderFooterHolder(header);
|
||||
case FOOTER_TYPE:
|
||||
return new HeaderFooterHolder(footer);
|
||||
case PLAYLIST_HOLDER_TYPE:
|
||||
return new LocalPlaylistItemHolder(localItemBuilder, parent);
|
||||
case STREAM_PLAYLIST_HOLDER_TYPE:
|
||||
return new LocalPlaylistStreamItemHolder(localItemBuilder, parent);
|
||||
case STREAM_STATISTICS_HOLDER_TYPE:
|
||||
return new LocalStatisticStreamItemHolder(localItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "No view type has been considered for holder: [" + type + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" +
|
||||
holder.getClass().getSimpleName() + "], position = [" + position + "]");
|
||||
|
||||
if (holder instanceof LocalItemHolder) {
|
||||
// If header isn't null, offset the items by -1
|
||||
if (header != null) position--;
|
||||
|
||||
((LocalItemHolder) holder).updateFromItem(localItems.get(position), dateFormat);
|
||||
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
|
||||
((HeaderFooterHolder) holder).view = header;
|
||||
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
|
||||
&& footer != null && showFooter) {
|
||||
((HeaderFooterHolder) holder).view = footer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,25 +7,29 @@ import android.os.Bundle;
|
|||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -37,7 +41,7 @@ import io.reactivex.disposables.CompositeDisposable;
|
|||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>, Void> {
|
||||
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
|
||||
|
||||
private View headerRootLayout;
|
||||
private TextView headerTitleView;
|
||||
|
@ -49,12 +53,14 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
private View headerBackgroundButton;
|
||||
|
||||
@State
|
||||
protected long playlistId;
|
||||
protected Long playlistId;
|
||||
@State
|
||||
protected String name;
|
||||
@State
|
||||
protected Parcelable itemsListState;
|
||||
|
||||
private ItemTouchHelper itemTouchHelper;
|
||||
|
||||
/* Used for independent events */
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private Subscription databaseSubscription;
|
||||
|
@ -86,6 +92,9 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
saveJoin();
|
||||
|
||||
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
||||
}
|
||||
|
||||
|
@ -115,8 +124,6 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
infoListAdapter.useMiniItemVariants(true);
|
||||
|
||||
setFragmentTitle(name);
|
||||
}
|
||||
|
||||
|
@ -141,44 +148,61 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
infoListAdapter.setOnStreamSelectedListener(new OnInfoItemGesture<StreamInfoItem>() {
|
||||
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(itemsList);
|
||||
|
||||
itemListAdapter.setSelectedListener(new OnCustomItemGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(StreamInfoItem selectedItem) {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
public void selected(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof PlaylistStreamEntry) {
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
|
||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
||||
selectedItem.getServiceId(), selectedItem.url, selectedItem.getName());
|
||||
item.serviceId, item.url, item.title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(StreamInfoItem selectedItem) {
|
||||
showStreamDialog(selectedItem);
|
||||
public void held(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof PlaylistStreamEntry) {
|
||||
showStreamDialog((PlaylistStreamEntry) selectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drag(LocalItem selectedItem, RecyclerView.ViewHolder viewHolder) {
|
||||
if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder);
|
||||
}
|
||||
});
|
||||
|
||||
headerTitleView.setOnClickListener(view -> createRenameDialog());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
protected void showStreamDialog(final PlaylistStreamEntry item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
"Set as Thumbnail",
|
||||
context.getResources().getString(R.string.delete)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||
final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context,
|
||||
new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new
|
||||
SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
|
@ -189,18 +213,56 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
changeThumbnailUrl(item.thumbnailUrl);
|
||||
break;
|
||||
case 6:
|
||||
itemListAdapter.removeItemAt(index);
|
||||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
|
||||
}
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.ACTION_STATE_IDLE) {
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
|
||||
RecyclerView.ViewHolder target) {
|
||||
if (source.getItemViewType() != target.getItemViewType() ||
|
||||
itemListAdapter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int sourceIndex = source.getAdapterPosition();
|
||||
final int targetIndex = target.getAdapterPosition();
|
||||
return itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
|
||||
};
|
||||
}
|
||||
|
||||
private void resetFragment() {
|
||||
if (disposables != null) disposables.clear();
|
||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||
if (itemListAdapter != null) itemListAdapter.clearStreamItemList();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -224,8 +286,8 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
.subscribe(getPlaylistObserver());
|
||||
}
|
||||
|
||||
private Subscriber<List<StreamEntity>> getPlaylistObserver() {
|
||||
return new Subscriber<List<StreamEntity>>() {
|
||||
private Subscriber<List<PlaylistStreamEntry>> getPlaylistObserver() {
|
||||
return new Subscriber<List<PlaylistStreamEntry>>() {
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
showLoading();
|
||||
|
@ -236,7 +298,7 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onNext(List<StreamEntity> streams) {
|
||||
public void onNext(List<PlaylistStreamEntry> streams) {
|
||||
handleResult(streams);
|
||||
if (databaseSubscription != null) databaseSubscription.request(1);
|
||||
}
|
||||
|
@ -253,9 +315,9 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull List<StreamEntity> result) {
|
||||
public void handleResult(@NonNull List<PlaylistStreamEntry> result) {
|
||||
super.handleResult(result);
|
||||
infoListAdapter.clearStreamItemList();
|
||||
itemListAdapter.clearStreamItemList();
|
||||
|
||||
if (result.isEmpty()) {
|
||||
showEmptyState();
|
||||
|
@ -265,15 +327,14 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
animateView(headerRootLayout, true, 100);
|
||||
animateView(itemsList, true, 300);
|
||||
|
||||
infoListAdapter.addInfoItemList(getStreamItems(result));
|
||||
itemListAdapter.addInfoItemList(result);
|
||||
if (itemsListState != null) {
|
||||
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||
itemsListState = null;
|
||||
}
|
||||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
|
||||
playlistControl.setVisibility(View.VISIBLE);
|
||||
headerStreamCount.setText(
|
||||
getResources().getQuantityString(R.plurals.videos, result.size(), result.size()));
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||
|
@ -284,29 +345,6 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
hideLoading();
|
||||
}
|
||||
|
||||
|
||||
private List<InfoItem> getStreamItems(final List<StreamEntity> streams) {
|
||||
List<InfoItem> items = new ArrayList<>(streams.size());
|
||||
for (final StreamEntity stream : streams) {
|
||||
items.add(stream.toStreamEntityInfoItem());
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasMoreItems() {
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Error Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -325,13 +363,13 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void setInitialData(long playlistId, String name) {
|
||||
private void setInitialData(long playlistId, String name) {
|
||||
this.playlistId = playlistId;
|
||||
this.name = !TextUtils.isEmpty(name) ? name : "";
|
||||
}
|
||||
|
||||
protected void setFragmentTitle(final String title) {
|
||||
if (activity.getSupportActionBar() != null) {
|
||||
private void setFragmentTitle(final String title) {
|
||||
if (activity != null && activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar().setTitle(title);
|
||||
}
|
||||
if (headerTitleView != null) {
|
||||
|
@ -339,17 +377,80 @@ public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>,
|
|||
}
|
||||
}
|
||||
|
||||
private void setVideoCount(final long count) {
|
||||
if (activity != null && headerStreamCount != null) {
|
||||
headerStreamCount.setText(Localization.localizeStreamCount(activity, count));
|
||||
}
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
return getPlayQueue(0);
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
final List<InfoItem> infoItems = infoListAdapter.getItemsList();
|
||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
for (final InfoItem item : infoItems) {
|
||||
if (item instanceof StreamInfoItem) streamInfoItems.add((StreamInfoItem) item);
|
||||
for (final LocalItem item : infoItems) {
|
||||
if (item instanceof PlaylistStreamEntry) {
|
||||
streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem());
|
||||
}
|
||||
}
|
||||
return new SinglePlayQueue(streamInfoItems, index);
|
||||
}
|
||||
|
||||
private void createRenameDialog() {
|
||||
if (playlistId == null || name == null || getContext() == null) return;
|
||||
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
||||
nameEdit.setText(name);
|
||||
nameEdit.setSelection(nameEdit.getText().length());
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.rename_playlist)
|
||||
.setView(dialogView)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.create, (dialogInterface, i) -> {
|
||||
name = nameEdit.getText().toString();
|
||||
setFragmentTitle(name);
|
||||
|
||||
final LocalPlaylistManager playlistManager =
|
||||
new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
|
||||
final Toast successToast = Toast.makeText(getActivity(),
|
||||
"Playlist renamed",
|
||||
Toast.LENGTH_SHORT);
|
||||
|
||||
playlistManager.renamePlaylist(playlistId, name)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(longs -> successToast.show());
|
||||
});
|
||||
|
||||
dialogBuilder.show();
|
||||
}
|
||||
|
||||
private void changeThumbnailUrl(final String thumbnailUrl) {
|
||||
final Toast successToast = Toast.makeText(getActivity(),
|
||||
"Playlist thumbnail changed",
|
||||
Toast.LENGTH_SHORT);
|
||||
|
||||
playlistManager.changePlaylistThumbnail(playlistId, thumbnailUrl)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignore -> successToast.show());
|
||||
}
|
||||
|
||||
private void saveJoin() {
|
||||
final List<LocalItem> items = itemListAdapter.getItemsList();
|
||||
List<Long> streamIds = new ArrayList<>(items.size());
|
||||
for (final LocalItem item : items) {
|
||||
if (item instanceof PlaylistStreamEntry) {
|
||||
streamIds.add(((PlaylistStreamEntry) item).streamId);
|
||||
}
|
||||
}
|
||||
|
||||
playlistManager.updateJoin(playlistId, streamIds)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.support.annotation.Nullable;
|
|||
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
|
||||
import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||
|
@ -84,7 +85,7 @@ public class LocalPlaylistManager {
|
|||
return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Flowable<List<StreamEntity>> getPlaylistStreams(final long playlistId) {
|
||||
public Flowable<List<PlaylistStreamEntry>> getPlaylistStreams(final long playlistId) {
|
||||
return playlistStreamTable.getOrderedStreamsOf(playlistId).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class MostPlayedFragment extends StatisticsPlaylistFragment {
|
||||
@Override
|
||||
protected String getName() {
|
||||
return getString(R.string.title_most_played);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<InfoItem> processResult(List<StreamStatisticsEntry> results) {
|
||||
Collections.sort(results, (left, right) ->
|
||||
((Long) right.watchCount).compareTo(left.watchCount));
|
||||
|
||||
List<InfoItem> items = new ArrayList<>(results.size());
|
||||
for (final StreamStatisticsEntry stream : results) {
|
||||
items.add(stream.toStreamStatisticsInfoItem());
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAdditionalDetail(StreamStatisticsInfoItem infoItem) {
|
||||
final int watchCount = (int) infoItem.getWatchCount();
|
||||
return getResources().getQuantityString(R.plurals.views, watchCount, watchCount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
||||
public abstract class OnCustomItemGesture<T extends LocalItem> {
|
||||
|
||||
public abstract void selected(T selectedItem);
|
||||
|
||||
public void held(T selectedItem) {
|
||||
// Optional gesture
|
||||
}
|
||||
|
||||
public void drag(T selectedItem, RecyclerView.ViewHolder viewHolder) {
|
||||
// Optional gesture
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class WatchHistoryFragment extends StatisticsPlaylistFragment {
|
||||
@Override
|
||||
protected String getName() {
|
||||
return getString(R.string.title_watch_history);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<InfoItem> processResult(List<StreamStatisticsEntry> results) {
|
||||
Collections.sort(results, (left, right) ->
|
||||
right.latestAccessDate.compareTo(left.latestAccessDate));
|
||||
|
||||
List<InfoItem> items = new ArrayList<>(results.size());
|
||||
for (final StreamStatisticsEntry stream : results) {
|
||||
items.add(stream.toStreamStatisticsInfoItem());
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAdditionalDetail(StreamStatisticsInfoItem infoItem) {
|
||||
return DateFormat.getLongDateFormat(getContext()).format(infoItem.getLatestAccessDate());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
package org.schabi.newpipe.fragments.local.bookmark;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
@ -17,18 +17,15 @@ import org.reactivestreams.Subscriber;
|
|||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||
import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem;
|
||||
import org.schabi.newpipe.fragments.local.LocalItemListAdapter;
|
||||
import org.schabi.newpipe.fragments.local.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.fragments.local.OnCustomItemGesture;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -42,7 +39,7 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
private View watchHistoryButton;
|
||||
private View mostWatchedButton;
|
||||
|
||||
private InfoListAdapter infoListAdapter;
|
||||
private LocalItemListAdapter itemListAdapter;
|
||||
private RecyclerView itemsList;
|
||||
|
||||
@State
|
||||
|
@ -68,7 +65,7 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
infoListAdapter = new InfoListAdapter(activity);
|
||||
itemListAdapter = new LocalItemListAdapter(activity);
|
||||
localPlaylistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(context));
|
||||
}
|
||||
|
||||
|
@ -122,7 +119,6 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
infoListAdapter = new InfoListAdapter(getActivity());
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(new LinearLayoutManager(activity));
|
||||
|
||||
|
@ -131,35 +127,30 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
watchHistoryButton = headerRootLayout.findViewById(R.id.watchHistory);
|
||||
mostWatchedButton = headerRootLayout.findViewById(R.id.mostWatched);
|
||||
|
||||
infoListAdapter.setHeader(headerRootLayout);
|
||||
infoListAdapter.useMiniItemVariants(true);
|
||||
itemListAdapter.setHeader(headerRootLayout);
|
||||
|
||||
itemsList.setAdapter(infoListAdapter);
|
||||
itemsList.setAdapter(itemListAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
infoListAdapter.setOnPlaylistSelectedListener(new OnInfoItemGesture<PlaylistInfoItem>() {
|
||||
itemListAdapter.setSelectedListener(new OnCustomItemGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(PlaylistInfoItem selectedItem) {
|
||||
public void selected(LocalItem selectedItem) {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
if (selectedItem instanceof LocalPlaylistInfoItem && getParentFragment() != null) {
|
||||
final long playlistId = ((LocalPlaylistInfoItem) selectedItem).getPlaylistId();
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry && getParentFragment() != null) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
NavigationHelper.openLocalPlaylistFragment(
|
||||
getParentFragment().getFragmentManager(),
|
||||
playlistId,
|
||||
selectedItem.getName()
|
||||
);
|
||||
getParentFragment().getFragmentManager(), entry.uid, entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(PlaylistInfoItem selectedItem) {
|
||||
if (selectedItem instanceof LocalPlaylistInfoItem) {
|
||||
showPlaylistDialog((LocalPlaylistInfoItem) selectedItem);
|
||||
public void held(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
showDeleteDialog((PlaylistMetadataEntry) selectedItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -177,36 +168,25 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
});
|
||||
}
|
||||
|
||||
private void showPlaylistDialog(final LocalPlaylistInfoItem item) {
|
||||
final Context context = getContext();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.delete_playlist)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
private void showDeleteDialog(final PlaylistMetadataEntry item) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(item.name)
|
||||
.setMessage(R.string.delete_playlist_prompt)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.delete, (dialog, i) -> {
|
||||
final Toast deleteSuccessful =
|
||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT);
|
||||
disposables.add(localPlaylistManager.deletePlaylist(item.getPlaylistId())
|
||||
disposables.add(localPlaylistManager.deletePlaylist(item.uid)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> deleteSuccessful.show()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
final String videoCount = getResources().getQuantityString(R.plurals.videos,
|
||||
(int) item.getStreamCount(), (int) item.getStreamCount());
|
||||
new InfoItemDialog(getActivity(), commands, actions, item.getName(), videoCount).show();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void resetFragment() {
|
||||
if (disposables != null) disposables.clear();
|
||||
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||
if (itemListAdapter != null) itemListAdapter.clearStreamItemList();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -254,12 +234,12 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
public void handleResult(@NonNull List<PlaylistMetadataEntry> result) {
|
||||
super.handleResult(result);
|
||||
|
||||
infoListAdapter.clearStreamItemList();
|
||||
itemListAdapter.clearStreamItemList();
|
||||
|
||||
if (result.isEmpty()) {
|
||||
showEmptyState();
|
||||
} else {
|
||||
infoListAdapter.addInfoItemList(infoItemsOf(result));
|
||||
itemListAdapter.addInfoItemList(infoItemsOf(result));
|
||||
if (itemsListState != null) {
|
||||
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||
itemsListState = null;
|
||||
|
@ -269,13 +249,9 @@ public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEnt
|
|||
}
|
||||
|
||||
|
||||
private List<InfoItem> infoItemsOf(List<PlaylistMetadataEntry> playlists) {
|
||||
List<InfoItem> playlistInfoItems = new ArrayList<>(playlists.size());
|
||||
for (final PlaylistMetadataEntry playlist : playlists) {
|
||||
playlistInfoItems.add(playlist.toStoredPlaylistInfoItem());
|
||||
}
|
||||
Collections.sort(playlistInfoItems, (o1, o2) -> o1.name.compareToIgnoreCase(o2.name));
|
||||
return playlistInfoItems;
|
||||
private List<PlaylistMetadataEntry> infoItemsOf(List<PlaylistMetadataEntry> playlists) {
|
||||
Collections.sort(playlists, (o1, o2) -> o1.name.compareToIgnoreCase(o2.name));
|
||||
return playlists;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
|
@ -0,0 +1,22 @@
|
|||
package org.schabi.newpipe.fragments.local.bookmark;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class MostPlayedFragment extends StatisticsPlaylistFragment {
|
||||
@Override
|
||||
protected String getName() {
|
||||
return getString(R.string.title_most_played);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StreamStatisticsEntry> processResult(List<StreamStatisticsEntry> results) {
|
||||
Collections.sort(results, (left, right) ->
|
||||
((Long) right.watchCount).compareTo(left.watchCount));
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.schabi.newpipe.fragments.local;
|
||||
package org.schabi.newpipe.fragments.local.bookmark;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
@ -14,14 +14,13 @@ import android.view.ViewGroup;
|
|||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.fragments.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.fragments.local.OnCustomItemGesture;
|
||||
import org.schabi.newpipe.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.OnInfoItemGesture;
|
||||
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
@ -36,7 +35,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public abstract class StatisticsPlaylistFragment
|
||||
extends BaseListFragment<List<StreamStatisticsEntry>, Void> {
|
||||
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
|
||||
|
||||
private View headerRootLayout;
|
||||
private View playlistControl;
|
||||
|
@ -57,9 +56,7 @@ public abstract class StatisticsPlaylistFragment
|
|||
|
||||
protected abstract String getName();
|
||||
|
||||
protected abstract List<InfoItem> processResult(final List<StreamStatisticsEntry> results);
|
||||
|
||||
protected abstract String getAdditionalDetail(final StreamStatisticsInfoItem infoItem);
|
||||
protected abstract List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle
|
||||
|
@ -106,8 +103,6 @@ public abstract class StatisticsPlaylistFragment
|
|||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
infoListAdapter.useMiniItemVariants(true);
|
||||
|
||||
setFragmentTitle(getName());
|
||||
}
|
||||
|
||||
|
@ -127,27 +122,31 @@ public abstract class StatisticsPlaylistFragment
|
|||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
infoListAdapter.setOnStreamSelectedListener(new OnInfoItemGesture<StreamInfoItem>() {
|
||||
itemListAdapter.setSelectedListener(new OnCustomItemGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(StreamInfoItem selectedItem) {
|
||||
public void selected(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
|
||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
||||
selectedItem.getServiceId(), selectedItem.url, selectedItem.getName());
|
||||
item.serviceId, item.url, item.title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void held(StreamInfoItem selectedItem) {
|
||||
showStreamDialog(selectedItem);
|
||||
public void held(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||
showStreamDialog((StreamStatisticsEntry) selectedItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
private void showStreamDialog(final StreamStatisticsEntry item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null
|
||||
|| getActivity() == null || !(item instanceof StreamStatisticsInfoItem)) return;
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
|
@ -158,13 +157,13 @@ public abstract class StatisticsPlaylistFragment
|
|||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||
final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
|
@ -180,13 +179,12 @@ public abstract class StatisticsPlaylistFragment
|
|||
}
|
||||
};
|
||||
|
||||
final String detail = getAdditionalDetail((StreamStatisticsInfoItem) item);
|
||||
new InfoItemDialog(getActivity(), commands, actions, item.getName(), detail).show();
|
||||
new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
|
||||
}
|
||||
|
||||
private void resetFragment() {
|
||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||
if (itemListAdapter != null) itemListAdapter.clearStreamItemList();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -241,7 +239,7 @@ public abstract class StatisticsPlaylistFragment
|
|||
@Override
|
||||
public void handleResult(@NonNull List<StreamStatisticsEntry> result) {
|
||||
super.handleResult(result);
|
||||
infoListAdapter.clearStreamItemList();
|
||||
itemListAdapter.clearStreamItemList();
|
||||
|
||||
if (result.isEmpty()) {
|
||||
showEmptyState();
|
||||
|
@ -251,7 +249,7 @@ public abstract class StatisticsPlaylistFragment
|
|||
animateView(headerRootLayout, true, 100);
|
||||
animateView(itemsList, true, 300);
|
||||
|
||||
infoListAdapter.addInfoItemList(processResult(result));
|
||||
itemListAdapter.addInfoItemList(processResult(result));
|
||||
if (itemsListState != null) {
|
||||
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||
itemsListState = null;
|
||||
|
@ -267,20 +265,6 @@ public abstract class StatisticsPlaylistFragment
|
|||
hideLoading();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasMoreItems() {
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Error Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -310,10 +294,12 @@ public abstract class StatisticsPlaylistFragment
|
|||
}
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
final List<InfoItem> infoItems = infoListAdapter.getItemsList();
|
||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
for (final InfoItem item : infoItems) {
|
||||
if (item instanceof StreamInfoItem) streamInfoItems.add((StreamInfoItem) item);
|
||||
for (final LocalItem item : infoItems) {
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
|
||||
}
|
||||
}
|
||||
return new SinglePlayQueue(streamInfoItems, index);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.schabi.newpipe.fragments.local.bookmark;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class WatchHistoryFragment extends StatisticsPlaylistFragment {
|
||||
@Override
|
||||
protected String getName() {
|
||||
return getString(R.string.title_watch_history);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StreamStatisticsEntry> processResult(List<StreamStatisticsEntry> results) {
|
||||
Collections.sort(results, (left, right) ->
|
||||
right.latestAccessDate.compareTo(left.latestAccessDate));
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.schabi.newpipe.fragments.local.holder;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemHolder.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
|
||||
protected final LocalItemBuilder itemBuilder;
|
||||
|
||||
public LocalItemHolder(LocalItemBuilder itemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(LayoutInflater.from(itemBuilder.getContext())
|
||||
.inflate(layoutId, parent, false));
|
||||
this.itemBuilder = itemBuilder;
|
||||
}
|
||||
|
||||
public abstract void updateFromItem(final LocalItem item, final DateFormat dateFormat);
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ImageLoaderOptions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Base display options
|
||||
*/
|
||||
public static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS =
|
||||
new DisplayImageOptions.Builder()
|
||||
.cacheInMemory(true)
|
||||
.build();
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.schabi.newpipe.fragments.local.holder;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
public class LocalPlaylistItemHolder extends LocalItemHolder {
|
||||
public final ImageView itemThumbnailView;
|
||||
public final TextView itemStreamCountView;
|
||||
public final TextView itemTitleView;
|
||||
public final TextView itemUploaderView;
|
||||
|
||||
public LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder,
|
||||
int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
itemStreamCountView = itemView.findViewById(R.id.itemStreamCountView);
|
||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||
}
|
||||
|
||||
public LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry)) return;
|
||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
||||
|
||||
itemTitleView.setText(item.name);
|
||||
itemStreamCountView.setText(String.valueOf(item.streamCount));
|
||||
itemUploaderView.setVisibility(View.INVISIBLE);
|
||||
|
||||
itemBuilder.getImageLoader().displayImage(item.thumbnailUrl, itemThumbnailView,
|
||||
DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
|
||||
itemView.setLongClickable(true);
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().held(item);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display options for playlist thumbnails
|
||||
*/
|
||||
public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS =
|
||||
new DisplayImageOptions.Builder()
|
||||
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
|
||||
.showImageOnLoading(R.drawable.dummy_thumbnail_playlist)
|
||||
.showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist)
|
||||
.showImageOnFail(R.drawable.dummy_thumbnail_playlist)
|
||||
.build();
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package org.schabi.newpipe.info_list.holder;
|
||||
package org.schabi.newpipe.fragments.local.holder;
|
||||
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -11,40 +10,44 @@ import android.widget.TextView;
|
|||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
public class StreamPlaylistInfoItemHolder extends InfoItemHolder {
|
||||
import java.text.DateFormat;
|
||||
|
||||
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
|
||||
public final ImageView itemThumbnailView;
|
||||
public final TextView itemVideoTitleView;
|
||||
public final TextView itemUploaderView;
|
||||
public final TextView itemAdditionalDetailsView;
|
||||
public final TextView itemDurationView;
|
||||
public final View itemHandleView;
|
||||
|
||||
StreamPlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
|
||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||
itemAdditionalDetailsView = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||
itemDurationView = itemView.findViewById(R.id.itemDurationView);
|
||||
itemHandleView = itemView.findViewById(R.id.itemHandle);
|
||||
}
|
||||
|
||||
public StreamPlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
|
||||
public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_stream_playlist_item, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
if (!(infoItem instanceof StreamInfoItem)) return;
|
||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistStreamEntry)) return;
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||
|
||||
itemVideoTitleView.setText(item.getName());
|
||||
itemUploaderView.setText(item.uploader_name);
|
||||
itemVideoTitleView.setText(item.title);
|
||||
itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.uploader,
|
||||
NewPipe.getNameOfService(item.serviceId)));
|
||||
|
||||
if (item.duration > 0) {
|
||||
itemDurationView.setText(Localization.getDurationString(item.duration));
|
||||
|
@ -56,19 +59,19 @@ public class StreamPlaylistInfoItemHolder extends InfoItemHolder {
|
|||
}
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
itemBuilder.getImageLoader().displayImage(item.thumbnail_url, itemThumbnailView,
|
||||
StreamPlaylistInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
itemBuilder.getImageLoader().displayImage(item.thumbnailUrl, itemThumbnailView,
|
||||
LocalPlaylistStreamItemHolder.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnStreamSelectedListener() != null) {
|
||||
itemBuilder.getOnStreamSelectedListener().selected(item);
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
|
||||
itemView.setLongClickable(true);
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (itemBuilder.getOnStreamSelectedListener() != null) {
|
||||
itemBuilder.getOnStreamSelectedListener().held(item);
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().held(item);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@ -77,13 +80,13 @@ public class StreamPlaylistInfoItemHolder extends InfoItemHolder {
|
|||
itemHandleView.setOnTouchListener(getOnTouchListener(item));
|
||||
}
|
||||
|
||||
private View.OnTouchListener getOnTouchListener(final StreamInfoItem item) {
|
||||
private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) {
|
||||
return (view, motionEvent) -> {
|
||||
view.performClick();
|
||||
if (itemBuilder != null &&
|
||||
motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
itemBuilder.getOnStreamSelectedListener()
|
||||
.drag(item, StreamPlaylistInfoItemHolder.this);
|
||||
itemBuilder.getOnItemSelectedListener().drag(item,
|
||||
LocalPlaylistStreamItemHolder.this);
|
||||
}
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,119 @@
|
|||
package org.schabi.newpipe.fragments.local.holder;
|
||||
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.fragments.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
* <p>
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* StreamInfoItemHolder.java is part of NewPipe.
|
||||
* <p>
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
|
||||
public final ImageView itemThumbnailView;
|
||||
public final TextView itemVideoTitleView;
|
||||
public final TextView itemUploaderView;
|
||||
public final TextView itemDurationView;
|
||||
public final TextView itemAdditionalDetails;
|
||||
|
||||
LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
|
||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||
itemDurationView = itemView.findViewById(R.id.itemDurationView);
|
||||
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||
}
|
||||
|
||||
public LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_stream_item, parent);
|
||||
}
|
||||
|
||||
private String getStreamInfoDetailLine(final StreamStatisticsEntry entry,
|
||||
final DateFormat dateFormat) {
|
||||
final String watchCount = Localization.shortViewCount(itemBuilder.getContext(),
|
||||
entry.watchCount);
|
||||
final String uploadDate = dateFormat.format(entry.latestAccessDate);
|
||||
final String serviceName = NewPipe.getNameOfService(entry.serviceId);
|
||||
return Localization.concatenateStrings(watchCount, uploadDate, serviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof StreamStatisticsEntry)) return;
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
||||
|
||||
itemVideoTitleView.setText(item.title);
|
||||
itemUploaderView.setText(item.uploader);
|
||||
|
||||
if (item.duration > 0) {
|
||||
itemDurationView.setText(Localization.getDurationString(item.duration));
|
||||
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
itemDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
itemBuilder.getImageLoader().displayImage(item.thumbnailUrl, itemThumbnailView,
|
||||
DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
|
||||
itemView.setLongClickable(true);
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().held(item);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display options for stream thumbnails
|
||||
*/
|
||||
public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS =
|
||||
new DisplayImageOptions.Builder()
|
||||
.cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS)
|
||||
.showImageOnFail(R.drawable.dummy_thumbnail)
|
||||
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
|
||||
.showImageOnLoading(R.drawable.dummy_thumbnail)
|
||||
.build();
|
||||
}
|
|
@ -32,6 +32,7 @@ import io.reactivex.Flowable;
|
|||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
|
@ -169,8 +170,14 @@ public abstract class HistoryFragment<E> extends BaseFragment
|
|||
|
||||
private void clearHistory() {
|
||||
final Collection<E> itemsToDelete = new ArrayList<>(mHistoryAdapter.getItems());
|
||||
disposables.add(delete(itemsToDelete).observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe());
|
||||
|
||||
final Disposable deletion = delete(itemsToDelete)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
final Disposable cleanUp = historyRecordManager.removeOrphanedRecords()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
disposables.addAll(deletion, cleanUp);
|
||||
|
||||
makeSnackbar(R.string.history_cleared);
|
||||
mHistoryAdapter.clear();
|
||||
|
|
|
@ -48,6 +48,14 @@ public class HistoryRecordManager {
|
|||
streamHistoryKey = context.getString(R.string.enable_watch_history_key);
|
||||
}
|
||||
|
||||
public Single<Integer> removeOrphanedRecords() {
|
||||
return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Watch History
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
public Maybe<Long> onViewed(final StreamInfo info) {
|
||||
if (!isStreamHistoryEnabled()) return Maybe.empty();
|
||||
|
||||
|
|
|
@ -55,10 +55,6 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
|
||||
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
|
||||
|
||||
private static final int STATISTICS_HOLDER_TYPE = 0x1000;
|
||||
private static final int LOCAL_PLAYLIST_STREAM_HOLDER_TYPE = 0x1001;
|
||||
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x3000;
|
||||
|
||||
private final InfoItemBuilder infoItemBuilder;
|
||||
private final ArrayList<InfoItem> infoItemList;
|
||||
private boolean useMiniVariant = false;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
|
||||
public abstract class OnInfoItemGesture<T extends InfoItem> {
|
||||
|
@ -11,8 +9,4 @@ public abstract class OnInfoItemGesture<T extends InfoItem> {
|
|||
public void held(T selectedItem) {
|
||||
// Optional gesture
|
||||
}
|
||||
|
||||
public void drag(T selectedItem, RecyclerView.ViewHolder viewHolder) {
|
||||
// Optional gesture
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package org.schabi.newpipe.info_list.stored;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
public class StreamEntityInfoItem extends StreamInfoItem {
|
||||
protected final long streamId;
|
||||
|
||||
public StreamEntityInfoItem(final long streamId, final int serviceId,
|
||||
final String url, final String name, final StreamType type) {
|
||||
super(serviceId, url, name, type);
|
||||
this.streamId = streamId;
|
||||
}
|
||||
|
||||
public long getStreamId() {
|
||||
return streamId;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package org.schabi.newpipe.info_list.stored;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public final class StreamStatisticsInfoItem extends StreamEntityInfoItem {
|
||||
private Date latestAccessDate;
|
||||
private long watchCount;
|
||||
|
||||
public StreamStatisticsInfoItem(final long streamId, final int serviceId,
|
||||
final String url, final String name, final StreamType type) {
|
||||
super(streamId, serviceId, url, name, type);
|
||||
}
|
||||
|
||||
public Date getLatestAccessDate() {
|
||||
return latestAccessDate;
|
||||
}
|
||||
|
||||
public void setLatestAccessDate(Date latestAccessDate) {
|
||||
this.latestAccessDate = latestAccessDate;
|
||||
}
|
||||
|
||||
public long getWatchCount() {
|
||||
return watchCount;
|
||||
}
|
||||
|
||||
public void setWatchCount(long watchCount) {
|
||||
this.watchCount = watchCount;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.PluralsRes;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.text.TextUtils;
|
||||
|
@ -14,7 +15,9 @@ import java.text.DateFormat;
|
|||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/*
|
||||
|
@ -39,9 +42,33 @@ import java.util.Locale;
|
|||
|
||||
public class Localization {
|
||||
|
||||
public final static String DOT_SEPARATOR = " • ";
|
||||
|
||||
private Localization() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String concatenateStrings(final String... strings) {
|
||||
return concatenateStrings(Arrays.asList(strings));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String concatenateStrings(final List<String> strings) {
|
||||
if (strings.isEmpty()) return "";
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append(strings.get(0));
|
||||
|
||||
for (int i = 1; i < strings.size(); i++) {
|
||||
final String string = strings.get(i);
|
||||
if (!TextUtils.isEmpty(string)) {
|
||||
stringBuilder.append(DOT_SEPARATOR).append(strings.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public static Locale getPreferredLocale(Context context) {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
|||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.fragments.local.LocalPlaylistFragment;
|
||||
import org.schabi.newpipe.fragments.local.MostPlayedFragment;
|
||||
import org.schabi.newpipe.fragments.local.WatchHistoryFragment;
|
||||
import org.schabi.newpipe.fragments.local.bookmark.MostPlayedFragment;
|
||||
import org.schabi.newpipe.fragments.local.bookmark.WatchHistoryFragment;
|
||||
import org.schabi.newpipe.history.HistoryActivity;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.BackgroundPlayerActivity;
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique..."/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemUploaderView"
|
||||
android:id="@+id/itemAdditionalDetails"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/itemVideoTitleView"
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_toLeftOf="@id/playlist_stream_count"
|
||||
android:layout_toStartOf="@id/playlist_stream_count"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
|
@ -21,13 +26,13 @@
|
|||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/playlist_detail_title_text_size"
|
||||
tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum" />
|
||||
tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur..." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_stream_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/playlist_title_view"
|
||||
android:layout_alignBottom="@id/playlist_title_view"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="6dp"
|
||||
android:ellipsize="end"
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".player.BackgroundPlayerActivity">
|
||||
|
||||
<item android:id="@+id/action_append_playlist"
|
||||
android:orderInCategory="981"
|
||||
<item
|
||||
android:id="@+id/action_append_playlist"
|
||||
android:icon="?attr/playlist_add"
|
||||
android:title="@string/append_playlist"
|
||||
app:showAsAction="never"/>
|
||||
android:visible="true"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item android:id="@+id/action_settings"
|
||||
android:orderInCategory="990"
|
||||
|
|
|
@ -232,6 +232,7 @@
|
|||
<string name="delete_one">Delete One</string>
|
||||
<string name="delete_all">Delete All</string>
|
||||
<string name="checksum">Checksum</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
|
||||
<!-- Fragment -->
|
||||
<string name="add">New mission</string>
|
||||
|
@ -380,6 +381,8 @@
|
|||
<!-- Local Playlist -->
|
||||
<string name="create_playlist">Create New Playlist</string>
|
||||
<string name="delete_playlist">Delete Playlist</string>
|
||||
<string name="rename_playlist">Rename Playlist</string>
|
||||
<string name="playlist_name_input">Name</string>
|
||||
<string name="append_playlist">Add To Playlist</string>
|
||||
<string name="delete_playlist_prompt">Do you want to delete this playlist?</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue