diff --git a/app/build.gradle b/app/build.gradle index b97958edce2..1ba11861882 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -134,7 +134,7 @@ task formatKtlint(type: JavaExec) { } afterEvaluate { - preDebugBuild.dependsOn runCheckstyle, runKtlint + //preDebugBuild.dependsOn runCheckstyle, runKtlint } dependencies { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 336e3997e27..fe0c66e587b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -65,8 +65,8 @@ import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.EmptyFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment; +import org.schabi.newpipe.fragments.list.playlist.AppendPlaylistDialog; import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment; -import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer; @@ -409,7 +409,7 @@ public void onClick(final View v) { break; case R.id.detail_controls_playlist_append: if (getFragmentManager() != null && currentInfo != null) { - PlaylistAppendDialog.fromStreamInfo(currentInfo) + AppendPlaylistDialog.fromStreamInfo(currentInfo) .show(getFragmentManager(), TAG); } break; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 9ce62a0df1a..f6a725bb87a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.fragments.list; -import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -14,26 +13,15 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; -import org.schabi.newpipe.info_list.InfoItemDialog; -import org.schabi.newpipe.info_list.InfoListAdapter; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.OnClickGesture; +import org.schabi.newpipe.info_list.ItemListAdapter; import org.schabi.newpipe.util.StateSaver; -import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.views.SuperScrollLayoutManager; import java.util.List; @@ -54,7 +42,7 @@ public abstract class BaseListFragment extends BaseStateFragment // Views //////////////////////////////////////////////////////////////////////////*/ - protected InfoListAdapter infoListAdapter; + protected ItemListAdapter itemListAdapter; protected RecyclerView itemsList; private int focusedPosition = -1; @@ -66,8 +54,8 @@ public abstract class BaseListFragment extends BaseStateFragment public void onAttach(final Context context) { super.onAttach(context); - if (infoListAdapter == null) { - infoListAdapter = new InfoListAdapter(activity); + if (itemListAdapter == null) { + itemListAdapter = new ItemListAdapter(activity); } } @@ -103,8 +91,8 @@ public void onResume() { final boolean useGrid = isGridLayout(); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); - infoListAdapter.setUseGridVariant(useGrid); - infoListAdapter.notifyDataSetChanged(); + itemListAdapter.setUseGridVariant(useGrid); + itemListAdapter.notifyDataSetChanged(); } updateFlags = 0; } @@ -127,7 +115,7 @@ public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) { @Override public String generateSuffix() { // Naive solution, but it's good for now (the items don't change) - return "." + infoListAdapter.getItemsList().size() + ".list"; + return "." + itemListAdapter.getItemList().size() + ".list"; } private int getFocusedPosition() { @@ -147,7 +135,7 @@ public void writeTo(final Queue objectsToSave) { return; } - objectsToSave.add(infoListAdapter.getItemsList()); + objectsToSave.add(itemListAdapter.getItemList()); objectsToSave.add(getFocusedPosition()); } @@ -158,8 +146,8 @@ public void readFrom(@NonNull final Queue savedObjects) throws Exception return; } - infoListAdapter.getItemsList().clear(); - infoListAdapter.getItemsList().addAll((List) savedObjects.poll()); + itemListAdapter.getItemList().clear(); + itemListAdapter.getItemList().addAll((List) savedObjects.poll()); restoreFocus((Integer) savedObjects.poll()); } @@ -230,7 +218,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double) width); final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); - lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); + lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount)); return lm; } @@ -242,70 +230,16 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { itemsList = rootView.findViewById(R.id.items_list); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); - infoListAdapter.setUseGridVariant(useGrid); - infoListAdapter.setFooter(getListFooter()); - infoListAdapter.setHeader(getListHeader()); + itemListAdapter.setUseGridVariant(useGrid); + itemListAdapter.setFooter(getListFooter()); + itemListAdapter.setHeader(getListHeader()); - itemsList.setAdapter(infoListAdapter); - } - - protected void onItemSelected(final InfoItem selectedItem) { - if (DEBUG) { - Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]"); - } + itemsList.setAdapter(itemListAdapter); } @Override protected void initListeners() { super.initListeners(); - infoListAdapter.setOnStreamSelectedListener(new OnClickGesture() { - @Override - public void selected(final StreamInfoItem selectedItem) { - onStreamSelected(selectedItem); - } - - @Override - public void held(final StreamInfoItem selectedItem) { - showStreamDialog(selectedItem); - } - }); - - infoListAdapter.setOnChannelSelectedListener(new OnClickGesture() { - @Override - public void selected(final ChannelInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openChannelFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); - } - } - }); - - infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture() { - @Override - public void selected(final PlaylistInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openPlaylistFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); - } - } - }); - - infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture() { - @Override - public void selected(final CommentsInfoItem selectedItem) { - onItemSelected(selectedItem); - } - }); itemsList.clearOnScrollListeners(); itemsList.addOnScrollListener(new OnScrollBelowItemsListener() { @@ -316,12 +250,6 @@ public void onScrolledDown(final RecyclerView recyclerView) { }); } - private void onStreamSelected(final StreamInfoItem selectedItem) { - onItemSelected(selectedItem); - NavigationHelper.openVideoDetailFragment(getFM(), - selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName()); - } - protected void onScrollToBottom() { if (hasMoreItems() && !isLoading.get()) { loadMoreItems(); @@ -329,33 +257,6 @@ protected void onScrollToBottom() { } - protected void showStreamDialog(final StreamInfoItem item) { - final Context context = getContext(); - final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) { - return; - } - - if (item.getStreamType() == StreamType.AUDIO_STREAM) { - StreamDialogEntry.setEnabledEntries( - StreamDialogEntry.enqueue_on_background, - StreamDialogEntry.start_here_on_background, - StreamDialogEntry.append_playlist, - StreamDialogEntry.share); - } else { - StreamDialogEntry.setEnabledEntries( - StreamDialogEntry.enqueue_on_background, - StreamDialogEntry.enqueue_on_popup, - StreamDialogEntry.start_here_on_background, - StreamDialogEntry.start_here_on_popup, - StreamDialogEntry.append_playlist, - StreamDialogEntry.share); - } - - new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), - (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show(); - } - /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -418,8 +319,8 @@ public void showEmptyState() { @Override public void showListFooter(final boolean show) { itemsList.post(() -> { - if (infoListAdapter != null && itemsList != null) { - infoListAdapter.showFooter(show); + if (itemListAdapter != null && itemsList != null) { + itemListAdapter.showFooter(show); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 86b093e453b..e4c11871e45 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -54,7 +54,7 @@ public void onResume() { super.onResume(); // Check if it was loading when the fragment was stopped/paused, if (wasLoading.getAndSet(false)) { - if (hasMoreItems() && infoListAdapter.getItemsList().size() > 0) { + if (hasMoreItems() && itemListAdapter.getItemList().size() > 0) { loadMoreItems(); } else { doInitialLoadLogic(); @@ -119,7 +119,7 @@ public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); showListFooter(false); - infoListAdapter.clearStreamItemList(); + itemListAdapter.clearStreamItemList(); currentInfo = null; if (currentWorker != null) { @@ -183,7 +183,7 @@ private void allowDownwardFocusScroll() { public void handleNextItems(final ListExtractor.InfoItemsPage result) { super.handleNextItems(result); currentNextPage = result.getNextPage(); - infoListAdapter.addInfoItemList(result.getItems()); + itemListAdapter.addItems(result.getItems()); showListFooter(hasMoreItems()); } @@ -204,12 +204,12 @@ public void handleResult(@NonNull final I result) { name = result.getName(); setTitle(name); - if (infoListAdapter.getItemsList().size() == 0) { + if (itemListAdapter.getItemList().size() == 0) { if (result.getRelatedItems().size() > 0) { - infoListAdapter.addInfoItemList(result.getRelatedItems()); + itemListAdapter.addItems(result.getRelatedItems()); showListFooter(hasMoreItems()); } else { - infoListAdapter.clearStreamItemList(); + itemListAdapter.clearStreamItemList(); showEmptyState(); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 330aa7b423d..fd876f9f09e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -28,7 +28,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.subscription.SubscriptionEntity; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; @@ -549,7 +548,7 @@ private PlayQueue getPlayQueue() { private PlayQueue getPlayQueue(final int index) { final List streamItems = new ArrayList<>(); - for (InfoItem i : infoListAdapter.getItemsList()) { + for (Object i : itemListAdapter.getItemList()) { if (i instanceof StreamInfoItem) { streamItems.add((StreamInfoItem) i); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/AppendPlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/AppendPlaylistDialog.java new file mode 100644 index 00000000000..c4c982cad1e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/AppendPlaylistDialog.java @@ -0,0 +1,115 @@ +package org.schabi.newpipe.fragments.list.playlist; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; +import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; +import org.schabi.newpipe.database.stream.model.StreamEntity; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import io.reactivex.android.schedulers.AndroidSchedulers; + +public final class AppendPlaylistDialog extends PlaylistDialog + implements PlaylistDialog.OnSelectedListener { + public static final String TAG = AppendPlaylistDialog.class.getSimpleName(); + + public AppendPlaylistDialog(@Nullable final List streamEntities) { + super(streamEntities, false); + setOnSelectedListener(this); + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + final ViewGroup container, + final Bundle savedInstanceState) { + + final View view = super.onCreateView(inflater, container, savedInstanceState); + + final View newPlaylistButton = Objects.requireNonNull(view).findViewById(R.id.newPlaylist); + newPlaylistButton.setVisibility(View.VISIBLE); + newPlaylistButton.setOnClickListener(v -> openCreatePlaylistDialog()); + + view.findViewById(R.id.titleTextView).setVisibility(View.GONE); + + return view; + } + + public static AppendPlaylistDialog fromStreamInfo(final StreamInfo streamInfo) { + return new AppendPlaylistDialog(Collections.singletonList(new StreamEntity(streamInfo))); + } + + public static AppendPlaylistDialog fromStreamInfoItems( + final List streamInfoItems) { + + final List streamEntities = new ArrayList<>(streamInfoItems.size()); + for (final StreamInfoItem streamInfoItem : streamInfoItems) { + streamEntities.add(new StreamEntity(streamInfoItem)); + } + return new AppendPlaylistDialog(streamEntities); + } + + public static AppendPlaylistDialog fromPlayQueueItems( + final List playQueueItems) { + + final List streamEntities = new ArrayList<>(playQueueItems.size()); + for (final PlayQueueItem playQueueItem : playQueueItems) { + streamEntities.add(new StreamEntity(playQueueItem)); + } + return new AppendPlaylistDialog(streamEntities); + } + + + @Override + public void onLocalPlaylistSelected(final PlaylistMetadataEntry localPlaylist) { + if (getStoredStreamEntities() != null) { + final Toast successToast = Toast.makeText(getContext(), + R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); + + if (localPlaylist.thumbnailUrl + .equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { + dialogDisposables.add(getLocalPlaylistManager() + .changePlaylistThumbnail(localPlaylist.uid, + getStoredStreamEntities().get(0).getThumbnailUrl()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignored -> successToast.show())); + } + + dialogDisposables.add(getLocalPlaylistManager() + .appendToPlaylist(localPlaylist.uid, getStoredStreamEntities()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignored -> successToast.show())); + + requireDialog().dismiss(); + } + } + + @Override + public void onRemotePlaylistSelected(final PlaylistRemoteEntity remotePlaylist) { + // unreachable code + } + + public void openCreatePlaylistDialog() { + if (getStoredStreamEntities() == null) { + return; + } + + new CreatePlaylistDialog(getStoredStreamEntities()) + .show(getParentFragmentManager(), TAG); + requireDialog().dismiss(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/CreatePlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/CreatePlaylistDialog.java new file mode 100644 index 00000000000..969b533434a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/CreatePlaylistDialog.java @@ -0,0 +1,132 @@ +package org.schabi.newpipe.fragments.list.playlist; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import org.schabi.newpipe.NewPipeDatabase; +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.stream.model.StreamEntity; +import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.util.StateSaver; + +import java.util.List; +import java.util.Queue; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; + +public final class CreatePlaylistDialog extends DialogFragment implements StateSaver.WriteRead { + + /*////////////////////////////////////////////////////////////////////////// + // Dialog + //////////////////////////////////////////////////////////////////////////*/ + + private Disposable createPlaylistDisposable; + + private List streamEntities; + private StateSaver.SavedState savedState; + + CreatePlaylistDialog(final List streamEntities) { + this.streamEntities = streamEntities; + } + + protected List getStreams() { + return streamEntities; + } + + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + savedState = StateSaver.tryToRestore(savedInstanceState, this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + StateSaver.onDestroy(savedState); + + if (createPlaylistDisposable != null) { + createPlaylistDisposable.dispose(); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { + if (getStreams() == null) { + final Dialog dialog = super.onCreateDialog(savedInstanceState); + //remove title + final Window window = dialog.getWindow(); + if (window != null) { + window.requestFeature(Window.FEATURE_NO_TITLE); + } + return dialog; + } + + View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null); + EditText nameInput = dialogView.findViewById(R.id.playlist_name); + + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext()) + .setTitle(R.string.create_playlist) + .setView(dialogView) + .setCancelable(true) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.create, (dialogInterface, i) -> { + final String name = nameInput.getText().toString(); + final LocalPlaylistManager playlistManager = + new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext())); + final Toast successToast = Toast.makeText(getActivity(), + R.string.playlist_creation_success, + Toast.LENGTH_SHORT); + + createPlaylistDisposable = playlistManager.createPlaylist(name, getStreams()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(longs -> successToast.show()); + }); + + return dialogBuilder.create(); + } + + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public String generateSuffix() { + final int size = streamEntities == null ? 0 : streamEntities.size(); + return "." + size + ".list"; + } + + @Override + public void writeTo(final Queue objectsToSave) { + objectsToSave.add(streamEntities); + } + + @Override + @SuppressWarnings("unchecked") + public void readFrom(@NonNull final Queue savedObjects) { + streamEntities = (List) savedObjects.poll(); + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + if (getActivity() != null) { + savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(), + savedState, outState, this); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistDialog.java new file mode 100644 index 00000000000..e7ae8a252f0 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistDialog.java @@ -0,0 +1,307 @@ +package org.schabi.newpipe.fragments.list.playlist; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; + +import org.schabi.newpipe.NewPipeDatabase; +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.AppDatabase; +import org.schabi.newpipe.database.LocalItem; +import org.schabi.newpipe.database.playlist.PlaylistLocalItem; +import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; +import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; +import org.schabi.newpipe.database.stream.model.StreamEntity; +import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.local.playlist.RemotePlaylistManager; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.StateSaver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; + +import io.reactivex.Flowable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; + +public class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead { + /** + * This contains the base display options for images. + */ + private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS + = new DisplayImageOptions.Builder().cacheInMemory(true).build(); + + + public interface OnSelectedListener { + void onLocalPlaylistSelected(PlaylistMetadataEntry localPlaylist); + void onRemotePlaylistSelected(PlaylistRemoteEntity remotePlaylist); + } + + + private ProgressBar progressBar; + private TextView emptyView; + private RecyclerView recyclerView; + + protected CompositeDisposable dialogDisposables = new CompositeDisposable(); + private StateSaver.SavedState savedState; + + @Nullable private OnSelectedListener onSelectedListener = null; + private LocalPlaylistManager localPlaylistManager; + + private List playlists = new ArrayList<>(); + @Nullable private List storedStreamEntities; + private final boolean showRemotePlaylists; + + + public PlaylistDialog(@Nullable final List storedStreamEntities, + final boolean showRemotePlaylists) { + this.storedStreamEntities = storedStreamEntities; + this.showRemotePlaylists = showRemotePlaylists; + } + + @Nullable + protected List getStoredStreamEntities() { + return storedStreamEntities; + } + + protected LocalPlaylistManager getLocalPlaylistManager() { + return localPlaylistManager; + } + + /*////////////////////////////////////////////////////////////////////////// + // Fragment's Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + savedState = StateSaver.tryToRestore(savedInstanceState, this); + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + final View v = + inflater.inflate(R.layout.dialog_playlists, container, false); + recyclerView = v.findViewById(R.id.itemList); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter(); + recyclerView.setAdapter(playlistAdapter); + + progressBar = v.findViewById(R.id.progressBar); + emptyView = v.findViewById(R.id.emptyStateView); + progressBar.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + + final AppDatabase database = NewPipeDatabase.getInstance(requireContext()); + // save for usage in extending classes + localPlaylistManager = new LocalPlaylistManager(database); + + if (showRemotePlaylists) { + final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database); + dialogDisposables.add(Flowable.combineLatest(localPlaylistManager.getPlaylists(), + remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::displayPlaylists, this::onError)); + } else { + dialogDisposables.add(localPlaylistManager.getPlaylists() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe((p) -> displayPlaylists(new ArrayList<>(p)), this::onError)); + } + + return v; + } + + @Override + public void onDestroy() { + super.onDestroy(); + StateSaver.onDestroy(savedState); + + if (dialogDisposables != null) { + dialogDisposables.dispose(); + dialogDisposables = null; + } + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { + final Dialog dialog = super.onCreateDialog(savedInstanceState); + final Window window = dialog.getWindow(); + if (window != null) { // remove window title + window.requestFeature(Window.FEATURE_NO_TITLE); + } + return dialog; + } + + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public String generateSuffix() { + final int size = storedStreamEntities == null ? 0 : storedStreamEntities.size(); + return "." + size + ".list"; + } + + @Override + public void writeTo(final Queue objectsToSave) { + objectsToSave.add(playlists); + objectsToSave.add(storedStreamEntities); + } + + @Override + @SuppressWarnings("unchecked") + public void readFrom(@NonNull final Queue savedObjects) { + playlists = (List) savedObjects.poll(); + storedStreamEntities = (List) savedObjects.poll(); + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + if (getActivity() != null) { + savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(), + savedState, outState, this); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + public PlaylistDialog setOnSelectedListener(@Nullable final OnSelectedListener listener) { + onSelectedListener = listener; + return this; + } + + private void clickedItem(final int position) { + if (onSelectedListener != null) { + final LocalItem selectedItem = playlists.get(position); + + if (selectedItem instanceof PlaylistMetadataEntry) { + onSelectedListener.onLocalPlaylistSelected((PlaylistMetadataEntry) selectedItem); + + } else if (selectedItem instanceof PlaylistRemoteEntity) { + onSelectedListener.onRemotePlaylistSelected((PlaylistRemoteEntity) selectedItem); + } + } + dismiss(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Item handling + //////////////////////////////////////////////////////////////////////////*/ + + private void displayPlaylists(final List newPlaylists) { + this.playlists = newPlaylists; + progressBar.setVisibility(View.GONE); + if (newPlaylists.isEmpty()) { + emptyView.setVisibility(View.VISIBLE); + return; + } + recyclerView.setVisibility(View.VISIBLE); + + } + + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + + protected void onError(final Throwable e) { + final Activity activity = requireActivity(); + ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo + .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + } + + /*////////////////////////////////////////////////////////////////////////// + // Adapter + //////////////////////////////////////////////////////////////////////////*/ + + private class SelectPlaylistAdapter + extends RecyclerView.Adapter { + @Override + @NonNull + public SelectPlaylistItemHolder onCreateViewHolder(final ViewGroup parent, + final int viewType) { + final View item = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_playlist_mini_item, parent, false); + return new SelectPlaylistItemHolder(item); + } + + @Override + public void onBindViewHolder(@NonNull final SelectPlaylistItemHolder holder, + final int position) { + final PlaylistLocalItem selectedItem = playlists.get(position); + + if (selectedItem instanceof PlaylistMetadataEntry) { + final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); + holder.updateItem(entry.name, "", entry.streamCount, entry.thumbnailUrl, + view -> clickedItem(position)); + + } else if (selectedItem instanceof PlaylistRemoteEntity) { + final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); + holder.updateItem(entry.getName(), entry.getUploader(), entry.getStreamCount(), + entry.getThumbnailUrl(), view -> clickedItem(position)); + } + } + + @Override + public int getItemCount() { + return playlists.size(); + } + + } + + public static class SelectPlaylistItemHolder extends RecyclerView.ViewHolder { + private final View view; + private final TextView titleView; + private final TextView uploaderView; + private final TextView streamCountView; + private final ImageView thumbnailView; + + SelectPlaylistItemHolder(final View view) { + super(view); + this.view = view; + titleView = view.findViewById(R.id.itemTitleView); + uploaderView = view.findViewById(R.id.itemUploaderView); + streamCountView = view.findViewById(R.id.itemStreamCountView); + thumbnailView = view.findViewById(R.id.itemThumbnailView); + } + + void updateItem(final String title, + final String uploader, + final long streamCount, + final String thumbnailUrl, + final View.OnClickListener onClickListener) { + + titleView.setText(title); + uploaderView.setText(uploader); + streamCountView.setText(Localization.localizeStreamCountMini( + streamCountView.getContext(), streamCount)); + ImageLoader.getInstance() + .displayImage(thumbnailUrl, thumbnailView, DISPLAY_IMAGE_OPTIONS); + view.setOnClickListener(onClickListener); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index e2ec9c1f3b5..20993c46fb3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -1,7 +1,5 @@ package org.schabi.newpipe.fragments.list.playlist; -import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -23,15 +21,12 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; -import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; @@ -42,7 +37,6 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ShareUtils; -import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; @@ -136,47 +130,11 @@ protected View getListHeader() { protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - infoListAdapter.setUseMiniVariant(true); + itemListAdapter.setUseMiniVariant(true); } private PlayQueue getPlayQueueStartingAt(final StreamInfoItem infoItem) { - return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0)); - } - - @Override - protected void showStreamDialog(final StreamInfoItem item) { - final Context context = getContext(); - final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) { - return; - } - - if (item.getStreamType() == StreamType.AUDIO_STREAM) { - StreamDialogEntry.setEnabledEntries( - StreamDialogEntry.enqueue_on_background, - StreamDialogEntry.start_here_on_background, - StreamDialogEntry.append_playlist, - StreamDialogEntry.share); - } else { - StreamDialogEntry.setEnabledEntries( - StreamDialogEntry.enqueue_on_background, - StreamDialogEntry.enqueue_on_popup, - StreamDialogEntry.start_here_on_background, - StreamDialogEntry.start_here_on_popup, - StreamDialogEntry.append_playlist, - StreamDialogEntry.share); - - StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItem) -> - NavigationHelper.playOnPopupPlayer(context, - getPlayQueueStartingAt(infoItem), true)); - } - - StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) -> - NavigationHelper.playOnBackgroundPlayer(context, - getPlayQueueStartingAt(infoItem), true)); - - new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), - (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show(); + return getPlayQueue(Math.max(itemListAdapter.getItemList().indexOf(infoItem), 0)); } @Override @@ -341,7 +299,7 @@ private PlayQueue getPlayQueue() { private PlayQueue getPlayQueue(final int index) { final List infoItems = new ArrayList<>(); - for (InfoItem i : infoListAdapter.getItemsList()) { + for (Object i : itemListAdapter.getItemList()) { if (i instanceof StreamInfoItem) { infoItems.add((StreamInfoItem) i); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 4f21565f420..a216d44c43b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -35,7 +35,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.Page; @@ -53,6 +52,7 @@ import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.ServiceHelper; import java.util.ArrayList; @@ -257,11 +257,11 @@ public void onResume() { if (!TextUtils.isEmpty(searchString)) { if (wasLoading.getAndSet(false)) { search(searchString, contentFilter, sortFilter); - } else if (infoListAdapter.getItemsList().size() == 0) { + } else if (itemListAdapter.getItemList().size() == 0) { if (savedState == null) { search(searchString, contentFilter, sortFilter); } else if (!isLoading.get() && !wasSearchFocused) { - infoListAdapter.clearStreamItemList(); + itemListAdapter.clearStreamItemList(); showEmptyState(); } } @@ -694,7 +694,7 @@ private void showDeleteSuggestionDialog(final SuggestionItem item) { @Override public boolean onBackPressed() { if (suggestionsPanel.getVisibility() == View.VISIBLE - && infoListAdapter.getItemsList().size() > 0 + && itemListAdapter.getItemList().size() > 0 && !isLoading.get()) { hideSuggestionsPanel(); hideKeyboardSearch(); @@ -819,7 +819,7 @@ private void search(final String ss, final String[] cf, final String sf) { lastSearchedString = this.searchString; this.searchString = ss; - infoListAdapter.clearStreamItemList(); + itemListAdapter.clearStreamItemList(); hideSuggestionsPanel(); hideKeyboardSearch(); @@ -884,9 +884,14 @@ protected boolean hasMoreItems() { } @Override - protected void onItemSelected(final InfoItem selectedItem) { - super.onItemSelected(selectedItem); - hideKeyboardSearch(); + protected void initListeners() { + super.initListeners(); + itemListAdapter.setOnItemSelectedListener(new OnClickGesture() { + @Override + public void selected(final Object selectedItem) { + hideKeyboardSearch(); + } + }); } /*////////////////////////////////////////////////////////////////////////// @@ -981,11 +986,11 @@ public void handleResult(@NonNull final SearchInfo result) { lastSearchedString = searchString; nextPage = result.getNextPage(); - if (infoListAdapter.getItemsList().size() == 0) { + if (itemListAdapter.getItemList().size() == 0) { if (!result.getRelatedItems().isEmpty()) { - infoListAdapter.addInfoItemList(result.getRelatedItems()); + itemListAdapter.addItems(result.getRelatedItems()); } else { - infoListAdapter.clearStreamItemList(); + itemListAdapter.clearStreamItemList(); showEmptyState(); return; } @@ -1028,7 +1033,7 @@ private void handleSearchSuggestion() { @Override public void handleNextItems(final ListExtractor.InfoItemsPage result) { showListFooter(false); - infoListAdapter.addInfoItemList(result.getItems()); + itemListAdapter.addItems(result.getItems()); nextPage = result.getNextPage(); if (!result.getErrors().isEmpty()) { @@ -1048,7 +1053,7 @@ protected boolean onError(final Throwable exception) { } if (exception instanceof SearchExtractor.NothingFoundException) { - infoListAdapter.clearStreamItemList(); + itemListAdapter.clearStreamItemList(); showEmptyState(); } else { int errorId = exception instanceof ParsingException diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java deleted file mode 100644 index 148d0d0742e..00000000000 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.schabi.newpipe.info_list; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; - -import com.nostra13.universalimageloader.core.ImageLoader; - -import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; -import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder; -import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder; -import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder; -import org.schabi.newpipe.info_list.holder.InfoItemHolder; -import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; -import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; -import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; -import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; -import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.OnClickGesture; - -/* - * Created by Christian Schabesberger on 26.09.16. - *

- * Copyright (C) Christian Schabesberger 2016 - * InfoItemBuilder.java is part of NewPipe. - *

- *

- * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - *

- *

- * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - *

- *

- * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - *

- */ - -public class InfoItemBuilder { - private final Context context; - private final ImageLoader imageLoader = ImageLoader.getInstance(); - - private OnClickGesture onStreamSelectedListener; - private OnClickGesture onChannelSelectedListener; - private OnClickGesture onPlaylistSelectedListener; - private OnClickGesture onCommentsSelectedListener; - - public InfoItemBuilder(final Context context) { - this.context = context; - } - - public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, - final HistoryRecordManager historyRecordManager) { - return buildView(parent, infoItem, historyRecordManager, false); - } - - public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, - final HistoryRecordManager historyRecordManager, - final boolean useMiniVariant) { - InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); - holder.updateFromItem(infoItem, historyRecordManager); - return holder.itemView; - } - - private InfoItemHolder holderFromInfoType(@NonNull final ViewGroup parent, - @NonNull final InfoItem.InfoType infoType, - final boolean useMiniVariant) { - switch (infoType) { - case STREAM: - return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent) - : new StreamInfoItemHolder(this, parent); - case CHANNEL: - return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) - : new ChannelInfoItemHolder(this, parent); - case PLAYLIST: - return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) - : new PlaylistInfoItemHolder(this, parent); - case COMMENT: - return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) - : new CommentsInfoItemHolder(this, parent); - default: - throw new RuntimeException("InfoType not expected = " + infoType.name()); - } - } - - public Context getContext() { - return context; - } - - public ImageLoader getImageLoader() { - return imageLoader; - } - - public OnClickGesture getOnStreamSelectedListener() { - return onStreamSelectedListener; - } - - public void setOnStreamSelectedListener(final OnClickGesture listener) { - this.onStreamSelectedListener = listener; - } - - public OnClickGesture getOnChannelSelectedListener() { - return onChannelSelectedListener; - } - - public void setOnChannelSelectedListener(final OnClickGesture listener) { - this.onChannelSelectedListener = listener; - } - - public OnClickGesture getOnPlaylistSelectedListener() { - return onPlaylistSelectedListener; - } - - public void setOnPlaylistSelectedListener(final OnClickGesture listener) { - this.onPlaylistSelectedListener = listener; - } - - public OnClickGesture getOnCommentsSelectedListener() { - return onCommentsSelectedListener; - } - - public void setOnCommentsSelectedListener( - final OnClickGesture onCommentsSelectedListener) { - this.onCommentsSelectedListener = onCommentsSelectedListener; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java deleted file mode 100644 index eb4b2c2c0d2..00000000000 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ /dev/null @@ -1,382 +0,0 @@ -package org.schabi.newpipe.info_list; - -import android.content.Context; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import org.schabi.newpipe.database.stream.model.StreamStateEntity; -import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder; -import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; -import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder; -import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder; -import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder; -import org.schabi.newpipe.info_list.holder.InfoItemHolder; -import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder; -import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; -import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; -import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder; -import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; -import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; -import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.FallbackViewHolder; -import org.schabi.newpipe.util.OnClickGesture; - -import java.util.ArrayList; -import java.util.List; - -/* - * Created by Christian Schabesberger on 01.08.16. - * - * Copyright (C) Christian Schabesberger 2016 - * InfoListAdapter.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class InfoListAdapter extends RecyclerView.Adapter { - private static final String TAG = InfoListAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final int HEADER_TYPE = 0; - private static final int FOOTER_TYPE = 1; - - private static final int MINI_STREAM_HOLDER_TYPE = 0x100; - private static final int STREAM_HOLDER_TYPE = 0x101; - private static final int GRID_STREAM_HOLDER_TYPE = 0x102; - private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200; - private static final int CHANNEL_HOLDER_TYPE = 0x201; - private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202; - private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300; - private static final int PLAYLIST_HOLDER_TYPE = 0x301; - private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302; - private static final int MINI_COMMENT_HOLDER_TYPE = 0x400; - private static final int COMMENT_HOLDER_TYPE = 0x401; - - private final InfoItemBuilder infoItemBuilder; - private final ArrayList infoItemList; - private final HistoryRecordManager recordManager; - - private boolean useMiniVariant = false; - private boolean useGridVariant = false; - private boolean showFooter = false; - private View header = null; - private View footer = null; - - public InfoListAdapter(final Context context) { - this.recordManager = new HistoryRecordManager(context); - infoItemBuilder = new InfoItemBuilder(context); - infoItemList = new ArrayList<>(); - } - - public void setOnStreamSelectedListener(final OnClickGesture listener) { - infoItemBuilder.setOnStreamSelectedListener(listener); - } - - public void setOnChannelSelectedListener(final OnClickGesture listener) { - infoItemBuilder.setOnChannelSelectedListener(listener); - } - - public void setOnPlaylistSelectedListener(final OnClickGesture listener) { - infoItemBuilder.setOnPlaylistSelectedListener(listener); - } - - public void setOnCommentsSelectedListener(final OnClickGesture listener) { - infoItemBuilder.setOnCommentsSelectedListener(listener); - } - - public void setUseMiniVariant(final boolean useMiniVariant) { - this.useMiniVariant = useMiniVariant; - } - - public void setUseGridVariant(final boolean useGridVariant) { - this.useGridVariant = useGridVariant; - } - - public void addInfoItemList(@Nullable final List data) { - if (data == null) { - return; - } - if (DEBUG) { - Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " - + infoItemList.size() + ", data.size() = " + data.size()); - } - - int offsetStart = sizeConsideringHeaderOffset(); - infoItemList.addAll(data); - - if (DEBUG) { - Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", " - + "infoItemList.size() = " + infoItemList.size() + ", " - + "header = " + header + ", footer = " + footer + ", " - + "showFooter = " + showFooter); - } - notifyItemRangeInserted(offsetStart, data.size()); - - if (footer != null && showFooter) { - int footerNow = sizeConsideringHeaderOffset(); - notifyItemMoved(offsetStart, footerNow); - - if (DEBUG) { - Log.d(TAG, "addInfoItemList() footer from " + offsetStart - + " to " + footerNow); - } - } - } - - public void setInfoItemList(final List data) { - infoItemList.clear(); - infoItemList.addAll(data); - notifyDataSetChanged(); - } - - public void addInfoItem(@Nullable final InfoItem data) { - if (data == null) { - return; - } - if (DEBUG) { - Log.d(TAG, "addInfoItem() before > infoItemList.size() = " - + infoItemList.size() + ", thread = " + Thread.currentThread()); - } - - int positionInserted = sizeConsideringHeaderOffset(); - infoItemList.add(data); - - if (DEBUG) { - Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", " - + "infoItemList.size() = " + infoItemList.size() + ", " - + "header = " + header + ", footer = " + footer + ", " - + "showFooter = " + showFooter); - } - notifyItemInserted(positionInserted); - - if (footer != null && showFooter) { - int footerNow = sizeConsideringHeaderOffset(); - notifyItemMoved(positionInserted, footerNow); - - if (DEBUG) { - Log.d(TAG, "addInfoItem() footer from " + positionInserted - + " to " + footerNow); - } - } - } - - public void clearStreamItemList() { - if (infoItemList.isEmpty()) { - return; - } - infoItemList.clear(); - notifyDataSetChanged(); - } - - public void setHeader(final View header) { - boolean changed = header != this.header; - this.header = header; - if (changed) { - notifyDataSetChanged(); - } - } - - public void setFooter(final View view) { - this.footer = view; - } - - public void showFooter(final boolean show) { - if (DEBUG) { - Log.d(TAG, "showFooter() called with: show = [" + show + "]"); - } - if (show == showFooter) { - return; - } - - showFooter = show; - if (show) { - notifyItemInserted(sizeConsideringHeaderOffset()); - } else { - notifyItemRemoved(sizeConsideringHeaderOffset()); - } - } - - private int sizeConsideringHeaderOffset() { - int i = infoItemList.size() + (header != null ? 1 : 0); - if (DEBUG) { - Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i); - } - return i; - } - - public ArrayList getItemsList() { - return infoItemList; - } - - @Override - public int getItemCount() { - int count = infoItemList.size(); - if (header != null) { - count++; - } - if (footer != null && showFooter) { - count++; - } - - if (DEBUG) { - Log.d(TAG, "getItemCount() called with: " - + "count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", " - + "header = " + header + ", footer = " + footer + ", " - + "showFooter = " + showFooter); - } - return count; - } - - @Override - public int getItemViewType(int position) { - if (DEBUG) { - Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); - } - - if (header != null && position == 0) { - return HEADER_TYPE; - } else if (header != null) { - position--; - } - if (footer != null && position == infoItemList.size() && showFooter) { - return FOOTER_TYPE; - } - final InfoItem item = infoItemList.get(position); - switch (item.getInfoType()) { - case STREAM: - return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant - ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE; - case CHANNEL: - return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant - ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE; - case PLAYLIST: - return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant - ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE; - case COMMENT: - return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE; - default: - return -1; - } - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, - final int type) { - if (DEBUG) { - Log.d(TAG, "onCreateViewHolder() called with: " - + "parent = [" + parent + "], type = [" + type + "]"); - } - switch (type) { - case HEADER_TYPE: - return new HFHolder(header); - case FOOTER_TYPE: - return new HFHolder(footer); - case MINI_STREAM_HOLDER_TYPE: - return new StreamMiniInfoItemHolder(infoItemBuilder, parent); - case STREAM_HOLDER_TYPE: - return new StreamInfoItemHolder(infoItemBuilder, parent); - case GRID_STREAM_HOLDER_TYPE: - return new StreamGridInfoItemHolder(infoItemBuilder, parent); - case MINI_CHANNEL_HOLDER_TYPE: - return new ChannelMiniInfoItemHolder(infoItemBuilder, parent); - case CHANNEL_HOLDER_TYPE: - return new ChannelInfoItemHolder(infoItemBuilder, parent); - case GRID_CHANNEL_HOLDER_TYPE: - return new ChannelGridInfoItemHolder(infoItemBuilder, parent); - case MINI_PLAYLIST_HOLDER_TYPE: - return new PlaylistMiniInfoItemHolder(infoItemBuilder, parent); - case PLAYLIST_HOLDER_TYPE: - return new PlaylistInfoItemHolder(infoItemBuilder, parent); - case GRID_PLAYLIST_HOLDER_TYPE: - return new PlaylistGridInfoItemHolder(infoItemBuilder, parent); - case MINI_COMMENT_HOLDER_TYPE: - return new CommentsMiniInfoItemHolder(infoItemBuilder, parent); - case COMMENT_HOLDER_TYPE: - return new CommentsInfoItemHolder(infoItemBuilder, parent); - default: - return new FallbackViewHolder(new View(parent.getContext())); - } - } - - @Override - public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { - if (DEBUG) { - Log.d(TAG, "onBindViewHolder() called with: " - + "holder = [" + holder.getClass().getSimpleName() + "], " - + "position = [" + position + "]"); - } - if (holder instanceof InfoItemHolder) { - // If header isn't null, offset the items by -1 - if (header != null) { - position--; - } - - ((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager); - } else if (holder instanceof HFHolder && position == 0 && header != null) { - ((HFHolder) holder).view = header; - } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() - && footer != null && showFooter) { - ((HFHolder) holder).view = footer; - } - } - - @Override - public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position, - @NonNull final List payloads) { - if (!payloads.isEmpty() && holder instanceof InfoItemHolder) { - for (Object payload : payloads) { - if (payload instanceof StreamStateEntity) { - ((InfoItemHolder) holder).updateState(infoItemList - .get(header == null ? position : position - 1), recordManager); - } else if (payload instanceof Boolean) { - ((InfoItemHolder) holder).updateState(infoItemList - .get(header == null ? position : position - 1), recordManager); - } - } - } else { - onBindViewHolder(holder, position); - } - } - - public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { - return new GridLayoutManager.SpanSizeLookup() { - @Override - public int getSpanSize(final int position) { - final int type = getItemViewType(position); - return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1; - } - }; - } - - public class HFHolder extends RecyclerView.ViewHolder { - public View view; - - HFHolder(final View v) { - super(v); - view = v; - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ItemHandler.java b/app/src/main/java/org/schabi/newpipe/info_list/ItemHandler.java new file mode 100644 index 00000000000..bb7179ec957 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/ItemHandler.java @@ -0,0 +1,89 @@ +package org.schabi.newpipe.info_list; + +import android.widget.ImageView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; + +import org.schabi.newpipe.util.OnClickGesture; + +import java.text.DateFormat; + +/* + * Created by Christian Schabesberger on 26.09.16. + *

+ * Copyright (C) Christian Schabesberger 2016 + * InfoItemBuilder.java is part of NewPipe. + *

+ *

+ * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + *

+ *

+ * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *

+ *

+ * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + *

+ */ + +public class ItemHandler { + private final FragmentActivity activity; + private final ImageLoader imageLoader = ImageLoader.getInstance(); + private final DateFormat dateFormat; + + @Nullable private OnClickGesture onItemSelectedListener; + @Nullable private ItemHolderWithToolbar itemHolderWithToolbarOpen = null; + + public ItemHandler(final FragmentActivity activity, final DateFormat dateFormat) { + this.activity = activity; + this.dateFormat = dateFormat; + } + + + public FragmentActivity getActivity() { + return activity; + } + + public FragmentManager getFragmentManager() { + return activity.getSupportFragmentManager(); + } + + public void displayImage(final String url, final ImageView view, + final DisplayImageOptions options) { + imageLoader.displayImage(url, view, options); + } + + public DateFormat getDateFormat() { + return dateFormat; + } + + + public void setOnItemSelectedListener( + @Nullable final OnClickGesture onItemSelectedListener) { + this.onItemSelectedListener = onItemSelectedListener; + } + + @Nullable + public OnClickGesture getOnItemSelectedListener() { + return onItemSelectedListener; + } + + public void replaceItemHolderWithToolbarOpen( + @Nullable ItemHolderWithToolbar newItemHolderWithToolbar) { + if (itemHolderWithToolbarOpen != null) { + itemHolderWithToolbarOpen.hideItemToolbar(); + } + itemHolderWithToolbarOpen = newItemHolderWithToolbar; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/ItemHolder.java similarity index 55% rename from app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java rename to app/src/main/java/org/schabi/newpipe/info_list/ItemHolder.java index 9e15617863a..a71a0956ff3 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/ItemHolder.java @@ -1,12 +1,10 @@ -package org.schabi.newpipe.info_list.holder; +package org.schabi.newpipe.info_list; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; -import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; /* @@ -29,18 +27,18 @@ * along with NewPipe. If not, see . */ -public abstract class InfoItemHolder extends RecyclerView.ViewHolder { - protected final InfoItemBuilder itemBuilder; +public abstract class ItemHolder extends RecyclerView.ViewHolder { + protected final ItemHandler itemHandler; - public InfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, - final ViewGroup parent) { - super(LayoutInflater.from(infoItemBuilder.getContext()).inflate(layoutId, parent, false)); - this.itemBuilder = infoItemBuilder; + public ItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { + super(LayoutInflater.from(itemHandler.getActivity()).inflate(layoutId, parent, false)); + this.itemHandler = itemHandler; } - public abstract void updateFromItem(InfoItem infoItem, - HistoryRecordManager historyRecordManager); + public abstract void updateFromObject(Object itemObject, + HistoryRecordManager historyRecordManager); - public void updateState(final InfoItem infoItem, - final HistoryRecordManager historyRecordManager) { } + public void updateStateFromObject(final Object itemObject, + final HistoryRecordManager historyRecordManager) { + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ItemHolderWithToolbar.java b/app/src/main/java/org/schabi/newpipe/info_list/ItemHolderWithToolbar.java new file mode 100644 index 00000000000..def527dbb9f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/ItemHolderWithToolbar.java @@ -0,0 +1,335 @@ +package org.schabi.newpipe.info_list; + +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; +import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; +import org.schabi.newpipe.database.stream.StreamStatisticsEntry; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.fragments.list.playlist.AppendPlaylistDialog; +import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; +import org.schabi.newpipe.player.playqueue.LocalPlaylistPlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; +import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.ShareUtils; + +public abstract class ItemHolderWithToolbar extends ItemHolder { + private static final String TAG = ItemHolderWithToolbar.class.getSimpleName(); + private static final int TOGGLE_ITEM_TOOLBAR_ANIMATION_DURATION = 70; // ms + + private static class ToolbarButtonsVisibility { + private final boolean delete; + private final boolean setAsPlaylistThumbnail; + + ToolbarButtonsVisibility(final boolean delete, + final boolean setAsPlaylistThumbnail) { + this.delete = delete; + this.setAsPlaylistThumbnail = setAsPlaylistThumbnail; + } + } + + + private final Class itemClass; + @NonNull + private LinearLayout itemToolbarView; + private final ImageButton share; + private final ImageButton addToPlaylist; + private final ImageButton delete; + private final ImageButton setAsPlaylistThumbnail; + private final ImageButton download; + private final ImageButton playBackground; + private final ImageButton playPopup; + private final ImageButton playMain; + + public ItemHolderWithToolbar(final Class itemClass, + final ItemHandler itemHandler, + final int layoutId, + final ViewGroup parent) { + + super(itemHandler, layoutId, parent); + this.itemClass = itemClass; + + itemToolbarView = itemView.findViewById(R.id.toolbarBelowItem); + if (itemToolbarView == null) { + itemToolbarView = itemView.findViewById(R.id.toolbarOverlayItem); + if (itemToolbarView == null) { + throw new IllegalArgumentException( + "getToolbarViewFromItem(): no item toolbars found for itemView"); + } + } + + share = itemToolbarView.findViewById(R.id.shareToolbarButton); + addToPlaylist = itemToolbarView.findViewById(R.id.addToPlaylistToolbarButton); + delete = itemToolbarView.findViewById(R.id.deleteToolbarButton); + setAsPlaylistThumbnail = + itemToolbarView.findViewById(R.id.setAsPlaylistThumbnailToolbarButton); + download = itemToolbarView.findViewById(R.id.downloadToolbarButton); + playBackground = itemToolbarView.findViewById(R.id.playBackgroundToolbarButton); + playPopup = itemToolbarView.findViewById(R.id.playPopupToolbarButton); + playMain = itemToolbarView.findViewById(R.id.playMainToolbarButton); + } + + + public abstract void updateFromItem(ItemType item, + HistoryRecordManager historyRecordManager); + + public void updateStateFromItem(final ItemType item, + final HistoryRecordManager historyRecordManager) { + } + + @Override + public void updateFromObject(final Object itemObject, + final HistoryRecordManager historyRecordManager) { + resetItemToolbar(); + hideInvalidToolbarButtons(itemObject); + + itemView.setOnClickListener(view -> { + if (itemHandler.getOnItemSelectedListener() != null) { + itemHandler.getOnItemSelectedListener().selected(itemObject); + } + + if (itemToolbarView.getVisibility() == View.VISIBLE) { + onShowInfo(itemObject); + } else { + showItemToolbar(itemObject); + } + }); + + itemView.setOnLongClickListener(view -> { + if (itemHandler.getOnItemSelectedListener() != null) { + itemHandler.getOnItemSelectedListener().held(itemObject); + } + return true; + }); + + if (itemClass.isAssignableFrom(itemObject.getClass())) { + updateFromItem(itemClass.cast(itemObject), historyRecordManager); + } + } + + @Override + public void updateStateFromObject(final Object itemObject, + final HistoryRecordManager historyRecordManager) { + if (itemClass.isAssignableFrom(itemObject.getClass())) { + updateStateFromItem(itemClass.cast(itemObject), historyRecordManager); + } + } + + void hideInvalidToolbarButtons(final Object itemObject) { + final ToolbarButtonsVisibility v; + if (itemObject instanceof PlaylistInfoItem) { + v = new ToolbarButtonsVisibility(false, false); + } else if (itemObject instanceof ChannelInfoItem) { + v = new ToolbarButtonsVisibility(false, false); + } else if (itemObject instanceof PlaylistRemoteEntity) { + v = new ToolbarButtonsVisibility(false, false); + } else if (itemObject instanceof PlaylistMetadataEntry) { + v = new ToolbarButtonsVisibility(false, false); + } else { + // convertible to stream info item + v = new ToolbarButtonsVisibility( false, false); + } + + delete.setVisibility(v.delete ? View.VISIBLE : View.GONE); + setAsPlaylistThumbnail.setVisibility(v.setAsPlaylistThumbnail ? View.VISIBLE : View.GONE); + + download.setVisibility(View.GONE); // TODO + } + + + private void resetItemToolbar() { + switch (itemToolbarView.getId()) { + case R.id.toolbarOverlayItem: + ToolbarOverlayItemAnimation.resetToolbarOverlayItem(itemToolbarView); + break; + case R.id.toolbarBelowItem: + ToolbarBelowItemAnimation.resetToolbarBelowItem(itemToolbarView); + break; + default: + throw new IllegalArgumentException( + "Invalid itemToolbarView passed to resetItemToolbarView()"); + } + } + + private void animateToggleItemToolbar() { + final Animation animation; + switch (itemToolbarView.getId()) { + case R.id.toolbarOverlayItem: + animation = new ToolbarOverlayItemAnimation(TOGGLE_ITEM_TOOLBAR_ANIMATION_DURATION, + itemToolbarView); + break; + case R.id.toolbarBelowItem: + animation = new ToolbarBelowItemAnimation(TOGGLE_ITEM_TOOLBAR_ANIMATION_DURATION, + itemToolbarView); + break; + default: + throw new IllegalArgumentException( + "Invalid itemToolbarView passed to toggleItemToolbar()"); + } + itemToolbarView.startAnimation(animation); + } + + private void showItemToolbar(final Object itemObject) { + animateToggleItemToolbar(); + itemHandler.replaceItemHolderWithToolbarOpen(this); + + share.setOnClickListener(v -> onShare(itemObject)); + addToPlaylist.setOnClickListener(v -> onAddToPlaylist(itemObject)); + playMain.setOnClickListener(v -> onPlayMain(itemObject)); + playPopup.setOnClickListener(v -> onPlayPopup(itemObject)); + playPopup.setOnLongClickListener(v -> { + onEnqueuePopup(itemObject); + return true; + }); + playBackground.setOnClickListener(v -> onPlayBackground(itemObject)); + playBackground.setOnLongClickListener(v -> { + onEnqueueBackground(itemObject); + return true; + }); + } + + public void hideItemToolbar() { + // this function is public and called by itemHandler when another item's toolbar is opened + // so prevent the toolbar from opening in case it is called in an unwanted case + if (itemToolbarView.getVisibility() == View.VISIBLE) { + animateToggleItemToolbar(); + } + } + + + ////////////////////////////////////// + // ACTIONS + ////////////////////////////////////// + + private void onShowInfo(final Object itemObject) { + if (itemObject instanceof PlaylistInfoItem) { + PlaylistInfoItem item = (PlaylistInfoItem) itemObject; + NavigationHelper.openPlaylistFragment(itemHandler.getFragmentManager(), + item.getServiceId(), item.getUrl(), item.getName()); + + } else if (itemObject instanceof ChannelInfoItem) { + ChannelInfoItem item = (ChannelInfoItem) itemObject; + NavigationHelper.openChannelFragment(itemHandler.getFragmentManager(), + item.getServiceId(), item.getUrl(), item.getName()); + NavigationHelper.openVideoDetailFragment(itemHandler.getFragmentManager(), + item.getServiceId(), item.getUrl(), item.getName()); + + } else if (itemObject instanceof PlaylistRemoteEntity) { + PlaylistRemoteEntity item = (PlaylistRemoteEntity) itemObject; + NavigationHelper.openPlaylistFragment(itemHandler.getFragmentManager(), + item.getServiceId(), item.getUrl(), item.getName()); + + } else if (itemObject instanceof PlaylistMetadataEntry) { + PlaylistMetadataEntry item = (PlaylistMetadataEntry) itemObject; + NavigationHelper.openLocalPlaylistFragment(itemHandler.getFragmentManager(), + item.uid, item.name); + + } else { + StreamInfoItem item = toStreamInfoItem(itemObject); + NavigationHelper.openVideoDetailFragment(itemHandler.getFragmentManager(), + item.getServiceId(), item.getUrl(), item.getName()); + } + } + + private void onShare(final Object itemObject) { + if (itemObject instanceof PlaylistRemoteEntity) { + PlaylistRemoteEntity item = (PlaylistRemoteEntity) itemObject; + ShareUtils.shareUrl(itemHandler.getActivity(), item.getName(), item.getUrl()); + } else if (itemObject instanceof PlaylistMetadataEntry) { + Log.e(TAG, "onShare: tried to share local playlist: " + itemObject.toString()); + } else { + InfoItem item; + if (itemObject instanceof InfoItem) { + item = (InfoItem) itemObject; + } else { + item = toStreamInfoItem(itemObject); + } + + ShareUtils.shareUrl(itemHandler.getActivity(), item.getName(), item.getUrl()); + } + } + + private void onAddToPlaylist(final Object itemObject) { + // TODO this breaks strangely with LocalPlaylistPlayQueue (see the class) + // so currently adding local playlists to other playlists is disabled + // TODO adding remote playlists to other playlists does not show "Playlisted" toast + final PlayQueue playQueue = queueFromObject(itemObject); + playQueue.fetch(() -> AppendPlaylistDialog.fromPlayQueueItems(playQueue.getStreams()) + .show(itemHandler.getActivity().getSupportFragmentManager(), + "StreamDialogEntry@append_playlist")); + } + + private void onPlayMain(final Object itemObject) { + NavigationHelper.playOnMainPlayer( + itemHandler.getActivity(), queueFromObject(itemObject), true); + } + + private void onPlayPopup(final Object itemObject) { + NavigationHelper.playOnPopupPlayer( + itemHandler.getActivity(), queueFromObject(itemObject), true); + } + + private void onEnqueuePopup(final Object itemObject) { + NavigationHelper.enqueueOnPopupPlayer( + itemHandler.getActivity(), queueFromObject(itemObject), false); + } + + private void onPlayBackground(final Object itemObject) { + NavigationHelper.playOnBackgroundPlayer( + itemHandler.getActivity(), queueFromObject(itemObject), true); + } + + private void onEnqueueBackground(final Object itemObject) { + NavigationHelper.enqueueOnBackgroundPlayer( + itemHandler.getActivity(), queueFromObject(itemObject), false); + } + + + ////////////////////////////////////// + // UTIL + ////////////////////////////////////// + + private static StreamInfoItem toStreamInfoItem(final Object itemObject) { + if (itemObject instanceof StreamInfoItem) { + return (StreamInfoItem) itemObject; + } else if (itemObject instanceof StreamStatisticsEntry) { + return ((StreamStatisticsEntry) itemObject).toStreamInfoItem(); + } else if (itemObject instanceof PlaylistStreamEntry) { + return ((PlaylistStreamEntry) itemObject).toStreamInfoItem(); + } + + throw new IllegalArgumentException("toStreamInfoItem: invalid item object: " + itemObject); + } + + private PlayQueue queueFromObject(final Object itemObject) { + if (itemObject instanceof PlaylistInfoItem) { + return new PlaylistPlayQueue((PlaylistInfoItem) itemObject); + } else if (itemObject instanceof PlaylistRemoteEntity) { + final PlaylistRemoteEntity item = (PlaylistRemoteEntity) itemObject; + return new PlaylistPlayQueue( + new PlaylistInfoItem(item.getServiceId(), item.getUrl(), item.getName())); + } else if (itemObject instanceof PlaylistMetadataEntry) { + return new LocalPlaylistPlayQueue( + itemHandler.getActivity(), (PlaylistMetadataEntry) itemObject); + } else if (itemObject instanceof ChannelInfoItem) { + return new ChannelPlayQueue((ChannelInfoItem) itemObject); + } else { + return new SinglePlayQueue(toStreamInfoItem(itemObject)); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/ItemListAdapter.java similarity index 51% rename from app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java rename to app/src/main/java/org/schabi/newpipe/info_list/ItemListAdapter.java index ad0524f92f3..11facdb6238 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/ItemListAdapter.java @@ -1,19 +1,30 @@ -package org.schabi.newpipe.local; +package org.schabi.newpipe.info_list; -import android.content.Context; import android.util.Log; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.model.StreamStateEntity; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder; +import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; +import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder; +import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder; +import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder; +import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder; +import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; +import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; +import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder; +import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; +import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.local.holder.LocalItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder; @@ -50,82 +61,104 @@ * along with NewPipe. If not, see . */ -public class LocalItemListAdapter extends RecyclerView.Adapter { - private static final String TAG = LocalItemListAdapter.class.getSimpleName(); +public class ItemListAdapter extends RecyclerView.Adapter { + private static final String TAG = ItemListAdapter.class.getSimpleName(); private static final boolean DEBUG = false; private static final int HEADER_TYPE = 0; private static final int FOOTER_TYPE = 1; + private static final int STREAM_HOLDER_MINI_TYPE = 0x100; + private static final int STREAM_HOLDER_TYPE = 0x101; + private static final int STREAM_HOLDER_GRID_TYPE = 0x102; + private static final int CHANNEL_HOLDER_MINI_TYPE = 0x200; + private static final int CHANNEL_HOLDER_TYPE = 0x201; + private static final int CHANNEL_HOLDER_GRID_TYPE = 0x202; + private static final int PLAYLIST_HOLDER_MINI_TYPE = 0x300; + private static final int PLAYLIST_HOLDER_TYPE = 0x301; + private static final int PLAYLIST_HOLDER_GRID_TYPE = 0x302; + private static final int COMMENT_HOLDER_MINI_TYPE = 0x400; + private static final int COMMENT_HOLDER_TYPE = 0x401; + private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000; - private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001; private static final int STREAM_STATISTICS_GRID_HOLDER_TYPE = 0x1002; + private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001; private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004; private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000; - private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001; private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002; + private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001; private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004; - private final LocalItemBuilder localItemBuilder; - private final ArrayList localItems; + private final ItemHandler itemHandler; + private final ArrayList itemList; private final HistoryRecordManager recordManager; - private final DateFormat dateFormat; - private boolean showFooter = false; + private boolean useMiniVariant = false; private boolean useGridVariant = false; + private boolean showFooter = false; private View header = null; private View footer = null; - public LocalItemListAdapter(final Context context) { - recordManager = new HistoryRecordManager(context); - localItemBuilder = new LocalItemBuilder(context); - localItems = new ArrayList<>(); - dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, - Localization.getPreferredLocale(context)); + public ItemListAdapter(final FragmentActivity activity) { + this.recordManager = new HistoryRecordManager(activity); + itemHandler = new ItemHandler(activity, DateFormat.getDateInstance(DateFormat.SHORT, + Localization.getPreferredLocale(activity))); + itemList = new ArrayList<>(); + } + + public void setOnItemSelectedListener( + @Nullable final OnClickGesture listener) { + itemHandler.setOnItemSelectedListener(listener); } - public void setSelectedListener(final OnClickGesture listener) { - localItemBuilder.setOnItemSelectedListener(listener); + public void setUseMiniVariant(final boolean useMiniVariant) { + this.useMiniVariant = useMiniVariant; } - public void unsetSelectedListener() { - localItemBuilder.setOnItemSelectedListener(null); + public void setUseGridVariant(final boolean useGridVariant) { + this.useGridVariant = useGridVariant; } - public void addItems(@Nullable final List data) { + public void addItems(@Nullable final List data) { if (data == null) { return; } if (DEBUG) { - Log.d(TAG, "addItems() before > localItems.size() = " - + localItems.size() + ", data.size() = " + data.size()); + Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + + itemList.size() + ", data.size() = " + data.size()); } - int offsetStart = sizeConsideringHeader(); - localItems.addAll(data); + int offsetStart = sizeConsideringHeaderOffset(); + itemList.addAll(data); if (DEBUG) { - Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", " - + "localItems.size() = " + localItems.size() + ", " + Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", " + + "infoItemList.size() = " + itemList.size() + ", " + "header = " + header + ", footer = " + footer + ", " + "showFooter = " + showFooter); } notifyItemRangeInserted(offsetStart, data.size()); if (footer != null && showFooter) { - int footerNow = sizeConsideringHeader(); + int footerNow = sizeConsideringHeaderOffset(); notifyItemMoved(offsetStart, footerNow); if (DEBUG) { - Log.d(TAG, "addItems() footer from " + offsetStart + Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow); } } } + public void setItemList(final List data) { + itemList.clear(); + itemList.addAll(data); + notifyDataSetChanged(); + } + public void removeItem(final LocalItem data) { - final int index = localItems.indexOf(data); - localItems.remove(index); + final int index = itemList.indexOf(data); + itemList.remove(index); notifyItemRemoved(index + (header != null ? 1 : 0)); } @@ -136,27 +169,23 @@ public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosit if (actualFrom < 0 || actualTo < 0) { return false; } - if (actualFrom >= localItems.size() || actualTo >= localItems.size()) { + if (actualFrom >= itemList.size() || actualTo >= itemList.size()) { return false; } - localItems.add(actualTo, localItems.remove(actualFrom)); + itemList.add(actualTo, itemList.remove(actualFrom)); notifyItemMoved(fromAdapterPosition, toAdapterPosition); return true; } public void clearStreamItemList() { - if (localItems.isEmpty()) { + if (itemList.isEmpty()) { return; } - localItems.clear(); + itemList.clear(); notifyDataSetChanged(); } - public void setUseGridVariant(final boolean useGridVariant) { - this.useGridVariant = useGridVariant; - } - public void setHeader(final View header) { boolean changed = header != this.header; this.header = header; @@ -179,9 +208,9 @@ public void showFooter(final boolean show) { showFooter = show; if (show) { - notifyItemInserted(sizeConsideringHeader()); + notifyItemInserted(sizeConsideringHeaderOffset()); } else { - notifyItemRemoved(sizeConsideringHeader()); + notifyItemRemoved(sizeConsideringHeaderOffset()); } } @@ -189,17 +218,21 @@ private int adapterOffsetWithoutHeader(final int offset) { return offset - (header != null ? 1 : 0); } - private int sizeConsideringHeader() { - return localItems.size() + (header != null ? 1 : 0); + private int sizeConsideringHeaderOffset() { + int i = itemList.size() + (header != null ? 1 : 0); + if (DEBUG) { + Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i); + } + return i; } - public ArrayList getItemsList() { - return localItems; + public ArrayList getItemList() { + return itemList; } @Override public int getItemCount() { - int count = localItems.size(); + int count = itemList.size(); if (header != null) { count++; } @@ -208,8 +241,8 @@ public int getItemCount() { } if (DEBUG) { - Log.d(TAG, "getItemCount() called, count = " + count + ", " - + "localItems.size() = " + localItems.size() + ", " + Log.d(TAG, "getItemCount() called with: " + + "count = " + count + ", infoItemList.size() = " + itemList.size() + ", " + "header = " + header + ", footer = " + footer + ", " + "showFooter = " + showFooter); } @@ -227,11 +260,43 @@ public int getItemViewType(int position) { } else if (header != null) { position--; } - if (footer != null && position == localItems.size() && showFooter) { + if (footer != null && position == itemList.size() && showFooter) { return FOOTER_TYPE; } - final LocalItem item = localItems.get(position); + final Object object = itemList.get(position); + if (object instanceof InfoItem) { + return getRemoteItemViewType((InfoItem) object); + } else if (object instanceof LocalItem) { + return getLocalItemViewType((LocalItem) object); + } else { + Log.e(TAG, "No holder type has been considered for item of unknown type: [" + + object + "]"); + return -1; + } + } + + private int getRemoteItemViewType(final InfoItem item) { + switch (item.getInfoType()) { + case STREAM: + return useGridVariant ? STREAM_HOLDER_GRID_TYPE : useMiniVariant + ? STREAM_HOLDER_MINI_TYPE : STREAM_HOLDER_TYPE; + case CHANNEL: + return useGridVariant ? CHANNEL_HOLDER_GRID_TYPE : useMiniVariant + ? CHANNEL_HOLDER_MINI_TYPE : CHANNEL_HOLDER_TYPE; + case PLAYLIST: + return useGridVariant ? PLAYLIST_HOLDER_GRID_TYPE : useMiniVariant + ? PLAYLIST_HOLDER_MINI_TYPE : PLAYLIST_HOLDER_TYPE; + case COMMENT: + return useMiniVariant ? COMMENT_HOLDER_MINI_TYPE : COMMENT_HOLDER_TYPE; + default: + Log.e(TAG, "No holder type has been considered for remote item: [" + + item.getInfoType() + "]"); + return -1; + } + } + + private int getLocalItemViewType(final LocalItem item) { switch (item.getLocalItemType()) { case PLAYLIST_LOCAL_ITEM: return useGridVariant @@ -239,7 +304,6 @@ public int getItemViewType(int position) { case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE; - case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE; @@ -247,7 +311,7 @@ public int getItemViewType(int position) { return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE; default: - Log.e(TAG, "No holder type has been considered for item: [" + Log.e(TAG, "No holder type has been considered for local item: [" + item.getLocalItemType() + "]"); return -1; } @@ -263,27 +327,49 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup paren } switch (type) { case HEADER_TYPE: - return new HeaderFooterHolder(header); + return new HFHolder(header); case FOOTER_TYPE: - return new HeaderFooterHolder(footer); + return new HFHolder(footer); + case STREAM_HOLDER_MINI_TYPE: + return new StreamMiniInfoItemHolder(itemHandler, parent); + case STREAM_HOLDER_TYPE: + return new StreamInfoItemHolder(itemHandler, parent); + case STREAM_HOLDER_GRID_TYPE: + return new StreamGridInfoItemHolder(itemHandler, parent); + case CHANNEL_HOLDER_MINI_TYPE: + return new ChannelMiniInfoItemHolder(itemHandler, parent); + case CHANNEL_HOLDER_TYPE: + return new ChannelInfoItemHolder(itemHandler, parent); + case CHANNEL_HOLDER_GRID_TYPE: + return new ChannelGridInfoItemHolder(itemHandler, parent); + case PLAYLIST_HOLDER_MINI_TYPE: + return new PlaylistMiniInfoItemHolder(itemHandler, parent); + case PLAYLIST_HOLDER_TYPE: + return new PlaylistInfoItemHolder(itemHandler, parent); + case PLAYLIST_HOLDER_GRID_TYPE: + return new PlaylistGridInfoItemHolder(itemHandler, parent); + case COMMENT_HOLDER_MINI_TYPE: + return new CommentsMiniInfoItemHolder(itemHandler, parent); + case COMMENT_HOLDER_TYPE: + return new CommentsInfoItemHolder(itemHandler, parent); case LOCAL_PLAYLIST_HOLDER_TYPE: - return new LocalPlaylistItemHolder(localItemBuilder, parent); + return new LocalPlaylistItemHolder(itemHandler, parent); case LOCAL_PLAYLIST_GRID_HOLDER_TYPE: - return new LocalPlaylistGridItemHolder(localItemBuilder, parent); + return new LocalPlaylistGridItemHolder(itemHandler, parent); case REMOTE_PLAYLIST_HOLDER_TYPE: - return new RemotePlaylistItemHolder(localItemBuilder, parent); + return new RemotePlaylistItemHolder(itemHandler, parent); case REMOTE_PLAYLIST_GRID_HOLDER_TYPE: - return new RemotePlaylistGridItemHolder(localItemBuilder, parent); + return new RemotePlaylistGridItemHolder(itemHandler, parent); case STREAM_PLAYLIST_HOLDER_TYPE: - return new LocalPlaylistStreamItemHolder(localItemBuilder, parent); + return new LocalPlaylistStreamItemHolder(itemHandler, parent); case STREAM_PLAYLIST_GRID_HOLDER_TYPE: - return new LocalPlaylistStreamGridItemHolder(localItemBuilder, parent); + return new LocalPlaylistStreamGridItemHolder(itemHandler, parent); case STREAM_STATISTICS_HOLDER_TYPE: - return new LocalStatisticStreamItemHolder(localItemBuilder, parent); + return new LocalStatisticStreamItemHolder(itemHandler, parent); case STREAM_STATISTICS_GRID_HOLDER_TYPE: - return new LocalStatisticStreamGridItemHolder(localItemBuilder, parent); + return new LocalStatisticStreamGridItemHolder(itemHandler, parent); default: - Log.e(TAG, "No view type has been considered for holder: [" + type + "]"); + Log.e(TAG, "Invalid view holder type: [" + type + "]"); return new FallbackViewHolder(new View(parent.getContext())); } } @@ -295,33 +381,31 @@ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int + "holder = [" + holder.getClass().getSimpleName() + "], " + "position = [" + position + "]"); } - - if (holder instanceof LocalItemHolder) { + if (holder instanceof ItemHolder) { // If header isn't null, offset the items by -1 if (header != null) { position--; } - ((LocalItemHolder) holder) - .updateFromItem(localItems.get(position), recordManager, dateFormat); - } else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { - ((HeaderFooterHolder) holder).view = header; - } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() + ((ItemHolder) holder).updateFromObject(itemList.get(position), recordManager); + } else if (holder instanceof HFHolder && position == 0 && header != null) { + ((HFHolder) holder).view = header; + } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) { - ((HeaderFooterHolder) holder).view = footer; + ((HFHolder) holder).view = footer; } } @Override public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position, @NonNull final List payloads) { - if (!payloads.isEmpty() && holder instanceof LocalItemHolder) { + if (!payloads.isEmpty() && holder instanceof ItemHolder) { for (Object payload : payloads) { if (payload instanceof StreamStateEntity) { - ((LocalItemHolder) holder).updateState(localItems + ((ItemHolder) holder).updateStateFromObject(itemList .get(header == null ? position : position - 1), recordManager); } else if (payload instanceof Boolean) { - ((LocalItemHolder) holder).updateState(localItems + ((ItemHolder) holder).updateStateFromObject(itemList .get(header == null ? position : position - 1), recordManager); } } @@ -339,4 +423,13 @@ public int getSpanSize(final int position) { } }; } + + public class HFHolder extends RecyclerView.ViewHolder { + public View view; + + HFHolder(final View v) { + super(v); + view = v; + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ToolbarBelowItemAnimation.kt b/app/src/main/java/org/schabi/newpipe/info_list/ToolbarBelowItemAnimation.kt new file mode 100644 index 00000000000..35bbb2949e6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/ToolbarBelowItemAnimation.kt @@ -0,0 +1,58 @@ +package org.schabi.newpipe.info_list + +import android.view.View +import android.view.animation.Animation +import android.view.animation.Transformation +import android.widget.LinearLayout + +/** + * @param duration The duration of the animation, in milliseconds + * @param toolbar The view to animate + */ +class ToolbarBelowItemAnimation(duration: Int, toolbar: View) : Animation() { + private val toolbar: View + private val toolbarLayoutParams: LinearLayout.LayoutParams + private val marginStart: Int + private val marginEnd: Int + + init { + setDuration(duration.toLong()) + this.toolbar = toolbar + toolbarLayoutParams = toolbar.layoutParams as LinearLayout.LayoutParams + marginStart = toolbarLayoutParams.bottomMargin + marginEnd = if (marginStart == 0) -(toolbar.height + toolbarLayoutParams.topMargin) else 0 + toolbar.visibility = View.VISIBLE + } + + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + super.applyTransformation(interpolatedTime, t) + toolbarLayoutParams.bottomMargin = (marginStart + + ((marginEnd - marginStart) * interpolatedTime).toInt()) + + if (marginStart == 0) { + toolbar.alpha = 1.0f - interpolatedTime + if (interpolatedTime >= 1.0f) { + toolbar.visibility = View.GONE + } + + } else { + toolbar.alpha = interpolatedTime + if (interpolatedTime >= 1.0f) { + toolbar.visibility = View.VISIBLE + } + } + + toolbar.requestLayout() + } + + companion object { + @JvmStatic + fun resetToolbarBelowItem(toolbar: View) { + toolbar.visibility = View.GONE + val layoutParams = toolbar.layoutParams as LinearLayout.LayoutParams + if (layoutParams.bottomMargin == 0) { + layoutParams.bottomMargin = -(toolbar.height + layoutParams.topMargin) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ToolbarOverlayItemAnimation.kt b/app/src/main/java/org/schabi/newpipe/info_list/ToolbarOverlayItemAnimation.kt new file mode 100644 index 00000000000..f2fa9c76121 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/ToolbarOverlayItemAnimation.kt @@ -0,0 +1,67 @@ +package org.schabi.newpipe.info_list + +import android.util.Log +import android.view.View +import android.view.animation.Animation +import android.view.animation.Transformation + +/** + * @param duration The duration of the animation, in milliseconds + * @param toolbar The view to animate + * @param viewsUnderground The views under the toolbar to hide when the toolbar is shown and + * vice versa + */ +class ToolbarOverlayItemAnimation(duration: Int, + toolbar: View, + vararg viewsUnderground: View) : Animation() { + private val toolbar: View + private val viewsUnderground: Array + private val goingToShowToolbar: Boolean + + init { + setDuration(duration.toLong()) + this.toolbar = toolbar + this.viewsUnderground = viewsUnderground + goingToShowToolbar = toolbar.visibility != View.VISIBLE + } + + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + super.applyTransformation(interpolatedTime, t) + + if (goingToShowToolbar) { + toolbar.alpha = interpolatedTime + for (view in viewsUnderground) { + view.alpha = 1.0f - (1.0f - MINIMUM_UNDERGROUND_ALPHA) * interpolatedTime + } + if (interpolatedTime >= 1.0f) { + Log.e("hi", "hihi"); + toolbar.visibility = View.VISIBLE + toolbar.isClickable = true // to prevent clicks on the underground views + } + + } else { + toolbar.alpha = 1.0f - interpolatedTime + for (view in viewsUnderground) { + view.alpha = (MINIMUM_UNDERGROUND_ALPHA + + (1.0f - MINIMUM_UNDERGROUND_ALPHA) * interpolatedTime) + } + if (interpolatedTime >= 1.0f) { + resetToolbarOverlayItem(toolbar, *viewsUnderground) + } + } + } + + companion object { + private const val MINIMUM_UNDERGROUND_ALPHA = 0.4f + + @JvmStatic + fun resetToolbarOverlayItem(toolbar: View, vararg viewsUnderground: View) { + toolbar.visibility = View.INVISIBLE + toolbar.isClickable = false + for (view in viewsUnderground) { + view.alpha = 1.0f + view.visibility = View.VISIBLE + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java index a4755052ed0..a502bd4e7cf 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java @@ -3,11 +3,10 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder { - public ChannelGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, - final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_channel_grid_item, parent); + public ChannelGridInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_channel_grid_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java index 28d44733737..b05255e0828 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java @@ -4,9 +4,8 @@ import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.Localization; @@ -33,21 +32,15 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder { private final TextView itemChannelDescriptionView; - public ChannelInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_channel_item, parent); + public ChannelInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_channel_item, parent); itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView); } @Override - public void updateFromItem(final InfoItem infoItem, + public void updateFromItem(final ChannelInfoItem item, final HistoryRecordManager historyRecordManager) { - super.updateFromItem(infoItem, historyRecordManager); - - if (!(infoItem instanceof ChannelInfoItem)) { - return; - } - final ChannelInfoItem item = (ChannelInfoItem) infoItem; - + super.updateFromItem(item, historyRecordManager); itemChannelDescriptionView.setText(item.getDescription()); } @@ -56,7 +49,7 @@ protected String getDetailLine(final ChannelInfoItem item) { String details = super.getDetailLine(item); if (item.getStreamCount() >= 0) { - String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(), + String formattedVideoAmount = Localization.localizeStreamCount(itemHandler.getActivity(), item.getStreamCount()); if (!details.isEmpty()) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 9d93abcd965..e18d8df7403 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -4,68 +4,47 @@ import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolderWithToolbar; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import de.hdodenhof.circleimageview.CircleImageView; -public class ChannelMiniInfoItemHolder extends InfoItemHolder { +public class ChannelMiniInfoItemHolder extends ItemHolderWithToolbar { public final CircleImageView itemThumbnailView; public final TextView itemTitleView; private final TextView itemAdditionalDetailView; - ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + ChannelMiniInfoItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(ChannelInfoItem.class, itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemTitleView = itemView.findViewById(R.id.itemTitleView); itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails); } - public ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, - final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_channel_mini_item, parent); + public ChannelMiniInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + this(itemHandler, R.layout.list_channel_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, + public void updateFromItem(final ChannelInfoItem item, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof ChannelInfoItem)) { - return; - } - final ChannelInfoItem item = (ChannelInfoItem) infoItem; - itemTitleView.setText(item.getName()); itemAdditionalDetailView.setText(getDetailLine(item)); - itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), - itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); - - itemView.setOnClickListener(view -> { - if (itemBuilder.getOnChannelSelectedListener() != null) { - itemBuilder.getOnChannelSelectedListener().selected(item); - } - }); - - itemView.setOnLongClickListener(view -> { - if (itemBuilder.getOnChannelSelectedListener() != null) { - itemBuilder.getOnChannelSelectedListener().held(item); - } - return true; - }); + itemHandler.displayImage(item.getThumbnailUrl(), itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); } protected String getDetailLine(final ChannelInfoItem item) { String details = ""; if (item.getSubscriberCount() >= 0) { - details += Localization.shortSubscriberCount(itemBuilder.getContext(), + details += Localization.shortSubscriberCount(itemHandler.getActivity(), item.getSubscriberCount()); } return details; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java index 842d9c4551a..43b5d3e15a0 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java @@ -4,9 +4,8 @@ import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; import org.schabi.newpipe.local.history.HistoryRecordManager; /* @@ -32,22 +31,21 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { public final TextView itemTitleView; - public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_comments_item, parent); + public CommentsInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_comments_item, parent); itemTitleView = itemView.findViewById(R.id.itemTitleView); } @Override - public void updateFromItem(final InfoItem infoItem, - final HistoryRecordManager historyRecordManager) { - super.updateFromItem(infoItem, historyRecordManager); + public void updateFromObject(final Object object, + final HistoryRecordManager historyRecordManager) { + super.updateFromObject(object, historyRecordManager); - if (!(infoItem instanceof CommentsInfoItem)) { + if (!(object instanceof CommentsInfoItem)) { return; } - final CommentsInfoItem item = (CommentsInfoItem) infoItem; - itemTitleView.setText(item.getUploaderName()); + itemTitleView.setText(((CommentsInfoItem) object).getUploaderName()); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 863273a88e8..901557bbbe0 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -10,9 +10,9 @@ import androidx.appcompat.app.AppCompatActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.AndroidTvUtils; @@ -27,7 +27,7 @@ import de.hdodenhof.circleimageview.CircleImageView; -public class CommentsMiniInfoItemHolder extends InfoItemHolder { +public class CommentsMiniInfoItemHolder extends ItemHolder { private static final int COMMENT_DEFAULT_LINES = 2; private static final int COMMENT_EXPANDED_LINES = 1000; private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)"); @@ -61,9 +61,9 @@ public String transformUrl(final Matcher match, final String url) { } }; - CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + CommentsMiniInfoItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view); @@ -72,23 +72,21 @@ public String transformUrl(final Matcher match, final String url) { itemContentView = itemView.findViewById(R.id.itemCommentContentView); } - public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, + public CommentsMiniInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_comments_mini_item, parent); + this(itemHandler, R.layout.list_comments_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, - final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof CommentsInfoItem)) { + public void updateFromObject(final Object object, + final HistoryRecordManager historyRecordManager) { + if (!(object instanceof CommentsInfoItem)) { return; } - final CommentsInfoItem item = (CommentsInfoItem) infoItem; + final CommentsInfoItem item = (CommentsInfoItem) object; - itemBuilder.getImageLoader() - .displayImage(item.getUploaderAvatarUrl(), - itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + itemHandler.displayImage(item.getUploaderAvatarUrl(), itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item)); @@ -119,17 +117,17 @@ public void updateFromItem(final InfoItem infoItem, itemView.setOnClickListener(view -> { toggleEllipsize(); - if (itemBuilder.getOnCommentsSelectedListener() != null) { - itemBuilder.getOnCommentsSelectedListener().selected(item); + if (itemHandler.getOnItemSelectedListener() != null) { + itemHandler.getOnItemSelectedListener().selected(item); } }); itemView.setOnLongClickListener(view -> { - if (AndroidTvUtils.isTv(itemBuilder.getContext())) { + if (AndroidTvUtils.isTv(itemHandler.getActivity())) { openCommentAuthor(item); } else { - ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText); + ShareUtils.copyToClipboard(itemHandler.getActivity(), commentText); } return true; }); @@ -140,14 +138,14 @@ private void openCommentAuthor(final CommentsInfoItem item) { return; } try { - final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); + final AppCompatActivity activity = (AppCompatActivity) itemHandler.getActivity(); NavigationHelper.openChannelFragment( activity.getSupportFragmentManager(), item.getServiceId(), item.getUploaderUrl(), item.getUploaderName()); } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e); + ErrorActivity.reportUiError((AppCompatActivity) itemHandler.getActivity(), e); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java index 1cb69208b70..1f81f9273a5 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java @@ -3,11 +3,10 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder { - public PlaylistGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, - final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + public PlaylistGridInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_playlist_grid_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java index 7691a377ddf..b53ff021022 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java @@ -3,10 +3,10 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder { - public PlaylistInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_item, parent); + public PlaylistInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_playlist_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index d4af630623b..c92c2ffe34b 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -5,22 +5,22 @@ import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolderWithToolbar; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; -public class PlaylistMiniInfoItemHolder extends InfoItemHolder { +public class PlaylistMiniInfoItemHolder extends ItemHolderWithToolbar { public final ImageView itemThumbnailView; private final TextView itemStreamCountView; public final TextView itemTitleView; public final TextView itemUploaderView; - public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + public PlaylistMiniInfoItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(PlaylistInfoItem.class, itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemTitleView = itemView.findViewById(R.id.itemTitleView); @@ -28,40 +28,20 @@ public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final i itemUploaderView = itemView.findViewById(R.id.itemUploaderView); } - public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, + public PlaylistMiniInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_playlist_mini_item, parent); + this(itemHandler, R.layout.list_playlist_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, + public void updateFromItem(final PlaylistInfoItem item, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof PlaylistInfoItem)) { - return; - } - final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; - itemTitleView.setText(item.getName()); - itemStreamCountView.setText(Localization - .localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount())); + itemStreamCountView.setText(Localization.localizeStreamCountMini( + itemStreamCountView.getContext(), item.getStreamCount())); itemUploaderView.setText(item.getUploaderName()); - itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); - - itemView.setOnClickListener(view -> { - if (itemBuilder.getOnPlaylistSelectedListener() != null) { - itemBuilder.getOnPlaylistSelectedListener().selected(item); - } - }); - - itemView.setLongClickable(true); - itemView.setOnLongClickListener(view -> { - if (itemBuilder.getOnPlaylistSelectedListener() != null) { - itemBuilder.getOnPlaylistSelectedListener().held(item); - } - return true; - }); + itemHandler.displayImage(item.getThumbnailUrl(), itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java index 8e4a1914e2d..a5d76dcf1ad 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java @@ -3,10 +3,10 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class StreamGridInfoItemHolder extends StreamInfoItemHolder { - public StreamGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_stream_grid_item, parent); + public StreamGridInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_stream_grid_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index 5fa0904de3e..27e0e50c900 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -6,10 +6,9 @@ import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.Localization; @@ -42,45 +41,39 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { public final TextView itemAdditionalDetails; - public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_stream_item, parent); + public StreamInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + this(itemHandler, R.layout.list_stream_item, parent); } - public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + public StreamInfoItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(itemHandler, layoutId, parent); itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails); } @Override - public void updateFromItem(final InfoItem infoItem, + public void updateFromItem(final StreamInfoItem item, final HistoryRecordManager historyRecordManager) { - super.updateFromItem(infoItem, historyRecordManager); - - if (!(infoItem instanceof StreamInfoItem)) { - return; - } - final StreamInfoItem item = (StreamInfoItem) infoItem; - + super.updateFromItem(item, historyRecordManager); itemAdditionalDetails.setText(getStreamInfoDetailLine(item)); } - private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { + private String getStreamInfoDetailLine(final StreamInfoItem item) { String viewsAndDate = ""; - if (infoItem.getViewCount() >= 0) { - if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) { + if (item.getViewCount() >= 0) { + if (item.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) { viewsAndDate = Localization - .listeningCount(itemBuilder.getContext(), infoItem.getViewCount()); - } else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) { + .listeningCount(itemHandler.getActivity(), item.getViewCount()); + } else if (item.getStreamType().equals(StreamType.LIVE_STREAM)) { viewsAndDate = Localization - .shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount()); + .shortWatchingCount(itemHandler.getActivity(), item.getViewCount()); } else { viewsAndDate = Localization - .shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); + .shortViewCount(itemHandler.getActivity(), item.getViewCount()); } } - final String uploadDate = getFormattedRelativeUploadDate(infoItem); + final String uploadDate = getFormattedRelativeUploadDate(item); if (!TextUtils.isEmpty(uploadDate)) { if (viewsAndDate.isEmpty()) { return uploadDate; @@ -92,19 +85,19 @@ private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { return viewsAndDate; } - private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) { - if (infoItem.getUploadDate() != null) { + private String getFormattedRelativeUploadDate(final StreamInfoItem item) { + if (item.getUploadDate() != null) { String formattedRelativeTime = Localization - .relativeTime(infoItem.getUploadDate().date()); + .relativeTime(item.getUploadDate().date()); - if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext()) - .getBoolean(itemBuilder.getContext() + if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemHandler.getActivity()) + .getBoolean(itemHandler.getActivity() .getString(R.string.show_original_time_ago_key), false)) { - formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")"; + formattedRelativeTime += " (" + item.getTextualUploadDate() + ")"; } return formattedRelativeTime; } else { - return infoItem.getTextualUploadDate(); + return item.getTextualUploadDate(); } } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index da6c9e82f17..e4fdb6f846c 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -9,10 +9,10 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamStateEntity; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; -import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolderWithToolbar; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ImageDisplayConstants; @@ -21,16 +21,16 @@ import java.util.concurrent.TimeUnit; -public class StreamMiniInfoItemHolder extends InfoItemHolder { +public class StreamMiniInfoItemHolder extends ItemHolderWithToolbar { public final ImageView itemThumbnailView; public final TextView itemVideoTitleView; public final TextView itemUploaderView; public final TextView itemDurationView; private final AnimatedProgressBar itemProgressView; - StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + StreamMiniInfoItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(StreamInfoItem.class, itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); @@ -39,28 +39,23 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { itemProgressView = itemView.findViewById(R.id.itemProgressView); } - public StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_stream_mini_item, parent); + public StreamMiniInfoItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + this(itemHandler, R.layout.list_stream_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, + public void updateFromItem(final StreamInfoItem item, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof StreamInfoItem)) { - return; - } - final StreamInfoItem item = (StreamInfoItem) infoItem; - itemVideoTitleView.setText(item.getName()); itemUploaderView.setText(item.getUploaderName()); if (item.getDuration() > 0) { itemDurationView.setText(Localization.getDurationString(item.getDuration())); - itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemHandler.getActivity(), R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); - StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem) + StreamStateEntity state2 = historyRecordManager.loadStreamState(item) .blockingGet()[0]; if (state2 != null) { itemProgressView.setVisibility(View.VISIBLE); @@ -72,7 +67,7 @@ public void updateFromItem(final InfoItem infoItem, } } else if (item.getStreamType() == StreamType.LIVE_STREAM) { itemDurationView.setText(R.string.duration_live); - itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemHandler.getActivity(), R.color.live_duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); itemProgressView.setVisibility(View.GONE); @@ -82,38 +77,14 @@ public void updateFromItem(final InfoItem infoItem, } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), - itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); - - itemView.setOnClickListener(view -> { - if (itemBuilder.getOnStreamSelectedListener() != null) { - itemBuilder.getOnStreamSelectedListener().selected(item); - } - }); - - switch (item.getStreamType()) { - case AUDIO_STREAM: - case VIDEO_STREAM: - case LIVE_STREAM: - case AUDIO_LIVE_STREAM: - enableLongClick(item); - break; - case FILE: - case NONE: - default: - disableLongClick(); - break; - } + itemHandler.displayImage(item.getThumbnailUrl(), itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); } @Override - public void updateState(final InfoItem infoItem, - final HistoryRecordManager historyRecordManager) { - final StreamInfoItem item = (StreamInfoItem) infoItem; - - StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; + public void updateStateFromItem(final StreamInfoItem item, + final HistoryRecordManager historyRecordManager) { + StreamStateEntity state = historyRecordManager.loadStreamState(item).blockingGet()[0]; if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) { itemProgressView.setMax((int) item.getDuration()); @@ -129,19 +100,4 @@ public void updateState(final InfoItem infoItem, AnimationUtils.animateView(itemProgressView, false, 500); } } - - private void enableLongClick(final StreamInfoItem item) { - itemView.setLongClickable(true); - itemView.setOnLongClickListener(view -> { - if (itemBuilder.getOnStreamSelectedListener() != null) { - itemBuilder.getOnStreamSelectedListener().held(item); - } - return true; - }); - } - - private void disableLongClick() { - itemView.setLongClickable(false); - itemView.setOnLongClickListener(null); - } } diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 650953bea46..e758111064d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -19,6 +19,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.list.ListViewContract; +import org.schabi.newpipe.info_list.ItemListAdapter; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -44,7 +45,7 @@ public abstract class BaseLocalListFragment extends BaseStateFragment private static final int LIST_MODE_UPDATE_FLAG = 0x32; private View headerRootView; private View footerRootView; - protected LocalItemListAdapter itemListAdapter; + protected ItemListAdapter itemListAdapter; protected RecyclerView itemsList; private int updateFlags = 0; @@ -113,7 +114,7 @@ protected RecyclerView.LayoutManager getListLayoutManager() { protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - itemListAdapter = new LocalItemListAdapter(activity); + itemListAdapter = new ItemListAdapter(activity); final boolean useGrid = isGridLayout(); itemsList = rootView.findViewById(R.id.items_list); diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java deleted file mode 100644 index d7aaddcc413..00000000000 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.schabi.newpipe.local; - -import android.content.Context; -import android.widget.ImageView; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; - -import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.util.OnClickGesture; - -/* - * Created by Christian Schabesberger on 26.09.16. - *

- * Copyright (C) Christian Schabesberger 2016 - * InfoItemBuilder.java is part of NewPipe. - *

- * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - *

- * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - *

- * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class LocalItemBuilder { - private final Context context; - private final ImageLoader imageLoader = ImageLoader.getInstance(); - - private OnClickGesture onSelectedListener; - - public LocalItemBuilder(final Context context) { - this.context = context; - } - - public Context getContext() { - return context; - } - - public void displayImage(final String url, final ImageView view, - final DisplayImageOptions options) { - imageLoader.displayImage(url, view, options); - } - - public OnClickGesture getOnItemSelectedListener() { - return onSelectedListener; - } - - public void setOnItemSelectedListener(final OnClickGesture listener) { - this.onSelectedListener = listener; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index 7e11d7a2e33..40064ef6c06 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -12,14 +12,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.AppDatabase; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; @@ -27,7 +25,6 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import java.util.List; @@ -97,28 +94,9 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { protected void initListeners() { super.initListeners(); - itemListAdapter.setSelectedListener(new OnClickGesture() { + itemListAdapter.setOnItemSelectedListener(new OnClickGesture() { @Override - public void selected(final LocalItem selectedItem) { - final FragmentManager fragmentManager = getFM(); - - if (selectedItem instanceof PlaylistMetadataEntry) { - final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); - NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid, - entry.name); - - } else if (selectedItem instanceof PlaylistRemoteEntity) { - final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); - NavigationHelper.openPlaylistFragment( - fragmentManager, - entry.getServiceId(), - entry.getUrl(), - entry.getName()); - } - } - - @Override - public void held(final LocalItem selectedItem) { + public void held(final Object selectedItem) { if (selectedItem instanceof PlaylistMetadataEntry) { showLocalDialog((PlaylistMetadataEntry) selectedItem); } else if (selectedItem instanceof PlaylistRemoteEntity) { diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java deleted file mode 100644 index 4eb97bbbf6f..00000000000 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.schabi.newpipe.local.dialog; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import org.schabi.newpipe.NewPipeDatabase; -import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; -import org.schabi.newpipe.database.stream.model.StreamEntity; -import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.local.LocalItemListAdapter; -import org.schabi.newpipe.local.playlist.LocalPlaylistManager; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.util.OnClickGesture; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; - -public final class PlaylistAppendDialog extends PlaylistDialog { - private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); - - private RecyclerView playlistRecyclerView; - private LocalItemListAdapter playlistAdapter; - - private CompositeDisposable playlistDisposables = new CompositeDisposable(); - - public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) { - PlaylistAppendDialog dialog = new PlaylistAppendDialog(); - dialog.setInfo(Collections.singletonList(new StreamEntity(info))); - return dialog; - } - - public static PlaylistAppendDialog fromStreamInfoItems(final List items) { - PlaylistAppendDialog dialog = new PlaylistAppendDialog(); - List entities = new ArrayList<>(items.size()); - for (final StreamInfoItem item : items) { - entities.add(new StreamEntity(item)); - } - dialog.setInfo(entities); - return dialog; - } - - public static PlaylistAppendDialog fromPlayQueueItems(final List items) { - PlaylistAppendDialog dialog = new PlaylistAppendDialog(); - List entities = new ArrayList<>(items.size()); - for (final PlayQueueItem item : items) { - entities.add(new StreamEntity(item)); - } - dialog.setInfo(entities); - return dialog; - } - - /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - Creation - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - return inflater.inflate(R.layout.dialog_playlists, container); - } - - @Override - public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final LocalPlaylistManager playlistManager = - new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); - - playlistAdapter = new LocalItemListAdapter(getActivity()); - playlistAdapter.setSelectedListener(new OnClickGesture() { - @Override - public void selected(final LocalItem selectedItem) { - if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) { - return; - } - onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, - getStreams()); - } - }); - - playlistRecyclerView = view.findViewById(R.id.playlist_list); - playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - playlistRecyclerView.setAdapter(playlistAdapter); - - final View newPlaylistButton = view.findViewById(R.id.newPlaylist); - newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog()); - - playlistDisposables.add(playlistManager.getPlaylists() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::onPlaylistsReceived)); - } - - /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - Destruction - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onDestroyView() { - super.onDestroyView(); - playlistDisposables.dispose(); - if (playlistAdapter != null) { - playlistAdapter.unsetSelectedListener(); - } - - playlistDisposables.clear(); - playlistRecyclerView = null; - playlistAdapter = null; - } - - /*////////////////////////////////////////////////////////////////////////// - // Helper - //////////////////////////////////////////////////////////////////////////*/ - - public void openCreatePlaylistDialog() { - if (getStreams() == null || getFragmentManager() == null) { - return; - } - - PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG); - getDialog().dismiss(); - } - - private void onPlaylistsReceived(@NonNull final List playlists) { - if (playlists.isEmpty()) { - openCreatePlaylistDialog(); - return; - } - - if (playlistAdapter != null && playlistRecyclerView != null) { - playlistAdapter.clearStreamItemList(); - playlistAdapter.addItems(playlists); - playlistRecyclerView.setVisibility(View.VISIBLE); - } - } - - private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager, - @NonNull final PlaylistMetadataEntry playlist, - @NonNull final List streams) { - if (getStreams() == null) { - return; - } - - final Toast successToast = Toast.makeText(getContext(), - R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); - - if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { - playlistDisposables.add(manager - .changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> successToast.show())); - } - - playlistDisposables.add(manager.appendToPlaylist(playlist.uid, streams) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> successToast.show())); - - getDialog().dismiss(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java deleted file mode 100644 index b25ec7288a5..00000000000 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.schabi.newpipe.local.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.os.Bundle; -import android.view.View; -import android.widget.EditText; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.schabi.newpipe.NewPipeDatabase; -import org.schabi.newpipe.R; -import org.schabi.newpipe.database.stream.model.StreamEntity; -import org.schabi.newpipe.local.playlist.LocalPlaylistManager; - -import java.util.List; - -import io.reactivex.android.schedulers.AndroidSchedulers; - -public final class PlaylistCreationDialog extends PlaylistDialog { - public static PlaylistCreationDialog newInstance(final List streams) { - PlaylistCreationDialog dialog = new PlaylistCreationDialog(); - dialog.setInfo(streams); - return dialog; - } - - /*////////////////////////////////////////////////////////////////////////// - // Dialog - //////////////////////////////////////////////////////////////////////////*/ - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { - if (getStreams() == null) { - return super.onCreateDialog(savedInstanceState); - } - - View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null); - EditText nameInput = dialogView.findViewById(R.id.playlist_name); - - final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext()) - .setTitle(R.string.create_playlist) - .setView(dialogView) - .setCancelable(true) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.create, (dialogInterface, i) -> { - final String name = nameInput.getText().toString(); - final LocalPlaylistManager playlistManager = - new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); - final Toast successToast = Toast.makeText(getActivity(), - R.string.playlist_creation_success, - Toast.LENGTH_SHORT); - - playlistManager.createPlaylist(name, getStreams()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(longs -> successToast.show()); - }); - - return dialogBuilder.create(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java deleted file mode 100644 index 9ca8733ccd8..00000000000 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.schabi.newpipe.local.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.Window; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; - -import org.schabi.newpipe.database.stream.model.StreamEntity; -import org.schabi.newpipe.util.StateSaver; - -import java.util.List; -import java.util.Queue; - -public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead { - private List streamEntities; - - private StateSaver.SavedState savedState; - - protected void setInfo(final List entities) { - this.streamEntities = entities; - } - - protected List getStreams() { - return streamEntities; - } - - /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - savedState = StateSaver.tryToRestore(savedInstanceState, this); - } - - @Override - public void onDestroy() { - super.onDestroy(); - StateSaver.onDestroy(savedState); - } - - @NonNull - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final Dialog dialog = super.onCreateDialog(savedInstanceState); - //remove title - final Window window = dialog.getWindow(); - if (window != null) { - window.requestFeature(Window.FEATURE_NO_TITLE); - } - return dialog; - } - - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public String generateSuffix() { - final int size = streamEntities == null ? 0 : streamEntities.size(); - return "." + size + ".list"; - } - - @Override - public void writeTo(final Queue objectsToSave) { - objectsToSave.add(streamEntities); - } - - @Override - @SuppressWarnings("unchecked") - public void readFrom(@NonNull final Queue savedObjects) { - streamEntities = (List) savedObjects.poll(); - } - - @Override - public void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - if (getActivity() != null) { - savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(), - savedState, outState, this); - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 8018e2cd819..cbc56d3018a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -201,7 +201,7 @@ class FeedFragment : BaseListFragment() { } override fun showError(message: String, showRetryButton: Boolean) { - infoListAdapter.clearStreamItemList() + itemListAdapter.clearStreamItemList() animateView(refresh_root_view, false, 120) animateView(items_list, false, 120) @@ -245,7 +245,7 @@ class FeedFragment : BaseListFragment() { } private fun handleLoadedState(loadedState: FeedState.LoadedState) { - infoListAdapter.setInfoItemList(loadedState.items) + itemListAdapter.setItemList(loadedState.items) listState?.run { items_list.layoutManager?.onRestoreInstanceState(listState) listState = null @@ -283,7 +283,7 @@ class FeedFragment : BaseListFragment() { private fun updateRelativeTimeViews() { updateRefreshViewState() - infoListAdapter.notifyDataSetChanged() + itemListAdapter.notifyDataSetChanged() } private fun updateRefreshViewState() { diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 582be00d9bc..55387367174 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -23,7 +23,6 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; @@ -35,7 +34,6 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.util.ThemeHelper; @@ -137,31 +135,6 @@ protected View getListHeader() { return headerRootLayout; } - @Override - protected void initListeners() { - super.initListeners(); - - itemListAdapter.setSelectedListener(new OnClickGesture() { - @Override - public void selected(final LocalItem selectedItem) { - if (selectedItem instanceof StreamStatisticsEntry) { - final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem; - NavigationHelper.openVideoDetailFragment(getFM(), - item.getStreamEntity().getServiceId(), - item.getStreamEntity().getUrl(), - item.getStreamEntity().getTitle()); - } - } - - @Override - public void held(final LocalItem selectedItem) { - if (selectedItem instanceof StreamStatisticsEntry) { - showStreamDialog((StreamStatisticsEntry) selectedItem); - } - } - }); - } - @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { @@ -237,7 +210,7 @@ public void onDestroyView() { super.onDestroyView(); if (itemListAdapter != null) { - itemListAdapter.unsetSelectedListener(); + itemListAdapter.setOnItemSelectedListener(null); } if (headerBackgroundButton != null) { headerBackgroundButton.setOnClickListener(null); @@ -376,7 +349,7 @@ private void toggleSortMode() { } private PlayQueue getPlayQueueStartingAt(final StreamStatisticsEntry infoItem) { - return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); + return getPlayQueue(Math.max(itemListAdapter.getItemList().indexOf(infoItem), 0)); } private void showStreamDialog(final StreamStatisticsEntry item) { @@ -413,17 +386,16 @@ private void showStreamDialog(final StreamStatisticsEntry item) { NavigationHelper .playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) -> - deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0))); + deleteEntry(Math.max(itemListAdapter.getItemList().indexOf(item), 0))); new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) -> StreamDialogEntry.clickOn(which, this, infoItem)).show(); } private void deleteEntry(final int index) { - final LocalItem infoItem = itemListAdapter.getItemsList() - .get(index); - if (infoItem instanceof StreamStatisticsEntry) { - final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem; + final Object item = itemListAdapter.getItemList().get(index); + if (item instanceof StreamStatisticsEntry) { + final StreamStatisticsEntry entry = (StreamStatisticsEntry) item; final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( @@ -454,9 +426,9 @@ private PlayQueue getPlayQueue(final int index) { return new SinglePlayQueue(Collections.emptyList(), 0); } - final List infoItems = itemListAdapter.getItemsList(); - List streamInfoItems = new ArrayList<>(infoItems.size()); - for (final LocalItem item : infoItems) { + final List items = itemListAdapter.getItemList(); + List streamInfoItems = new ArrayList<>(items.size()); + for (final Object item : items) { if (item instanceof StreamStatisticsEntry) { streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem()); } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java deleted file mode 100644 index c4307fcde10..00000000000 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.schabi.newpipe.local.holder; - -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.recyclerview.widget.RecyclerView; - -import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.local.LocalItemBuilder; -import org.schabi.newpipe.local.history.HistoryRecordManager; - -import java.text.DateFormat; - -/* - * Created by Christian Schabesberger on 12.02.17. - * - * Copyright (C) Christian Schabesberger 2016 - * InfoItemHolder.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public abstract class LocalItemHolder extends RecyclerView.ViewHolder { - protected final LocalItemBuilder itemBuilder; - - public LocalItemHolder(final LocalItemBuilder itemBuilder, final int layoutId, - final ViewGroup parent) { - super(LayoutInflater.from(itemBuilder.getContext()).inflate(layoutId, parent, false)); - this.itemBuilder = itemBuilder; - } - - public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager, - DateFormat dateFormat); - - public void updateState(final LocalItem localItem, - final HistoryRecordManager historyRecordManager) { } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java index 2b493f4eec3..772ceb3e090 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java @@ -3,11 +3,10 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder { - public LocalPlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder, - final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + public LocalPlaylistGridItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_playlist_grid_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index 458b3c30eae..41bb26dec0f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -3,42 +3,31 @@ import android.view.View; import android.view.ViewGroup; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; -import java.text.DateFormat; - -public class LocalPlaylistItemHolder extends PlaylistItemHolder { - public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) { - super(infoItemBuilder, parent); +public class LocalPlaylistItemHolder extends PlaylistItemHolder { + public LocalPlaylistItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(PlaylistMetadataEntry.class, itemHandler, parent); } - LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + LocalPlaylistItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(PlaylistMetadataEntry.class, itemHandler, layoutId, parent); } @Override - public void updateFromItem(final LocalItem localItem, - final HistoryRecordManager historyRecordManager, - final DateFormat dateFormat) { - if (!(localItem instanceof PlaylistMetadataEntry)) { - return; - } - final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; - + public void updateFromItem(final PlaylistMetadataEntry item, + final HistoryRecordManager historyRecordManager) { itemTitleView.setText(item.name); itemStreamCountView.setText(Localization.localizeStreamCountMini( itemStreamCountView.getContext(), item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); - itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, + itemHandler.displayImage(item.thumbnailUrl, itemThumbnailView, ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); - - super.updateFromItem(localItem, historyRecordManager, dateFormat); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java index e2f93679234..1808a7da63d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java @@ -3,11 +3,11 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder { - public LocalPlaylistStreamGridItemHolder(final LocalItemBuilder infoItemBuilder, + public LocalPlaylistStreamGridItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); // TODO + super(itemHandler, R.layout.list_stream_playlist_grid_item, parent); // TODO } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index ece5f099448..1f71be378ee 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -13,18 +13,18 @@ import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolderWithToolbar; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.views.AnimatedProgressBar; -import java.text.DateFormat; import java.util.ArrayList; import java.util.concurrent.TimeUnit; -public class LocalPlaylistStreamItemHolder extends LocalItemHolder { +public class LocalPlaylistStreamItemHolder extends ItemHolderWithToolbar { public final ImageView itemThumbnailView; public final TextView itemVideoTitleView; private final TextView itemAdditionalDetailsView; @@ -32,9 +32,9 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { private final View itemHandleView; private final AnimatedProgressBar itemProgressView; - LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + LocalPlaylistStreamItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(PlaylistStreamEntry.class, itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); @@ -44,20 +44,13 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { itemProgressView = itemView.findViewById(R.id.itemProgressView); } - public LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder, - final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_stream_playlist_item, parent); + public LocalPlaylistStreamItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + this(itemHandler, R.layout.list_stream_playlist_item, parent); } @Override - public void updateFromItem(final LocalItem localItem, - final HistoryRecordManager historyRecordManager, - final DateFormat dateFormat) { - if (!(localItem instanceof PlaylistStreamEntry)) { - return; - } - final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; - + public void updateFromItem(final PlaylistStreamEntry item, + final HistoryRecordManager historyRecordManager) { itemVideoTitleView.setText(item.getStreamEntity().getTitle()); itemAdditionalDetailsView.setText(Localization .concatenateStrings(item.getStreamEntity().getUploader(), @@ -66,13 +59,13 @@ public void updateFromItem(final LocalItem localItem, if (item.getStreamEntity().getDuration() > 0) { itemDurationView.setText(Localization .getDurationString(item.getStreamEntity().getDuration())); - itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemHandler.getActivity(), R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); StreamStateEntity state = historyRecordManager .loadLocalStreamStateBatch(new ArrayList() {{ - add(localItem); + add(item); }}).blockingGet().get(0); if (state != null) { itemProgressView.setVisibility(View.VISIBLE); @@ -87,38 +80,19 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView, + itemHandler.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); - itemView.setOnClickListener(view -> { - if (itemBuilder.getOnItemSelectedListener() != null) { - itemBuilder.getOnItemSelectedListener().selected(item); - } - }); - - itemView.setLongClickable(true); - itemView.setOnLongClickListener(view -> { - if (itemBuilder.getOnItemSelectedListener() != null) { - itemBuilder.getOnItemSelectedListener().held(item); - } - return true; - }); - itemThumbnailView.setOnTouchListener(getOnTouchListener(item)); itemHandleView.setOnTouchListener(getOnTouchListener(item)); } @Override - public void updateState(final LocalItem localItem, - final HistoryRecordManager historyRecordManager) { - if (!(localItem instanceof PlaylistStreamEntry)) { - return; - } - final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; - + public void updateStateFromItem(final PlaylistStreamEntry item, + final HistoryRecordManager historyRecordManager) { StreamStateEntity state = historyRecordManager .loadLocalStreamStateBatch(new ArrayList() {{ - add(localItem); + add(item); }}).blockingGet().get(0); if (state != null && item.getStreamEntity().getDuration() > 0) { itemProgressView.setMax((int) item.getStreamEntity().getDuration()); @@ -138,9 +112,9 @@ public void updateState(final LocalItem localItem, private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) { return (view, motionEvent) -> { view.performClick(); - if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null + if (itemHandler != null && itemHandler.getOnItemSelectedListener() != null && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { - itemBuilder.getOnItemSelectedListener().drag(item, + itemHandler.getOnItemSelectedListener().drag(item, LocalPlaylistStreamItemHolder.this); } return false; diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java index 39a43b0344f..603136f44bf 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java @@ -3,11 +3,11 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class LocalStatisticStreamGridItemHolder extends LocalStatisticStreamItemHolder { - public LocalStatisticStreamGridItemHolder(final LocalItemBuilder infoItemBuilder, + public LocalStatisticStreamGridItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_stream_grid_item, parent); + super(itemHandler, R.layout.list_stream_grid_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java index a83c6ba6746..36521708968 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java @@ -13,7 +13,8 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolderWithToolbar; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ImageDisplayConstants; @@ -44,7 +45,7 @@ * along with NewPipe. If not, see . */ -public class LocalStatisticStreamItemHolder extends LocalItemHolder { +public class LocalStatisticStreamItemHolder extends ItemHolderWithToolbar { public final ImageView itemThumbnailView; public final TextView itemVideoTitleView; public final TextView itemUploaderView; @@ -53,14 +54,14 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { public final TextView itemAdditionalDetails; private final AnimatedProgressBar itemProgressView; - public LocalStatisticStreamItemHolder(final LocalItemBuilder itemBuilder, + public LocalStatisticStreamItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { - this(itemBuilder, R.layout.list_stream_item, parent); + this(itemHandler, R.layout.list_stream_item, parent); } - LocalStatisticStreamItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + LocalStatisticStreamItemHolder(final ItemHandler itemHandler, final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(StreamStatisticsEntry.class, itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); @@ -73,34 +74,28 @@ public LocalStatisticStreamItemHolder(final LocalItemBuilder itemBuilder, private String getStreamInfoDetailLine(final StreamStatisticsEntry entry, final DateFormat dateFormat) { final String watchCount = Localization - .shortViewCount(itemBuilder.getContext(), entry.getWatchCount()); + .shortViewCount(itemHandler.getActivity(), entry.getWatchCount()); final String uploadDate = dateFormat.format(entry.getLatestAccessDate()); final String serviceName = NewPipe.getNameOfService(entry.getStreamEntity().getServiceId()); return Localization.concatenateStrings(watchCount, uploadDate, serviceName); } @Override - public void updateFromItem(final LocalItem localItem, - final HistoryRecordManager historyRecordManager, - final DateFormat dateFormat) { - if (!(localItem instanceof StreamStatisticsEntry)) { - return; - } - final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; - + public void updateFromItem(final StreamStatisticsEntry item, + final HistoryRecordManager historyRecordManager) { itemVideoTitleView.setText(item.getStreamEntity().getTitle()); itemUploaderView.setText(item.getStreamEntity().getUploader()); if (item.getStreamEntity().getDuration() > 0) { - itemDurationView. - setText(Localization.getDurationString(item.getStreamEntity().getDuration())); - itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + itemDurationView.setText( + Localization.getDurationString(item.getStreamEntity().getDuration())); + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemHandler.getActivity(), R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); StreamStateEntity state = historyRecordManager .loadLocalStreamStateBatch(new ArrayList() {{ - add(localItem); + add(item); }}).blockingGet().get(0); if (state != null) { itemProgressView.setVisibility(View.VISIBLE); @@ -115,40 +110,22 @@ public void updateFromItem(final LocalItem localItem, itemProgressView.setVisibility(View.GONE); } - if (itemAdditionalDetails != null) { - itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat)); + if (itemAdditionalDetails != null && itemHandler.getDateFormat() != null) { + itemAdditionalDetails.setText(getStreamInfoDetailLine(item, + itemHandler.getDateFormat())); } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView, + itemHandler.displayImage(item.getStreamEntity().getThumbnailUrl(), itemThumbnailView, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); - - itemView.setOnClickListener(view -> { - if (itemBuilder.getOnItemSelectedListener() != null) { - itemBuilder.getOnItemSelectedListener().selected(item); - } - }); - - itemView.setLongClickable(true); - itemView.setOnLongClickListener(view -> { - if (itemBuilder.getOnItemSelectedListener() != null) { - itemBuilder.getOnItemSelectedListener().held(item); - } - return true; - }); } @Override - public void updateState(final LocalItem localItem, - final HistoryRecordManager historyRecordManager) { - if (!(localItem instanceof StreamStatisticsEntry)) { - return; - } - final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; - + public void updateStateFromItem(final StreamStatisticsEntry item, + final HistoryRecordManager historyRecordManager) { StreamStateEntity state = historyRecordManager .loadLocalStreamStateBatch(new ArrayList() {{ - add(localItem); + add(item); }}).blockingGet().get(0); if (state != null && item.getStreamEntity().getDuration() > 0) { itemProgressView.setMax((int) item.getStreamEntity().getDuration()); diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java index 11e3deb6738..d738c3874ef 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java @@ -5,21 +5,22 @@ import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.local.LocalItemBuilder; -import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.database.playlist.PlaylistLocalItem; +import org.schabi.newpipe.info_list.ItemHandler; +import org.schabi.newpipe.info_list.ItemHolderWithToolbar; -import java.text.DateFormat; - -public abstract class PlaylistItemHolder extends LocalItemHolder { +public abstract class PlaylistItemHolder + extends ItemHolderWithToolbar { public final ImageView itemThumbnailView; final TextView itemStreamCountView; public final TextView itemTitleView; public final TextView itemUploaderView; - public PlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + public PlaylistItemHolder(final Class itemClass, + final ItemHandler itemHandler, + final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(itemClass, itemHandler, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemTitleView = itemView.findViewById(R.id.itemTitleView); @@ -27,26 +28,9 @@ public PlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layo itemUploaderView = itemView.findViewById(R.id.itemUploaderView); } - public PlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) { - this(infoItemBuilder, R.layout.list_playlist_mini_item, parent); - } - - @Override - public void updateFromItem(final LocalItem localItem, - final HistoryRecordManager historyRecordManager, - final DateFormat dateFormat) { - itemView.setOnClickListener(view -> { - if (itemBuilder.getOnItemSelectedListener() != null) { - itemBuilder.getOnItemSelectedListener().selected(localItem); - } - }); - - itemView.setLongClickable(true); - itemView.setOnLongClickListener(view -> { - if (itemBuilder.getOnItemSelectedListener() != null) { - itemBuilder.getOnItemSelectedListener().held(localItem); - } - return true; - }); + public PlaylistItemHolder(final Class itemClass, + final ItemHandler itemHandler, + final ViewGroup parent) { + this(itemClass, itemHandler, R.layout.list_playlist_mini_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java index 00dcefbda32..5262f28ec1f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java @@ -3,11 +3,10 @@ import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; public class RemotePlaylistGridItemHolder extends RemotePlaylistItemHolder { - public RemotePlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder, - final ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + public RemotePlaylistGridItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(itemHandler, R.layout.list_playlist_grid_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index a47d61d2f72..094c1b1edc6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -3,39 +3,31 @@ import android.text.TextUtils; import android.view.ViewGroup; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.local.LocalItemBuilder; +import org.schabi.newpipe.info_list.ItemHandler; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; -import java.text.DateFormat; - -public class RemotePlaylistItemHolder extends PlaylistItemHolder { - public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, - final ViewGroup parent) { - super(infoItemBuilder, parent); +public class RemotePlaylistItemHolder extends PlaylistItemHolder { + public RemotePlaylistItemHolder(final ItemHandler itemHandler, final ViewGroup parent) { + super(PlaylistRemoteEntity.class, itemHandler, parent); } - RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + RemotePlaylistItemHolder(final ItemHandler itemHandler, + final int layoutId, final ViewGroup parent) { - super(infoItemBuilder, layoutId, parent); + super(PlaylistRemoteEntity.class, itemHandler, layoutId, parent); } @Override - public void updateFromItem(final LocalItem localItem, - final HistoryRecordManager historyRecordManager, - final DateFormat dateFormat) { - if (!(localItem instanceof PlaylistRemoteEntity)) { - return; - } - final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; - + public void updateFromItem(final PlaylistRemoteEntity item, + final HistoryRecordManager historyRecordManager) { itemTitleView.setText(item.getName()); itemStreamCountView.setText(Localization.localizeStreamCountMini( itemStreamCountView.getContext(), item.getStreamCount())); + // Here is where the uploader name is set in the bookmarked playlists library if (!TextUtils.isEmpty(item.getUploader())) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), @@ -44,10 +36,7 @@ public void updateFromItem(final LocalItem localItem, itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); } - - itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView, + itemHandler.displayImage(item.getThumbnailUrl(), itemThumbnailView, ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); - - super.updateFromItem(localItem, historyRecordManager, dateFormat); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 485d3f39143..589d6f47a48 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -27,7 +27,6 @@ import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.stream.model.StreamStateEntity; @@ -110,7 +109,7 @@ public static LocalPlaylistFragment getInstance(final long playlistId, final Str @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); + playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext())); debouncedSaveSignal = PublishSubject.create(); disposables = new CompositeDisposable(); @@ -172,26 +171,10 @@ protected void initListeners() { itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(itemsList); - itemListAdapter.setSelectedListener(new OnClickGesture() { - @Override - public void selected(final LocalItem selectedItem) { - if (selectedItem instanceof PlaylistStreamEntry) { - final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem; - NavigationHelper.openVideoDetailFragment(getFragmentManager(), - item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(), - item.getStreamEntity().getTitle()); - } - } + itemListAdapter.setOnItemSelectedListener(new OnClickGesture() { @Override - public void held(final LocalItem selectedItem) { - if (selectedItem instanceof PlaylistStreamEntry) { - showStreamItemDialog((PlaylistStreamEntry) selectedItem); - } - } - - @Override - public void drag(final LocalItem selectedItem, + public void drag(final Object selectedItem, final RecyclerView.ViewHolder viewHolder) { if (itemTouchHelper != null) { itemTouchHelper.startDrag(viewHolder); @@ -230,7 +213,9 @@ public void hideLoading() { public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); - if (disposables != null) { + if (disposables == null) { + disposables = new CompositeDisposable(); + } else { disposables.clear(); } disposables.add(getDebouncedSaver()); @@ -272,7 +257,7 @@ public void onDestroyView() { super.onDestroyView(); if (itemListAdapter != null) { - itemListAdapter.unsetSelectedListener(); + itemListAdapter.setOnItemSelectedListener(null); } if (headerBackgroundButton != null) { headerBackgroundButton.setOnClickListener(null); @@ -459,7 +444,7 @@ public void removeWatchedStreams(final boolean removePartiallyWatched) { updateThumbnailUrl(); } - final long videoCount = itemListAdapter.getItemsList().size(); + final long videoCount = itemListAdapter.getItemList().size(); setVideoCount(videoCount); if (videoCount == 0) { showEmptyState(); @@ -489,7 +474,7 @@ public void handleResult(@NonNull final List result) { itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); itemsListState = null; } - setVideoCount(itemListAdapter.getItemsList().size()); + setVideoCount(itemListAdapter.getItemList().size()); headerPlayAllButton.setOnClickListener(view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); @@ -603,8 +588,8 @@ private void changeThumbnailUrl(final String thumbnailUrl) { private void updateThumbnailUrl() { String newThumbnailUrl; - if (!itemListAdapter.getItemsList().isEmpty()) { - newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0)) + if (!itemListAdapter.getItemList().isEmpty()) { + newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemList().get(0)) .getStreamEntity().getThumbnailUrl(); } else { newThumbnailUrl = "drawable://" + R.drawable.dummy_thumbnail_playlist; @@ -624,7 +609,7 @@ private void deleteItem(final PlaylistStreamEntry item) { updateThumbnailUrl(); } - setVideoCount(itemListAdapter.getItemsList().size()); + setVideoCount(itemListAdapter.getItemList().size()); saveChanges(); } @@ -661,9 +646,9 @@ private void saveImmediate() { return; } - final List items = itemListAdapter.getItemsList(); + final List items = itemListAdapter.getItemList(); List streamIds = new ArrayList<>(items.size()); - for (final LocalItem item : items) { + for (final Object item : items) { if (item instanceof PlaylistStreamEntry) { streamIds.add(((PlaylistStreamEntry) item).getStreamId()); } @@ -746,7 +731,7 @@ public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDi //////////////////////////////////////////////////////////////////////////*/ private PlayQueue getPlayQueueStartingAt(final PlaylistStreamEntry infoItem) { - return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); + return getPlayQueue(Math.max(itemListAdapter.getItemList().indexOf(infoItem), 0)); } protected void showStreamItemDialog(final PlaylistStreamEntry item) { @@ -814,9 +799,9 @@ private PlayQueue getPlayQueue(final int index) { return new SinglePlayQueue(Collections.emptyList(), 0); } - final List infoItems = itemListAdapter.getItemsList(); - List streamInfoItems = new ArrayList<>(infoItems.size()); - for (final LocalItem item : infoItems) { + final List items = itemListAdapter.getItemList(); + List streamInfoItems = new ArrayList<>(items.size()); + for (final Object item : items) { if (item instanceof PlaylistStreamEntry) { streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem()); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 7fea3b5d848..f03f27bc37c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -435,7 +435,7 @@ class SubscriptionFragment : BaseStateFragment() { } private fun getGridSpanCount(): Int { - val minWidth = resources.getDimensionPixelSize(R.dimen.channel_item_grid_min_width) + val minWidth = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width) return max(1, floor(resources.displayMetrics.widthPixels / minWidth.toDouble()).toInt()) } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 72becef8ff1..09591acf377 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -30,7 +30,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; -import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; +import org.schabi.newpipe.fragments.list.playlist.AppendPlaylistDialog; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; @@ -551,7 +551,7 @@ private void appendAllToPlaylist() { } private void openPlaylistAppendDialog(final List playlist) { - PlaylistAppendDialog.fromPlayQueueItems(playlist) + AppendPlaylistDialog.fromPlayQueueItems(playlist) .show(getSupportFragmentManager(), getTag()); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 3c15cd3422d..c2a2f5cafe7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -3,6 +3,7 @@ import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; @@ -50,7 +51,8 @@ public boolean isComplete() { return isComplete; } - SingleObserver getHeadListObserver() { + SingleObserver getHeadListObserver( + @Nullable final Runnable runnable) { return new SingleObserver() { @Override public void onSubscribe(@NonNull final Disposable d) { @@ -71,6 +73,9 @@ public void onSuccess(@NonNull final T result) { nextPage = result.getNextPage(); append(extractListItems(result.getRelatedItems())); + if (runnable != null) { + runnable.run(); + } fetchReactor.dispose(); fetchReactor = null; @@ -80,12 +85,17 @@ public void onSuccess(@NonNull final T result) { public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; + append(); // Notify change + if (runnable != null) { + runnable.run(); + } } }; } - SingleObserver getNextPageObserver() { + SingleObserver getNextPageObserver( + @Nullable final Runnable runnable) { return new SingleObserver() { @Override public void onSubscribe(@NonNull final Disposable d) { @@ -98,13 +108,17 @@ public void onSubscribe(@NonNull final Disposable d) { } @Override - public void onSuccess(@NonNull final ListExtractor.InfoItemsPage result) { + public void onSuccess( + @NonNull final ListExtractor.InfoItemsPage result) { if (!result.hasNextPage()) { isComplete = true; } nextPage = result.getNextPage(); append(extractListItems(result.getItems())); + if (runnable != null) { + runnable.run(); + } fetchReactor.dispose(); fetchReactor = null; @@ -114,7 +128,11 @@ public void onSuccess(@NonNull final ListExtractor.InfoItemsPage result) { public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; + append(); // Notify change + if (runnable != null) { + runnable.run(); + } } }; } @@ -129,7 +147,7 @@ public void dispose() { } private static List extractListItems(final List infos) { - List result = new ArrayList<>(); + final List result = new ArrayList<>(); for (final InfoItem stream : infos) { if (stream instanceof StreamInfoItem) { result.add(new PlayQueueItem((StreamInfoItem) stream)); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelPlayQueue.java index 9e0d2b6942b..8b4f15709ef 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelPlayQueue.java @@ -1,6 +1,8 @@ package org.schabi.newpipe.player.playqueue; +import androidx.annotation.Nullable; + import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; @@ -35,17 +37,17 @@ protected String getTag() { } @Override - public void fetch() { + public void fetch(@Nullable final Runnable runnable) { if (this.isInitial) { ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getHeadListObserver()); + .subscribe(getHeadListObserver(runnable)); } else { ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextPage) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getNextPageObserver()); + .subscribe(getNextPageObserver(runnable)); } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/LocalPlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/LocalPlaylistPlayQueue.java new file mode 100644 index 00000000000..d721553e839 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/LocalPlaylistPlayQueue.java @@ -0,0 +1,79 @@ +package org.schabi.newpipe.player.playqueue; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.Nullable; + +import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.NewPipeDatabase; +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; +import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.Flowable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; + +public class LocalPlaylistPlayQueue extends PlayQueue { + // TODO not sure if context can be made transient, because then it would be lost on instance + // saving, but there were no errors + private final transient Context context; + private final long playlistId; + + private boolean isComplete = false; + private transient Disposable disposable; + + public LocalPlaylistPlayQueue(final Context context, + final PlaylistMetadataEntry playlistMetadataEntry) { + super(0, Collections.emptyList()); + this.context = context; + playlistId = playlistMetadataEntry.uid; + } + + @Override + public boolean isComplete() { + return isComplete; + } + + @Override + public void fetch(@Nullable final Runnable runnable) { + final LocalPlaylistManager playlistManager = + new LocalPlaylistManager(NewPipeDatabase.getInstance(context)); + + disposable = playlistManager.getPlaylistStreams(playlistId) + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + streams -> { + List playQueueItems = new ArrayList<>(streams.size()); + for (final PlaylistStreamEntry stream : streams) { + playQueueItems.add(new PlayQueueItem(stream.toStreamInfoItem())); + } + + isComplete = true; + append(playQueueItems); + // TODO calling runnable here creates strange loops + }, + t -> ErrorActivity.reportError(context, t, MainActivity.class, null, + ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, + "none", "Local Playlist", R.string.general_error)) + ); + } + + @Override + public void dispose() { + super.dispose(); + if (disposable != null) { + disposable.dispose(); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index 7391294ba7a..7f48819362d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -115,7 +115,16 @@ public void dispose() { /** * Load partial queue in the background, does nothing if the queue is complete. */ - public abstract void fetch(); + public final void fetch() { + fetch(null); + } + + /** + * Load partial queue in the background, does nothing if the queue is complete. + * @param runnable if not null, it will be called once the fetch has finished (or instantly if + * the queue is complete) + */ + public abstract void fetch(@Nullable final Runnable runnable); /*////////////////////////////////////////////////////////////////////////// // Readonly ops diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java index 0777027470d..1e5f0476a4a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.playqueue; +import androidx.annotation.Nullable; + import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; @@ -34,17 +36,17 @@ protected String getTag() { } @Override - public void fetch() { + public void fetch(@Nullable final Runnable runnable) { if (this.isInitial) { ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getHeadListObserver()); + .subscribe(getHeadListObserver(runnable)); } else { ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextPage) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getNextPageObserver()); + .subscribe(getNextPageObserver(runnable)); } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java index 79cf0601c39..b0121954866 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.playqueue; +import androidx.annotation.Nullable; + import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; @@ -39,6 +41,9 @@ public boolean isComplete() { } @Override - public void fetch() { + public void fetch(@Nullable final Runnable runnable) { + if (runnable != null) { + runnable.run(); + } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java deleted file mode 100644 index 1d5c94421a7..00000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ /dev/null @@ -1,225 +0,0 @@ -package org.schabi.newpipe.settings; - -import android.app.Activity; -import android.content.DialogInterface; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; - -import org.schabi.newpipe.NewPipeDatabase; -import org.schabi.newpipe.R; -import org.schabi.newpipe.database.AppDatabase; -import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.database.playlist.PlaylistLocalItem; -import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; -import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import org.schabi.newpipe.local.playlist.LocalPlaylistManager; -import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.UserAction; - -import java.util.List; -import java.util.Vector; - -import io.reactivex.Flowable; -import io.reactivex.disposables.Disposable; - -public class SelectPlaylistFragment extends DialogFragment { - /** - * This contains the base display options for images. - */ - private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS - = new DisplayImageOptions.Builder().cacheInMemory(true).build(); - - private final ImageLoader imageLoader = ImageLoader.getInstance(); - - private OnSelectedListener onSelectedListener = null; - private OnCancelListener onCancelListener = null; - - private ProgressBar progressBar; - private TextView emptyView; - private RecyclerView recyclerView; - private Disposable playlistsSubscriber; - - private List playlists = new Vector<>(); - - public void setOnSelectedListener(final OnSelectedListener listener) { - onSelectedListener = listener; - } - - public void setOnCancelListener(final OnCancelListener listener) { - onCancelListener = listener; - } - - /*////////////////////////////////////////////////////////////////////////// - // Fragment's Lifecycle - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, - final Bundle savedInstanceState) { - final View v = - inflater.inflate(R.layout.select_playlist_fragment, container, false); - recyclerView = v.findViewById(R.id.items_list); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter(); - recyclerView.setAdapter(playlistAdapter); - - progressBar = v.findViewById(R.id.progressBar); - emptyView = v.findViewById(R.id.empty_state_view); - progressBar.setVisibility(View.VISIBLE); - recyclerView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); - - final AppDatabase database = NewPipeDatabase.getInstance(requireContext()); - final LocalPlaylistManager localPlaylistManager = new LocalPlaylistManager(database); - final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database); - - playlistsSubscriber = Flowable.combineLatest(localPlaylistManager.getPlaylists(), - remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge) - .subscribe(this::displayPlaylists, this::onError); - - return v; - } - - @Override - public void onDestroy() { - super.onDestroy(); - - if (playlistsSubscriber != null) { - playlistsSubscriber.dispose(); - playlistsSubscriber = null; - } - } - - /*////////////////////////////////////////////////////////////////////////// - // Handle actions - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onCancel(final DialogInterface dialogInterface) { - super.onCancel(dialogInterface); - if (onCancelListener != null) { - onCancelListener.onCancel(); - } - } - - private void clickedItem(final int position) { - if (onSelectedListener != null) { - final LocalItem selectedItem = playlists.get(position); - - if (selectedItem instanceof PlaylistMetadataEntry) { - final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); - onSelectedListener - .onLocalPlaylistSelected(entry.uid, entry.name); - - } else if (selectedItem instanceof PlaylistRemoteEntity) { - final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); - onSelectedListener.onRemotePlaylistSelected( - entry.getServiceId(), entry.getUrl(), entry.getName()); - } - } - dismiss(); - } - - /*////////////////////////////////////////////////////////////////////////// - // Item handling - //////////////////////////////////////////////////////////////////////////*/ - - private void displayPlaylists(final List newPlaylists) { - this.playlists = newPlaylists; - progressBar.setVisibility(View.GONE); - if (newPlaylists.isEmpty()) { - emptyView.setVisibility(View.VISIBLE); - return; - } - recyclerView.setVisibility(View.VISIBLE); - - } - - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - - protected void onError(final Throwable e) { - final Activity activity = getActivity(); - ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo - .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); - } - - /*////////////////////////////////////////////////////////////////////////// - // Interfaces - //////////////////////////////////////////////////////////////////////////*/ - - public interface OnSelectedListener { - void onLocalPlaylistSelected(long id, String name); - void onRemotePlaylistSelected(int serviceId, String url, String name); - } - - public interface OnCancelListener { - void onCancel(); - } - - private class SelectPlaylistAdapter - extends RecyclerView.Adapter { - @Override - public SelectPlaylistItemHolder onCreateViewHolder(final ViewGroup parent, - final int viewType) { - final View item = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.list_playlist_mini_item, parent, false); - return new SelectPlaylistItemHolder(item); - } - - @Override - public void onBindViewHolder(final SelectPlaylistItemHolder holder, final int position) { - final PlaylistLocalItem selectedItem = playlists.get(position); - - if (selectedItem instanceof PlaylistMetadataEntry) { - final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); - - holder.titleView.setText(entry.name); - holder.view.setOnClickListener(view -> clickedItem(position)); - imageLoader.displayImage(entry.thumbnailUrl, holder.thumbnailView, - DISPLAY_IMAGE_OPTIONS); - - } else if (selectedItem instanceof PlaylistRemoteEntity) { - final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); - - holder.titleView.setText(entry.getName()); - holder.view.setOnClickListener(view -> clickedItem(position)); - imageLoader.displayImage(entry.getThumbnailUrl(), holder.thumbnailView, - DISPLAY_IMAGE_OPTIONS); - } - } - - @Override - public int getItemCount() { - return playlists.size(); - } - - public class SelectPlaylistItemHolder extends RecyclerView.ViewHolder { - public final View view; - final ImageView thumbnailView; - final TextView titleView; - - SelectPlaylistItemHolder(final View v) { - super(v); - this.view = v; - thumbnailView = v.findViewById(R.id.itemThumbnailView); - titleView = v.findViewById(R.id.itemTitleView); - } - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 1b26cd529e9..621443cc6f5 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -29,12 +29,14 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.schabi.newpipe.R; +import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; +import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SelectChannelFragment; import org.schabi.newpipe.settings.SelectKioskFragment; -import org.schabi.newpipe.settings.SelectPlaylistFragment; +import org.schabi.newpipe.fragments.list.playlist.PlaylistDialog; import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem; import org.schabi.newpipe.util.ThemeHelper; @@ -213,21 +215,22 @@ private void addTab(final int tabId) { selectChannelFragment.show(requireFragmentManager(), "select_channel"); return; case PLAYLIST: - SelectPlaylistFragment selectPlaylistFragment = new SelectPlaylistFragment(); - selectPlaylistFragment.setOnSelectedListener( - new SelectPlaylistFragment.OnSelectedListener() { + new PlaylistDialog(null, true) + .setOnSelectedListener(new PlaylistDialog.OnSelectedListener() { @Override - public void onLocalPlaylistSelected(final long id, final String name) { - addTab(new Tab.PlaylistTab(id, name)); + public void onLocalPlaylistSelected( + final PlaylistMetadataEntry localPlaylist) { + addTab(new Tab.PlaylistTab(localPlaylist.uid, localPlaylist.name)); } @Override public void onRemotePlaylistSelected( - final int serviceId, final String url, final String name) { - addTab(new Tab.PlaylistTab(serviceId, url, name)); + final PlaylistRemoteEntity remotePlaylist) { + addTab(new Tab.PlaylistTab(remotePlaylist.getServiceId(), + remotePlaylist.getUrl(), remotePlaylist.getName())); } - }); - selectPlaylistFragment.show(requireFragmentManager(), "select_playlist"); + }) + .show(getParentFragmentManager(), "select_playlist"); return; default: addTab(type.getTab()); diff --git a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java index 5f44cab8b67..e4038b81416 100644 --- a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java +++ b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java @@ -4,7 +4,9 @@ public abstract class OnClickGesture { - public abstract void selected(T selectedItem); + public void selected(T selectedItem) { + // Optional gesture + } public void held(final T selectedItem) { // Optional gesture diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java index 92aee8ba704..f379d3f3ae6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java @@ -6,7 +6,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; +import org.schabi.newpipe.fragments.list.playlist.AppendPlaylistDialog; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import java.util.Collections; @@ -39,14 +39,13 @@ public enum StreamDialogEntry { }), // has to be set manually append_playlist(R.string.append_playlist, (fragment, item) -> { - if (fragment.getFragmentManager() != null) { - PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) - .show(fragment.getFragmentManager(), "StreamDialogEntry@append_playlist"); - } + AppendPlaylistDialog.fromStreamInfoItems(Collections.singletonList(item)) + .show(fragment.getParentFragmentManager(), + "StreamDialogEntry@append_playlist"); }), share(R.string.share, (fragment, item) -> - ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl())); + ShareUtils.shareUrl(fragment.requireContext(), item.getName(), item.getUrl())); /////////////// diff --git a/app/src/main/res/drawable/ic_photo_black_24dp.xml b/app/src/main/res/drawable/ic_photo_black_24dp.xml new file mode 100644 index 00000000000..b2018595e06 --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_photo_white_24dp.xml b/app/src/main/res/drawable/ic_photo_white_24dp.xml new file mode 100644 index 00000000000..0d8d5030a1f --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/dialog_playlists.xml b/app/src/main/res/layout/dialog_playlists.xml index eebeb29515b..f9157c4a0f7 100644 --- a/app/src/main/res/layout/dialog_playlists.xml +++ b/app/src/main/res/layout/dialog_playlists.xml @@ -1,16 +1,30 @@ - + android:layout_height="match_parent"> + + + android:clickable="true" + android:visibility="gone" + tools:visibility="visible"> + - - + + + + + + diff --git a/app/src/main/res/layout/item_toolbar_below.xml b/app/src/main/res/layout/item_toolbar_below.xml new file mode 100644 index 00000000000..4aa85d696cb --- /dev/null +++ b/app/src/main/res/layout/item_toolbar_below.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_toolbar_overlay.xml b/app/src/main/res/layout/item_toolbar_overlay.xml new file mode 100644 index 00000000000..7222cff79b5 --- /dev/null +++ b/app/src/main/res/layout/item_toolbar_overlay.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_channel_grid_item.xml b/app/src/main/res/layout/list_channel_grid_item.xml index 423bfeb9efe..34433519b7b 100644 --- a/app/src/main/res/layout/list_channel_grid_item.xml +++ b/app/src/main/res/layout/list_channel_grid_item.xml @@ -7,19 +7,28 @@ android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" - android:minWidth="@dimen/channel_item_grid_min_width" android:padding="@dimen/channel_item_grid_padding"> + + - + android:orientation="vertical"> - - - + android:layout_height="@dimen/video_item_search_height" + android:padding="@dimen/video_item_search_padding"> - + + + + + + + - + - + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index e009aa7d6c6..35ba88a39f9 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -1,50 +1,56 @@ - + android:orientation="vertical"> - - - + android:padding="@dimen/video_item_search_padding"> - + + + + + + - + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml index 4100ba39f51..504fb6d121d 100644 --- a/app/src/main/res/layout/list_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -41,6 +41,16 @@ tools:ignore="RtlHardcoded" tools:text="314159"/> + + - + android:orientation="vertical"> - + - + - + - + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_playlist_mini_item.xml b/app/src/main/res/layout/list_playlist_mini_item.xml index 3935d30ca72..0845ec986a4 100644 --- a/app/src/main/res/layout/list_playlist_mini_item.xml +++ b/app/src/main/res/layout/list_playlist_mini_item.xml @@ -1,70 +1,77 @@ - + android:orientation="vertical"> - - - - - + android:padding="@dimen/video_item_search_padding"> - + + + + + + + + - + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_radio_icon_item.xml b/app/src/main/res/layout/list_radio_icon_item.xml index 947747c16d7..3446e351c61 100644 --- a/app/src/main/res/layout/list_radio_icon_item.xml +++ b/app/src/main/res/layout/list_radio_icon_item.xml @@ -10,10 +10,8 @@ android:maxLines="2" android:minHeight="?attr/listPreferredItemHeightSmall" android:paddingEnd="?attr/listPreferredItemPaddingRight" - android:paddingLeft="?attr/listPreferredItemPaddingLeft" - android:paddingRight="?attr/listPreferredItemPaddingRight" android:paddingStart="?attr/listPreferredItemPaddingLeft" android:background="?attr/checked_selector" android:textColor="?attr/textColorAlertDialogListItem" - tools:drawableLeft="?attr/ic_play" + tools:drawableLeft="?attr/ic_play_arrow" tools:text="Lorem ipsum dolor sit amet" /> diff --git a/app/src/main/res/layout/list_stream_grid_item.xml b/app/src/main/res/layout/list_stream_grid_item.xml index 27c69aee8bb..fa7f9866df9 100644 --- a/app/src/main/res/layout/list_stream_grid_item.xml +++ b/app/src/main/res/layout/list_stream_grid_item.xml @@ -1,6 +1,5 @@ - + + - + android:orientation="vertical"> - + - + - + - + - + + + - + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_stream_mini_item.xml b/app/src/main/res/layout/list_stream_mini_item.xml index 667798128f8..f8bea6853dc 100644 --- a/app/src/main/res/layout/list_stream_mini_item.xml +++ b/app/src/main/res/layout/list_stream_mini_item.xml @@ -1,84 +1,88 @@ - + android:orientation="vertical"> - - - + android:padding="@dimen/video_item_search_padding"> + - + - - + + + + + + - \ No newline at end of file + + diff --git a/app/src/main/res/layout/list_stream_playlist_grid_item.xml b/app/src/main/res/layout/list_stream_playlist_grid_item.xml index 2b578e2f282..5df1fca24a1 100644 --- a/app/src/main/res/layout/list_stream_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_stream_playlist_grid_item.xml @@ -41,6 +41,16 @@ tools:ignore="RtlHardcoded" tools:text="1:09:10" /> + + - + android:orientation="vertical"> - - - + android:padding="@dimen/video_item_search_padding"> - + - + - + + + + + - + + - \ No newline at end of file + + diff --git a/app/src/main/res/layout/select_playlist_fragment.xml b/app/src/main/res/layout/select_playlist_fragment.xml deleted file mode 100644 index ca0d49e3258..00000000000 --- a/app/src/main/res/layout/select_playlist_fragment.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index cbf538fb50e..7d5ccb1d7b3 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -82,6 +82,7 @@ + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d3feb0ea8b7..bdca4a578a7 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -27,8 +27,6 @@ 164dp 92dp - 42dp - 128dp 96dp @@ -116,4 +114,8 @@ 4dp 16sp + + + 39dp + -27dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9018a2d2a73..734dd9ca505 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -97,6 +97,7 @@ @drawable/ic_sort_black_24dp @drawable/ic_help_black_24dp @drawable/ic_arrow_back_black_24dp + @drawable/ic_photo_black_24dp @color/light_separator_color @color/light_contrast_background_color @@ -204,6 +205,7 @@ @drawable/ic_sort_white_24dp @drawable/ic_help_white_24dp @drawable/ic_arrow_back_white_24dp + @drawable/ic_photo_white_24dp @color/dark_separator_color @color/dark_contrast_background_color diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index b29789ecaf9..6e00be9825c 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -4,12 +4,8 @@ "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - - + files="ItemListAdapter.java" + lines="278,403"/>