mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	-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:
		| @@ -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.playlist.model.PlaylistEntity; | ||||
| 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.StreamEntity; | ||||
| import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionDAO; | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionEntity; | ||||
|  | ||||
| @@ -23,8 +25,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity; | ||||
| @Database( | ||||
|         entities = { | ||||
|                 SubscriptionEntity.class, WatchHistoryEntry.class, SearchHistoryEntry.class, | ||||
|                 StreamEntity.class, StreamHistoryEntity.class, PlaylistEntity.class, | ||||
|                 PlaylistStreamEntity.class | ||||
|                 StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class, | ||||
|                 PlaylistEntity.class, PlaylistStreamEntity.class | ||||
|         }, | ||||
|         version = 1, | ||||
|         exportSchema = false | ||||
| @@ -43,6 +45,8 @@ public abstract class AppDatabase extends RoomDatabase { | ||||
|  | ||||
|     public abstract StreamHistoryDAO streamHistoryDAO(); | ||||
|  | ||||
|     public abstract StreamStateDAO streamStateDAO(); | ||||
|  | ||||
|     public abstract PlaylistDAO playlistDAO(); | ||||
|  | ||||
|     public abstract PlaylistStreamDAO playlistStreamDAO(); | ||||
|   | ||||
| @@ -32,4 +32,7 @@ public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> { | ||||
|  | ||||
|     @Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :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.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; | ||||
|  | ||||
| @@ -51,4 +53,15 @@ public class StreamStatisticsEntry { | ||||
|         this.latestAccessDate = latestAccessDate; | ||||
|         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.feed.FeedFragment; | ||||
| 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.report.ErrorActivity; | ||||
| import org.schabi.newpipe.report.UserAction; | ||||
| @@ -87,9 +88,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | ||||
|  | ||||
|         if (isSubscriptionsPageOnlySelected()) { | ||||
|             tabLayout.getTabAt(0).setIcon(channelIcon); | ||||
|             tabLayout.getTabAt(1).setText(R.string.tab_bookmarks); | ||||
|         } else { | ||||
|             tabLayout.getTabAt(0).setIcon(whatsHotIcon); | ||||
|             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 { | ||||
|  | ||||
|         PagerAdapter(FragmentManager fm) { | ||||
|             super(fm); | ||||
|         } | ||||
| @@ -158,7 +160,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | ||||
|                 case 0: | ||||
|                     return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment(); | ||||
|                 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: | ||||
|                     return new BlankFragment(); | ||||
|             } | ||||
| @@ -172,7 +182,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | ||||
|  | ||||
|         @Override | ||||
|         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() { | ||||
|         if (getActivity() == null) return new BlankFragment(); | ||||
|  | ||||
|         try { | ||||
|             SharedPreferences preferences = | ||||
|                     PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
| @@ -216,6 +228,10 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | ||||
|                 ChannelFragment fragment = ChannelFragment.getInstance(serviceId, url, name); | ||||
|                 fragment.useAsFrontPage(true); | ||||
|                 return fragment; | ||||
|             } else if (setMainPage.equals(getString(R.string.bookmark_page_key))) { | ||||
|                 final BookmarkFragment fragment = new BookmarkFragment(); | ||||
|                 fragment.useAsFrontPage(true); | ||||
|                 return fragment; | ||||
|             } else { | ||||
|                 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.playlist.PlaylistMetadataEntry; | ||||
| @@ -13,8 +13,9 @@ import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import io.reactivex.Completable; | ||||
| import io.reactivex.Flowable; | ||||
| import io.reactivex.Maybe; | ||||
| import io.reactivex.Scheduler; | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| 
 | ||||
| public class LocalPlaylistManager { | ||||
| @@ -74,9 +75,16 @@ public class LocalPlaylistManager { | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     public Maybe<List<PlaylistMetadataEntry>> getPlaylists() { | ||||
|         return playlistStreamTable.getPlaylistMetadata() | ||||
|                 .firstElement() | ||||
|     public Flowable<List<PlaylistMetadataEntry>> getPlaylists() { | ||||
|         return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io()); | ||||
|     } | ||||
| 
 | ||||
|     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()); | ||||
|     } | ||||
| } | ||||
| @@ -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.os.Bundle; | ||||
| @@ -113,6 +113,7 @@ public class PlaylistAppendDialog extends DialogFragment { | ||||
|                 .subscribe(metadataEntries -> { | ||||
|                     if (metadataEntries.isEmpty()) { | ||||
|                         openCreatePlaylistDialog(); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     List<InfoItem> playlistInfoItems = new ArrayList<>(metadataEntries.size()); | ||||
| @@ -123,8 +124,6 @@ public class PlaylistAppendDialog extends DialogFragment { | ||||
|                     playlistAdapter.clearStreamItemList(); | ||||
|                     playlistAdapter.addInfoItemList(playlistInfoItems); | ||||
|                     playlistRecyclerView.setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|                     getDialog().setCanceledOnTouchOutside(true); | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
| @@ -141,7 +140,7 @@ public class PlaylistAppendDialog extends DialogFragment { | ||||
|     public void openCreatePlaylistDialog() { | ||||
|         if (streamInfo == null || getFragmentManager() == null) return; | ||||
| 
 | ||||
|         getDialog().dismiss(); | ||||
|         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.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.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.StreamHistoryEntity; | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import io.reactivex.MaybeObserver; | ||||
| import io.reactivex.Flowable; | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.disposables.Disposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| 
 | ||||
| public class StreamRecordManager { | ||||
| @@ -29,11 +27,6 @@ public class StreamRecordManager { | ||||
|         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) { | ||||
|         return Single.fromCallable(() -> database.runInTransaction(() -> { | ||||
|             final long streamId = streamTable.upsert(new StreamEntity(info)); | ||||
| @@ -45,30 +38,7 @@ public class StreamRecordManager { | ||||
|         return historyTable.deleteHistory(streamId); | ||||
|     } | ||||
| 
 | ||||
|     public void removeRecord() { | ||||
|         historyTable.getStatistics().firstElement().subscribe( | ||||
|                 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() { | ||||
| 
 | ||||
|                     } | ||||
|                 } | ||||
|         ); | ||||
|     public Flowable<List<StreamStatisticsEntry>> getStatistics() { | ||||
|         return historyTable.getStatistics(); | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         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.R; | ||||
| 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.CacheFactory; | ||||
| 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()); | ||||
|         recordManager.removeRecord(); | ||||
|         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.StreamInfoItem; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| public final class SinglePlayQueue extends PlayQueue { | ||||
|     public SinglePlayQueue(final StreamInfoItem item) { | ||||
|         this(new PlayQueueItem(item)); | ||||
|         super(0, Collections.singletonList(new PlayQueueItem(item))); | ||||
|     } | ||||
|  | ||||
|     public SinglePlayQueue(final StreamInfo info) { | ||||
|         this(new PlayQueueItem(info)); | ||||
|         super(0, Collections.singletonList(new PlayQueueItem(info))); | ||||
|     } | ||||
|  | ||||
|     private SinglePlayQueue(final PlayQueueItem playQueueItem) { | ||||
|         super(0, Collections.singletonList(playQueueItem)); | ||||
|     public SinglePlayQueue(final List<StreamInfoItem> items, final int index) { | ||||
|         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 | ||||
|   | ||||
| @@ -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.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.history.HistoryActivity; | ||||
| import org.schabi.newpipe.player.BackgroundPlayer; | ||||
| import org.schabi.newpipe.player.BackgroundPlayerActivity; | ||||
| @@ -323,6 +326,30 @@ public class NavigationHelper { | ||||
|                 .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 | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|   | ||||
							
								
								
									
										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 | ||||
|         android:id="@+id/empty_state_view" | ||||
|         layout="@layout/subscription_feed_empty_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|  | ||||
|     <include | ||||
|         android:id="@+id/empty_state_view" | ||||
|         layout="@layout/subscription_feed_empty_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|         android:layout_alignParentTop="true" | ||||
|         android:layout_marginRight="@dimen/video_item_search_image_right_margin" | ||||
|         android:contentDescription="@string/list_thumbnail_view_description" | ||||
|         android:scaleType="fitEnd" | ||||
|         android:scaleType="centerCrop" | ||||
|         android:src="@drawable/dummy_thumbnail_playlist" | ||||
|         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="kiosk_page_key" translatable="false">kiosk_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"> | ||||
|         <item>@string/blank_page_key</item> | ||||
|         <item>@string/kiosk_page_key</item> | ||||
|         <item>@string/feed_page_key</item> | ||||
|         <item>@string/subscription_page_key</item> | ||||
|         <item>@string/channel_page_key</item> | ||||
|         <item>@string/bookmark_page_key</item> | ||||
|     </string-array> | ||||
|     <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> | ||||
|   | ||||
| @@ -32,6 +32,7 @@ | ||||
|  | ||||
|     <string name="tab_main">Main</string> | ||||
|     <string name="tab_subscriptions">Subscriptions</string> | ||||
|     <string name="tab_bookmarks">Bookmarks</string> | ||||
|  | ||||
|     <string name="fragment_whats_new">What\'s New</string> | ||||
|  | ||||
| @@ -304,6 +305,8 @@ | ||||
|     <string name="history_cleared">History cleared</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="title_watch_history">Watch History</string> | ||||
|     <string name="title_most_played">Most Played</string> | ||||
|  | ||||
|     <!-- Content --> | ||||
|     <string name="main_page_content">Content of main page</string> | ||||
| @@ -370,5 +373,6 @@ | ||||
|  | ||||
|     <!-- Local Playlist --> | ||||
|     <string name="create_playlist">Create New Playlist</string> | ||||
|     <string name="delete_playlist">Delete Playlist</string> | ||||
|     <string name="playlist_name_input">Name</string> | ||||
| </resources> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 John Zhen Mo
					John Zhen Mo