-Added basic UI for local playlists.
-Added UI for watch history and most played fragments. -Added stream state table for storing playback timestamp and future usage. -Enabled playlist deletion.
This commit is contained in:
parent
38946e4b0f
commit
ba9d0d7707
28 changed files with 1446 additions and 58 deletions
|
@ -14,8 +14,10 @@ import org.schabi.newpipe.database.stream.dao.StreamHistoryDAO;
|
||||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
|
|
||||||
|
@ -23,8 +25,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
@Database(
|
@Database(
|
||||||
entities = {
|
entities = {
|
||||||
SubscriptionEntity.class, WatchHistoryEntry.class, SearchHistoryEntry.class,
|
SubscriptionEntity.class, WatchHistoryEntry.class, SearchHistoryEntry.class,
|
||||||
StreamEntity.class, StreamHistoryEntity.class, PlaylistEntity.class,
|
StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class,
|
||||||
PlaylistStreamEntity.class
|
PlaylistEntity.class, PlaylistStreamEntity.class
|
||||||
},
|
},
|
||||||
version = 1,
|
version = 1,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
|
@ -43,6 +45,8 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public abstract StreamHistoryDAO streamHistoryDAO();
|
public abstract StreamHistoryDAO streamHistoryDAO();
|
||||||
|
|
||||||
|
public abstract StreamStateDAO streamStateDAO();
|
||||||
|
|
||||||
public abstract PlaylistDAO playlistDAO();
|
public abstract PlaylistDAO playlistDAO();
|
||||||
|
|
||||||
public abstract PlaylistStreamDAO playlistStreamDAO();
|
public abstract PlaylistStreamDAO playlistStreamDAO();
|
||||||
|
|
|
@ -32,4 +32,7 @@ public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> {
|
||||||
|
|
||||||
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
public abstract Flowable<List<PlaylistEntity>> getPlaylist(final long playlistId);
|
public abstract Flowable<List<PlaylistEntity>> getPlaylist(final long playlistId);
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract int deletePlaylist(final long playlistId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ import android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
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.extractor.stream.StreamType;
|
||||||
|
import org.schabi.newpipe.info_list.stored.StreamStatisticsInfoItem;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
@ -51,4 +53,15 @@ public class StreamStatisticsEntry {
|
||||||
this.latestAccessDate = latestAccessDate;
|
this.latestAccessDate = latestAccessDate;
|
||||||
this.watchCount = watchCount;
|
this.watchCount = watchCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StreamStatisticsInfoItem toStreamStatisticsInfoItem() {
|
||||||
|
StreamStatisticsInfoItem item =
|
||||||
|
new StreamStatisticsInfoItem(uid, serviceId, url, title, streamType);
|
||||||
|
item.setDuration(duration);
|
||||||
|
item.setUploaderName(uploader);
|
||||||
|
item.setThumbnailUrl(thumbnailUrl);
|
||||||
|
item.setLatestAccessDate(latestAccessDate);
|
||||||
|
item.setWatchCount(watchCount);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.schabi.newpipe.database.stream.dao;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Dao;
|
||||||
|
import android.arch.persistence.room.Query;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.BasicDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class StreamStateDAO implements BasicDAO<StreamStateEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + STREAM_STATE_TABLE)
|
||||||
|
public abstract Flowable<List<StreamStateEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + STREAM_STATE_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<StreamStateEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||||
|
public abstract int deleteState(final long streamId);
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.schabi.newpipe.database.stream.model;
|
||||||
|
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.ForeignKey;
|
||||||
|
|
||||||
|
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||||
|
|
||||||
|
@Entity(tableName = STREAM_STATE_TABLE,
|
||||||
|
primaryKeys = {JOIN_STREAM_ID},
|
||||||
|
foreignKeys = {
|
||||||
|
@ForeignKey(entity = StreamEntity.class,
|
||||||
|
parentColumns = StreamEntity.STREAM_ID,
|
||||||
|
childColumns = JOIN_STREAM_ID,
|
||||||
|
onDelete = CASCADE, onUpdate = CASCADE)
|
||||||
|
})
|
||||||
|
public class StreamStateEntity {
|
||||||
|
final public static String STREAM_STATE_TABLE = "stream_state";
|
||||||
|
final public static String JOIN_STREAM_ID = "stream_id";
|
||||||
|
final public static String STREAM_PROGRESS_TIME = "progress_time";
|
||||||
|
|
||||||
|
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||||
|
private long streamUid;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_PROGRESS_TIME)
|
||||||
|
private long progressTime;
|
||||||
|
|
||||||
|
public StreamStateEntity(long streamUid, long progressTime) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
this.progressTime = progressTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamUid() {
|
||||||
|
return streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamUid(long streamUid) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getProgressTime() {
|
||||||
|
return progressTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgressTime(long progressTime) {
|
||||||
|
this.progressTime = progressTime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||||
import org.schabi.newpipe.fragments.list.feed.FeedFragment;
|
import org.schabi.newpipe.fragments.list.feed.FeedFragment;
|
||||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||||
|
import org.schabi.newpipe.fragments.local.BookmarkFragment;
|
||||||
import org.schabi.newpipe.fragments.subscription.SubscriptionFragment;
|
import org.schabi.newpipe.fragments.subscription.SubscriptionFragment;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
@ -87,9 +88,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
|
|
||||||
if (isSubscriptionsPageOnlySelected()) {
|
if (isSubscriptionsPageOnlySelected()) {
|
||||||
tabLayout.getTabAt(0).setIcon(channelIcon);
|
tabLayout.getTabAt(0).setIcon(channelIcon);
|
||||||
|
tabLayout.getTabAt(1).setText(R.string.tab_bookmarks);
|
||||||
} else {
|
} else {
|
||||||
tabLayout.getTabAt(0).setIcon(whatsHotIcon);
|
tabLayout.getTabAt(0).setIcon(whatsHotIcon);
|
||||||
tabLayout.getTabAt(1).setIcon(channelIcon);
|
tabLayout.getTabAt(1).setIcon(channelIcon);
|
||||||
|
tabLayout.getTabAt(2).setText(R.string.tab_bookmarks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +150,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PagerAdapter extends FragmentPagerAdapter {
|
private class PagerAdapter extends FragmentPagerAdapter {
|
||||||
|
|
||||||
PagerAdapter(FragmentManager fm) {
|
PagerAdapter(FragmentManager fm) {
|
||||||
super(fm);
|
super(fm);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +160,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
case 0:
|
case 0:
|
||||||
return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment();
|
return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment();
|
||||||
case 1:
|
case 1:
|
||||||
return new SubscriptionFragment();
|
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||||
|
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
|
||||||
|
.equals(getString(R.string.subscription_page_key))) {
|
||||||
|
return new BookmarkFragment();
|
||||||
|
} else {
|
||||||
|
return new SubscriptionFragment();
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return new BookmarkFragment();
|
||||||
default:
|
default:
|
||||||
return new BlankFragment();
|
return new BlankFragment();
|
||||||
}
|
}
|
||||||
|
@ -172,7 +182,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return isSubscriptionsPageOnlySelected() ? 1 : 2;
|
return isSubscriptionsPageOnlySelected() ? 2 : 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +197,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getMainPageFragment() {
|
private Fragment getMainPageFragment() {
|
||||||
|
if (getActivity() == null) return new BlankFragment();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SharedPreferences preferences =
|
SharedPreferences preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(getActivity());
|
PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
|
@ -216,6 +228,10 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
ChannelFragment fragment = ChannelFragment.getInstance(serviceId, url, name);
|
ChannelFragment fragment = ChannelFragment.getInstance(serviceId, url, name);
|
||||||
fragment.useAsFrontPage(true);
|
fragment.useAsFrontPage(true);
|
||||||
return fragment;
|
return fragment;
|
||||||
|
} else if (setMainPage.equals(getString(R.string.bookmark_page_key))) {
|
||||||
|
final BookmarkFragment fragment = new BookmarkFragment();
|
||||||
|
fragment.useAsFrontPage(true);
|
||||||
|
return fragment;
|
||||||
} else {
|
} else {
|
||||||
return new BlankFragment();
|
return new BlankFragment();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,318 @@
|
||||||
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
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.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.InfoItemBuilder;
|
||||||
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
|
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||||
|
import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import icepick.State;
|
||||||
|
import io.reactivex.Observer;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
|
public class BookmarkFragment extends BaseStateFragment<List<PlaylistMetadataEntry>> {
|
||||||
|
private View watchHistoryButton;
|
||||||
|
private View mostWatchedButton;
|
||||||
|
|
||||||
|
private InfoListAdapter infoListAdapter;
|
||||||
|
private RecyclerView itemsList;
|
||||||
|
|
||||||
|
@State
|
||||||
|
protected Parcelable itemsListState;
|
||||||
|
|
||||||
|
private Subscription databaseSubscription;
|
||||||
|
private CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
private LocalPlaylistManager localPlaylistManager;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment LifeCycle
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||||
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
|
if(isVisibleToUser && activity != null && activity.getSupportActionBar() != null) {
|
||||||
|
activity.getSupportActionBar().setTitle(R.string.tab_bookmarks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
infoListAdapter = new InfoListAdapter(activity);
|
||||||
|
localPlaylistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
if (activity.getSupportActionBar() != null) {
|
||||||
|
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.setTitle(R.string.tab_bookmarks);
|
||||||
|
if(useAsFrontPage) {
|
||||||
|
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
|
}
|
||||||
|
return inflater.inflate(R.layout.fragment_bookmarks, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
if (disposables != null) disposables.clear();
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (disposables != null) disposables.dispose();
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
|
||||||
|
disposables = null;
|
||||||
|
databaseSubscription = null;
|
||||||
|
localPlaylistManager = null;
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Views
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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));
|
||||||
|
|
||||||
|
final View headerRootLayout = activity.getLayoutInflater()
|
||||||
|
.inflate(R.layout.bookmark_header, itemsList, false);
|
||||||
|
watchHistoryButton = headerRootLayout.findViewById(R.id.watchHistory);
|
||||||
|
mostWatchedButton = headerRootLayout.findViewById(R.id.mostWatched);
|
||||||
|
|
||||||
|
infoListAdapter.setHeader(headerRootLayout);
|
||||||
|
infoListAdapter.useMiniItemVariants(true);
|
||||||
|
|
||||||
|
itemsList.setAdapter(infoListAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initListeners() {
|
||||||
|
super.initListeners();
|
||||||
|
|
||||||
|
infoListAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() {
|
||||||
|
@Override
|
||||||
|
public void selected(PlaylistInfoItem selectedItem) {
|
||||||
|
// Requires the parent fragment to find holder for fragment replacement
|
||||||
|
if (selectedItem instanceof LocalPlaylistInfoItem && getParentFragment() != null) {
|
||||||
|
final long playlistId = ((LocalPlaylistInfoItem) selectedItem).getPlaylistId();
|
||||||
|
|
||||||
|
NavigationHelper.openLocalPlaylistFragment(
|
||||||
|
getParentFragment().getFragmentManager(),
|
||||||
|
playlistId,
|
||||||
|
selectedItem.getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void held(PlaylistInfoItem selectedItem) {
|
||||||
|
if (selectedItem instanceof LocalPlaylistInfoItem) {
|
||||||
|
showPlaylistDialog((LocalPlaylistInfoItem) selectedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watchHistoryButton.setOnClickListener(view -> {
|
||||||
|
if (getParentFragment() != null) {
|
||||||
|
NavigationHelper.openWatchHistoryFragment(getParentFragment().getFragmentManager());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mostWatchedButton.setOnClickListener(view -> {
|
||||||
|
if (getParentFragment() != null) {
|
||||||
|
NavigationHelper.openMostPlayedFragment(getParentFragment().getFragmentManager());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
final Toast deleteSuccessful =
|
||||||
|
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT);
|
||||||
|
disposables.add(localPlaylistManager.deletePlaylist(item.getPlaylistId())
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetFragment() {
|
||||||
|
if (disposables != null) disposables.clear();
|
||||||
|
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Subscriptions Loader
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startLoading(boolean forceLoad) {
|
||||||
|
super.startLoading(forceLoad);
|
||||||
|
resetFragment();
|
||||||
|
|
||||||
|
localPlaylistManager.getPlaylists()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(getSubscriptionSubscriber());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Subscriber<List<PlaylistMetadataEntry>> getSubscriptionSubscriber() {
|
||||||
|
return new Subscriber<List<PlaylistMetadataEntry>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Subscription s) {
|
||||||
|
showLoading();
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
databaseSubscription = s;
|
||||||
|
databaseSubscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(List<PlaylistMetadataEntry> subscriptions) {
|
||||||
|
handleResult(subscriptions);
|
||||||
|
if (databaseSubscription != null) databaseSubscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable exception) {
|
||||||
|
BookmarkFragment.this.onError(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResult(@NonNull List<PlaylistMetadataEntry> result) {
|
||||||
|
super.handleResult(result);
|
||||||
|
|
||||||
|
infoListAdapter.clearStreamItemList();
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
showEmptyState();
|
||||||
|
} else {
|
||||||
|
infoListAdapter.addInfoItemList(infoItemsOf(result));
|
||||||
|
if (itemsListState != null) {
|
||||||
|
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||||
|
itemsListState = null;
|
||||||
|
}
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Contract
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showLoading() {
|
||||||
|
super.showLoading();
|
||||||
|
animateView(itemsList, false, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideLoading() {
|
||||||
|
super.hideLoading();
|
||||||
|
animateView(itemsList, true, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showEmptyState() {
|
||||||
|
super.showEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Error Handling
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onError(Throwable exception) {
|
||||||
|
resetFragment();
|
||||||
|
if (super.onError(exception)) return true;
|
||||||
|
|
||||||
|
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
||||||
|
"none", "Bookmark", R.string.general_error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.reactivestreams.Subscriber;
|
||||||
|
import org.reactivestreams.Subscription;
|
||||||
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
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.info_list.InfoItemBuilder;
|
||||||
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
|
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;
|
||||||
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import icepick.State;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
|
public abstract class HistoryPlaylistFragment
|
||||||
|
extends BaseListFragment<List<StreamStatisticsEntry>, Void> {
|
||||||
|
|
||||||
|
private View headerRootLayout;
|
||||||
|
private View playlistControl;
|
||||||
|
private View headerPlayAllButton;
|
||||||
|
private View headerPopupButton;
|
||||||
|
private View headerBackgroundButton;
|
||||||
|
|
||||||
|
@State
|
||||||
|
protected Parcelable itemsListState;
|
||||||
|
|
||||||
|
/* Used for independent events */
|
||||||
|
private Subscription databaseSubscription;
|
||||||
|
private StreamRecordManager recordManager;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Abstracts
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
protected abstract String getName();
|
||||||
|
|
||||||
|
protected abstract List<InfoItem> processResult(final List<StreamStatisticsEntry> results);
|
||||||
|
|
||||||
|
protected abstract String getAdditionalDetail(final StreamStatisticsInfoItem infoItem);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment LifeCycle
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
recordManager = new StreamRecordManager(NewPipeDatabase.getInstance(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
databaseSubscription = null;
|
||||||
|
recordManager = null;
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Views
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||||
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
infoListAdapter.useMiniItemVariants(true);
|
||||||
|
|
||||||
|
setFragmentTitle(getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getListHeader() {
|
||||||
|
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_control,
|
||||||
|
itemsList, false);
|
||||||
|
playlistControl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||||
|
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||||
|
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||||
|
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
|
||||||
|
|
||||||
|
return headerRootLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initListeners() {
|
||||||
|
super.initListeners();
|
||||||
|
|
||||||
|
infoListAdapter.setOnStreamSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<StreamInfoItem>() {
|
||||||
|
@Override
|
||||||
|
public void selected(StreamInfoItem selectedItem) {
|
||||||
|
if (getParentFragment() == null) return;
|
||||||
|
// Requires the parent fragment to find holder for fragment replacement
|
||||||
|
NavigationHelper.openVideoDetailFragment(getParentFragment().getFragmentManager(),
|
||||||
|
selectedItem.getServiceId(), selectedItem.url, selectedItem.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void held(StreamInfoItem selectedItem) {
|
||||||
|
showStreamDialog(selectedItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void showStreamDialog(final StreamInfoItem item) {
|
||||||
|
final Context context = getContext();
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
if (context == null || context.getResources() == null
|
||||||
|
|| getActivity() == null || !(item instanceof StreamStatisticsInfoItem)) return;
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
|
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||||
|
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final String detail = getAdditionalDetail((StreamStatisticsInfoItem) item);
|
||||||
|
new InfoItemDialog(getActivity(), commands, actions, item.getName(), detail).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetFragment() {
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Loader
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showLoading() {
|
||||||
|
super.showLoading();
|
||||||
|
animateView(headerRootLayout, false, 200);
|
||||||
|
animateView(itemsList, false, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startLoading(boolean forceLoad) {
|
||||||
|
super.startLoading(forceLoad);
|
||||||
|
resetFragment();
|
||||||
|
|
||||||
|
recordManager.getStatistics()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(getHistoryObserver());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() {
|
||||||
|
return new Subscriber<List<StreamStatisticsEntry>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Subscription s) {
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
databaseSubscription = s;
|
||||||
|
databaseSubscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(List<StreamStatisticsEntry> streams) {
|
||||||
|
handleResult(streams);
|
||||||
|
if (databaseSubscription != null) databaseSubscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable exception) {
|
||||||
|
HistoryPlaylistFragment.this.onError(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResult(@NonNull List<StreamStatisticsEntry> result) {
|
||||||
|
super.handleResult(result);
|
||||||
|
infoListAdapter.clearStreamItemList();
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
showEmptyState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animateView(headerRootLayout, true, 100);
|
||||||
|
animateView(itemsList, true, 300);
|
||||||
|
|
||||||
|
infoListAdapter.addInfoItemList(processResult(result));
|
||||||
|
if (itemsListState != null) {
|
||||||
|
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||||
|
itemsListState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
playlistControl.setVisibility(View.VISIBLE);
|
||||||
|
headerPlayAllButton.setOnClickListener(view ->
|
||||||
|
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||||
|
headerPopupButton.setOnClickListener(view ->
|
||||||
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||||
|
headerBackgroundButton.setOnClickListener(view ->
|
||||||
|
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Contract
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loadMoreItems() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean hasMoreItems() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Error Handling
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onError(Throwable exception) {
|
||||||
|
resetFragment();
|
||||||
|
if (super.onError(exception)) return true;
|
||||||
|
|
||||||
|
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
||||||
|
"none", "History", R.string.general_error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
protected void setFragmentTitle(final String title) {
|
||||||
|
if (activity.getSupportActionBar() != null) {
|
||||||
|
activity.getSupportActionBar().setTitle(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayQueue getPlayQueue() {
|
||||||
|
return getPlayQueue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayQueue getPlayQueue(final int index) {
|
||||||
|
final List<InfoItem> infoItems = infoListAdapter.getItemsList();
|
||||||
|
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||||
|
for (final InfoItem item : infoItems) {
|
||||||
|
if (item instanceof StreamInfoItem) streamInfoItems.add((StreamInfoItem) item);
|
||||||
|
}
|
||||||
|
return new SinglePlayQueue(streamInfoItems, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,356 @@
|
||||||
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
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.extractor.stream.StreamInfoItem;
|
||||||
|
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||||
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
|
import org.schabi.newpipe.playlist.PlayQueue;
|
||||||
|
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import icepick.State;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
|
public class LocalPlaylistFragment extends BaseListFragment<List<StreamEntity>, Void> {
|
||||||
|
|
||||||
|
private View headerRootLayout;
|
||||||
|
private TextView headerTitleView;
|
||||||
|
private TextView headerStreamCount;
|
||||||
|
private View playlistControl;
|
||||||
|
|
||||||
|
private View headerPlayAllButton;
|
||||||
|
private View headerPopupButton;
|
||||||
|
private View headerBackgroundButton;
|
||||||
|
|
||||||
|
@State
|
||||||
|
protected long playlistId;
|
||||||
|
@State
|
||||||
|
protected String name;
|
||||||
|
@State
|
||||||
|
protected Parcelable itemsListState;
|
||||||
|
|
||||||
|
/* Used for independent events */
|
||||||
|
private CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
private Subscription databaseSubscription;
|
||||||
|
private LocalPlaylistManager playlistManager;
|
||||||
|
|
||||||
|
public static LocalPlaylistFragment getInstance(long playlistId, String name) {
|
||||||
|
LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
||||||
|
instance.setInitialData(playlistId, name);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment LifeCycle
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
if (disposables != null) disposables.clear();
|
||||||
|
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (disposables != null) disposables.dispose();
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
|
||||||
|
disposables = null;
|
||||||
|
databaseSubscription = null;
|
||||||
|
playlistManager = null;
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Views
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||||
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
infoListAdapter.useMiniItemVariants(true);
|
||||||
|
|
||||||
|
setFragmentTitle(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getListHeader() {
|
||||||
|
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.local_playlist_header,
|
||||||
|
itemsList, false);
|
||||||
|
|
||||||
|
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
|
||||||
|
headerTitleView.setSelected(true);
|
||||||
|
|
||||||
|
headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count);
|
||||||
|
playlistControl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||||
|
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||||
|
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||||
|
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
|
||||||
|
|
||||||
|
return headerRootLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initListeners() {
|
||||||
|
super.initListeners();
|
||||||
|
|
||||||
|
infoListAdapter.setOnStreamSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<StreamInfoItem>() {
|
||||||
|
@Override
|
||||||
|
public void selected(StreamInfoItem selectedItem) {
|
||||||
|
if (getParentFragment() == null) return;
|
||||||
|
// Requires the parent fragment to find holder for fragment replacement
|
||||||
|
NavigationHelper.openVideoDetailFragment(getParentFragment().getFragmentManager(),
|
||||||
|
selectedItem.getServiceId(), selectedItem.url, selectedItem.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void held(StreamInfoItem selectedItem) {
|
||||||
|
showStreamDialog(selectedItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void showStreamDialog(final StreamInfoItem item) {
|
||||||
|
final Context context = getContext();
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
|
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||||
|
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetFragment() {
|
||||||
|
if (disposables != null) disposables.clear();
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Loader
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showLoading() {
|
||||||
|
super.showLoading();
|
||||||
|
animateView(headerRootLayout, false, 200);
|
||||||
|
animateView(itemsList, false, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startLoading(boolean forceLoad) {
|
||||||
|
super.startLoading(forceLoad);
|
||||||
|
resetFragment();
|
||||||
|
|
||||||
|
playlistManager.getPlaylist(playlistId)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(getPlaylistObserver());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Subscriber<List<StreamEntity>> getPlaylistObserver() {
|
||||||
|
return new Subscriber<List<StreamEntity>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Subscription s) {
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
if (databaseSubscription != null) databaseSubscription.cancel();
|
||||||
|
databaseSubscription = s;
|
||||||
|
databaseSubscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(List<StreamEntity> streams) {
|
||||||
|
handleResult(streams);
|
||||||
|
if (databaseSubscription != null) databaseSubscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable exception) {
|
||||||
|
LocalPlaylistFragment.this.onError(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResult(@NonNull List<StreamEntity> result) {
|
||||||
|
super.handleResult(result);
|
||||||
|
infoListAdapter.clearStreamItemList();
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
showEmptyState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animateView(headerRootLayout, true, 100);
|
||||||
|
animateView(itemsList, true, 300);
|
||||||
|
|
||||||
|
infoListAdapter.addInfoItemList(getStreamItems(result));
|
||||||
|
if (itemsListState != null) {
|
||||||
|
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||||
|
itemsListState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
playlistControl.setVisibility(View.VISIBLE);
|
||||||
|
headerStreamCount.setText(
|
||||||
|
getResources().getQuantityString(R.plurals.videos, result.size(), result.size()));
|
||||||
|
|
||||||
|
headerPlayAllButton.setOnClickListener(view ->
|
||||||
|
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||||
|
headerPopupButton.setOnClickListener(view ->
|
||||||
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||||
|
headerBackgroundButton.setOnClickListener(view ->
|
||||||
|
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<InfoItem> getStreamItems(final List<StreamEntity> streams) {
|
||||||
|
List<InfoItem> items = new ArrayList<>(streams.size());
|
||||||
|
for (final StreamEntity stream : streams) {
|
||||||
|
items.add(stream.toStreamInfoItem());
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Contract
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void loadMoreItems() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean hasMoreItems() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Error Handling
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onError(Throwable exception) {
|
||||||
|
resetFragment();
|
||||||
|
if (super.onError(exception)) return true;
|
||||||
|
|
||||||
|
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
||||||
|
"none", "Subscriptions", R.string.general_error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
protected 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) {
|
||||||
|
activity.getSupportActionBar().setTitle(title);
|
||||||
|
}
|
||||||
|
if (headerTitleView != null) {
|
||||||
|
headerTitleView.setText(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayQueue getPlayQueue() {
|
||||||
|
return getPlayQueue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayQueue getPlayQueue(final int index) {
|
||||||
|
final List<InfoItem> infoItems = infoListAdapter.getItemsList();
|
||||||
|
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||||
|
for (final InfoItem item : infoItems) {
|
||||||
|
if (item instanceof StreamInfoItem) streamInfoItems.add((StreamInfoItem) item);
|
||||||
|
}
|
||||||
|
return new SinglePlayQueue(streamInfoItems, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.fragments.playlist;
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.AppDatabase;
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
|
@ -13,8 +13,9 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.Flowable;
|
||||||
import io.reactivex.Maybe;
|
import io.reactivex.Maybe;
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
public class LocalPlaylistManager {
|
public class LocalPlaylistManager {
|
||||||
|
@ -74,9 +75,16 @@ public class LocalPlaylistManager {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Maybe<List<PlaylistMetadataEntry>> getPlaylists() {
|
public Flowable<List<PlaylistMetadataEntry>> getPlaylists() {
|
||||||
return playlistStreamTable.getPlaylistMetadata()
|
return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
|
||||||
.firstElement()
|
}
|
||||||
|
|
||||||
|
public Flowable<List<StreamEntity>> getPlaylist(final long playlistId) {
|
||||||
|
return playlistStreamTable.getOrderedStreamsOf(playlistId).subscribeOn(Schedulers.io());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<Integer> deletePlaylist(final long playlistId) {
|
||||||
|
return Single.fromCallable(() -> playlistTable.deletePlaylist(playlistId))
|
||||||
.subscribeOn(Schedulers.io());
|
.subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
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 HistoryPlaylistFragment {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.fragments.playlist;
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -113,6 +113,7 @@ public class PlaylistAppendDialog extends DialogFragment {
|
||||||
.subscribe(metadataEntries -> {
|
.subscribe(metadataEntries -> {
|
||||||
if (metadataEntries.isEmpty()) {
|
if (metadataEntries.isEmpty()) {
|
||||||
openCreatePlaylistDialog();
|
openCreatePlaylistDialog();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<InfoItem> playlistInfoItems = new ArrayList<>(metadataEntries.size());
|
List<InfoItem> playlistInfoItems = new ArrayList<>(metadataEntries.size());
|
||||||
|
@ -123,8 +124,6 @@ public class PlaylistAppendDialog extends DialogFragment {
|
||||||
playlistAdapter.clearStreamItemList();
|
playlistAdapter.clearStreamItemList();
|
||||||
playlistAdapter.addInfoItemList(playlistInfoItems);
|
playlistAdapter.addInfoItemList(playlistInfoItems);
|
||||||
playlistRecyclerView.setVisibility(View.VISIBLE);
|
playlistRecyclerView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
getDialog().setCanceledOnTouchOutside(true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +140,7 @@ public class PlaylistAppendDialog extends DialogFragment {
|
||||||
public void openCreatePlaylistDialog() {
|
public void openCreatePlaylistDialog() {
|
||||||
if (streamInfo == null || getFragmentManager() == null) return;
|
if (streamInfo == null || getFragmentManager() == null) return;
|
||||||
|
|
||||||
getDialog().dismiss();
|
|
||||||
PlaylistCreationDialog.newInstance(streamInfo).show(getFragmentManager(), TAG);
|
PlaylistCreationDialog.newInstance(streamInfo).show(getFragmentManager(), TAG);
|
||||||
|
getDialog().dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.fragments.playlist;
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.fragments.playlist;
|
package org.schabi.newpipe.fragments.local;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.AppDatabase;
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||||
|
@ -7,14 +7,12 @@ import org.schabi.newpipe.database.stream.dao.StreamHistoryDAO;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.MaybeObserver;
|
import io.reactivex.Flowable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
public class StreamRecordManager {
|
public class StreamRecordManager {
|
||||||
|
@ -29,11 +27,6 @@ public class StreamRecordManager {
|
||||||
historyTable = db.streamHistoryDAO();
|
historyTable = db.streamHistoryDAO();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int onChanged(final StreamInfoItem infoItem) {
|
|
||||||
// Only existing streams are updated
|
|
||||||
return streamTable.update(new StreamEntity(infoItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Single<Long> onViewed(final StreamInfo info) {
|
public Single<Long> onViewed(final StreamInfo info) {
|
||||||
return Single.fromCallable(() -> database.runInTransaction(() -> {
|
return Single.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||||
|
@ -45,30 +38,7 @@ public class StreamRecordManager {
|
||||||
return historyTable.deleteHistory(streamId);
|
return historyTable.deleteHistory(streamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeRecord() {
|
public Flowable<List<StreamStatisticsEntry>> getStatistics() {
|
||||||
historyTable.getStatistics().firstElement().subscribe(
|
return historyTable.getStatistics();
|
||||||
new MaybeObserver<List<StreamStatisticsEntry>>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSubscribe(Disposable d) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<StreamStatisticsEntry> streamStatisticsEntries) {
|
|
||||||
hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onComplete() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
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 HistoryPlaylistFragment {
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,14 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemBuilder.getOnPlaylistSelectedListener().selected(item);
|
itemBuilder.getOnPlaylistSelectedListener().selected(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
itemView.setLongClickable(true);
|
||||||
|
itemView.setOnLongClickListener(view -> {
|
||||||
|
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
|
||||||
|
itemBuilder.getOnPlaylistSelectedListener().held(item);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -64,7 +64,7 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.fragments.playlist.StreamRecordManager;
|
import org.schabi.newpipe.fragments.local.StreamRecordManager;
|
||||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||||
import org.schabi.newpipe.player.helper.CacheFactory;
|
import org.schabi.newpipe.player.helper.CacheFactory;
|
||||||
import org.schabi.newpipe.player.helper.LoadController;
|
import org.schabi.newpipe.player.helper.LoadController;
|
||||||
|
@ -676,7 +676,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).subscribe());
|
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).subscribe());
|
||||||
recordManager.removeRecord();
|
|
||||||
initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
|
initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,29 @@ package org.schabi.newpipe.playlist;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class SinglePlayQueue extends PlayQueue {
|
public final class SinglePlayQueue extends PlayQueue {
|
||||||
public SinglePlayQueue(final StreamInfoItem item) {
|
public SinglePlayQueue(final StreamInfoItem item) {
|
||||||
this(new PlayQueueItem(item));
|
super(0, Collections.singletonList(new PlayQueueItem(item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SinglePlayQueue(final StreamInfo info) {
|
public SinglePlayQueue(final StreamInfo info) {
|
||||||
this(new PlayQueueItem(info));
|
super(0, Collections.singletonList(new PlayQueueItem(info)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SinglePlayQueue(final PlayQueueItem playQueueItem) {
|
public SinglePlayQueue(final List<StreamInfoItem> items, final int index) {
|
||||||
super(0, Collections.singletonList(playQueueItem));
|
super(index, playQueueItemsOf(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PlayQueueItem> playQueueItemsOf(List<StreamInfoItem> items) {
|
||||||
|
List<PlayQueueItem> playQueueItems = new ArrayList<>(items.size());
|
||||||
|
for (final StreamInfoItem item : items) {
|
||||||
|
playQueueItems.add(new PlayQueueItem(item));
|
||||||
|
}
|
||||||
|
return playQueueItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -34,6 +34,9 @@ import org.schabi.newpipe.fragments.list.feed.FeedFragment;
|
||||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
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.history.HistoryActivity;
|
import org.schabi.newpipe.history.HistoryActivity;
|
||||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||||
import org.schabi.newpipe.player.BackgroundPlayerActivity;
|
import org.schabi.newpipe.player.BackgroundPlayerActivity;
|
||||||
|
@ -323,6 +326,30 @@ public class NavigationHelper {
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void openLocalPlaylistFragment(FragmentManager fragmentManager, long playlistId, String name) {
|
||||||
|
if (name == null) name = "";
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||||
|
.replace(R.id.fragment_holder, LocalPlaylistFragment.getInstance(playlistId, name))
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openWatchHistoryFragment(FragmentManager fragmentManager) {
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||||
|
.replace(R.id.fragment_holder, new WatchHistoryFragment())
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openMostPlayedFragment(FragmentManager fragmentManager) {
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||||
|
.replace(R.id.fragment_holder, new MostPlayedFragment())
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Through Intents
|
// Through Intents
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
81
app/src/main/res/layout/bookmark_header.xml
Normal file
81
app/src/main/res/layout/bookmark_header.xml
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/watchHistory"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/watchHistoryIcon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
android:src="?attr/history"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/watchHistoryText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_toRightOf="@+id/watchHistoryIcon"
|
||||||
|
android:gravity="left|center"
|
||||||
|
android:text="@string/title_watch_history"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="RtlHardcoded"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/mostWatched"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/watchHistory"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/mostWatchedIcon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
android:src="?attr/filter"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mostWatchedText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_toRightOf="@+id/mostWatchedIcon"
|
||||||
|
android:gravity="left|center"
|
||||||
|
android:text="@string/title_most_played"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="RtlHardcoded"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_below="@+id/mostWatched"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:background="?attr/separator_color"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
44
app/src/main/res/layout/fragment_bookmarks.xml
Normal file
44
app/src/main/res/layout/fragment_bookmarks.xml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/items_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/list_playlist_mini_item"/>
|
||||||
|
|
||||||
|
<!--ERROR PANEL-->
|
||||||
|
<include
|
||||||
|
android:id="@+id/error_panel"
|
||||||
|
layout="@layout/error_retry"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="50dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/empty_state_view"
|
||||||
|
layout="@layout/list_empty_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginTop="50dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:background="?attr/toolbar_shadow_drawable"
|
||||||
|
android:layout_alignParentTop="true"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/empty_state_view"
|
android:id="@+id/empty_state_view"
|
||||||
layout="@layout/subscription_feed_empty_view"
|
layout="@layout/list_empty_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/empty_state_view"
|
android:id="@+id/empty_state_view"
|
||||||
layout="@layout/subscription_feed_empty_view"
|
layout="@layout/list_empty_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
|
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
|
||||||
android:contentDescription="@string/list_thumbnail_view_description"
|
android:contentDescription="@string/list_thumbnail_view_description"
|
||||||
android:scaleType="fitEnd"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/dummy_thumbnail_playlist"
|
android:src="@drawable/dummy_thumbnail_playlist"
|
||||||
tools:ignore="RtlHardcoded"/>
|
tools:ignore="RtlHardcoded"/>
|
||||||
|
|
||||||
|
|
48
app/src/main/res/layout/local_playlist_header.xml
Normal file
48
app/src/main/res/layout/local_playlist_header.xml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/contrast_background_color">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/playlist_title_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fadingEdge="horizontal"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:scrollHorizontally="true"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
<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_alignParentRight="true"
|
||||||
|
android:layout_marginRight="6dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="right|center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="@dimen/playlist_detail_subtext_size"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="234 videos"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/playlist_stream_count">
|
||||||
|
|
||||||
|
<include layout="@layout/playlist_control"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -119,12 +119,14 @@
|
||||||
<string name="subscription_page_key" translatable="false">subscription_page_key</string>
|
<string name="subscription_page_key" translatable="false">subscription_page_key</string>
|
||||||
<string name="kiosk_page_key" translatable="false">kiosk_page</string>
|
<string name="kiosk_page_key" translatable="false">kiosk_page</string>
|
||||||
<string name="channel_page_key" translatable="false">channel_page</string>
|
<string name="channel_page_key" translatable="false">channel_page</string>
|
||||||
|
<string name="bookmark_page_key" translatable="false">bookmark_page</string>
|
||||||
<string-array name="main_page_content_pages" translatable="false">
|
<string-array name="main_page_content_pages" translatable="false">
|
||||||
<item>@string/blank_page_key</item>
|
<item>@string/blank_page_key</item>
|
||||||
<item>@string/kiosk_page_key</item>
|
<item>@string/kiosk_page_key</item>
|
||||||
<item>@string/feed_page_key</item>
|
<item>@string/feed_page_key</item>
|
||||||
<item>@string/subscription_page_key</item>
|
<item>@string/subscription_page_key</item>
|
||||||
<item>@string/channel_page_key</item>
|
<item>@string/channel_page_key</item>
|
||||||
|
<item>@string/bookmark_page_key</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="main_page_selected_service" translatable="false">main_page_selected_service</string>
|
<string name="main_page_selected_service" translatable="false">main_page_selected_service</string>
|
||||||
<string name="main_page_selected_channel_name" translatable="false">main_page_selected_channel_name</string>
|
<string name="main_page_selected_channel_name" translatable="false">main_page_selected_channel_name</string>
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
<string name="tab_main">Main</string>
|
<string name="tab_main">Main</string>
|
||||||
<string name="tab_subscriptions">Subscriptions</string>
|
<string name="tab_subscriptions">Subscriptions</string>
|
||||||
|
<string name="tab_bookmarks">Bookmarks</string>
|
||||||
|
|
||||||
<string name="fragment_whats_new">What\'s New</string>
|
<string name="fragment_whats_new">What\'s New</string>
|
||||||
|
|
||||||
|
@ -304,6 +305,8 @@
|
||||||
<string name="history_cleared">History cleared</string>
|
<string name="history_cleared">History cleared</string>
|
||||||
<string name="item_deleted">Item deleted</string>
|
<string name="item_deleted">Item deleted</string>
|
||||||
<string name="delete_item_search_history">Do you want to delete this item from search history?</string>
|
<string name="delete_item_search_history">Do you want to delete this item from search history?</string>
|
||||||
|
<string name="title_watch_history">Watch History</string>
|
||||||
|
<string name="title_most_played">Most Played</string>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<string name="main_page_content">Content of main page</string>
|
<string name="main_page_content">Content of main page</string>
|
||||||
|
@ -370,5 +373,6 @@
|
||||||
|
|
||||||
<!-- Local Playlist -->
|
<!-- Local Playlist -->
|
||||||
<string name="create_playlist">Create New Playlist</string>
|
<string name="create_playlist">Create New Playlist</string>
|
||||||
|
<string name="delete_playlist">Delete Playlist</string>
|
||||||
<string name="playlist_name_input">Name</string>
|
<string name="playlist_name_input">Name</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue