- * ChannelInfoItemHolder .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 CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
- public final TextView itemTitleView;
- private final ImageView itemHeartView;
- private final ImageView itemPinnedView;
-
- public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
- super(infoItemBuilder, R.layout.list_comments_item, parent);
-
- itemTitleView = itemView.findViewById(R.id.itemTitleView);
- itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
- itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
- }
-
- @Override
- public void updateFromItem(final InfoItem infoItem,
- final HistoryRecordManager historyRecordManager) {
- super.updateFromItem(infoItem, historyRecordManager);
-
- if (!(infoItem instanceof CommentsInfoItem)) {
- return;
- }
- final CommentsInfoItem item = (CommentsInfoItem) infoItem;
-
- itemTitleView.setText(item.getUploaderName());
-
- itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
-
- itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
- }
-}
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
deleted file mode 100644
index 6e4773c09..000000000
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package org.schabi.newpipe.info_list.holder;
-
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorUtil;
-import org.schabi.newpipe.extractor.InfoItem;
-import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
-import org.schabi.newpipe.info_list.InfoItemBuilder;
-import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.util.CommentTextOnTouchListener;
-import org.schabi.newpipe.util.DeviceUtils;
-import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.external_communication.TimestampExtractor;
-import org.schabi.newpipe.util.external_communication.ShareUtils;
-import org.schabi.newpipe.util.PicassoHelper;
-
-import java.util.regex.Matcher;
-
-public class CommentsMiniInfoItemHolder extends InfoItemHolder {
- private static final String TAG = "CommentsMiniIIHolder";
-
- private static final int COMMENT_DEFAULT_LINES = 2;
- private static final int COMMENT_EXPANDED_LINES = 1000;
-
- private final int commentHorizontalPadding;
- private final int commentVerticalPadding;
-
- private final RelativeLayout itemRoot;
- public final ImageView itemThumbnailView;
- private final TextView itemContentView;
- private final TextView itemLikesCountView;
- private final TextView itemPublishedTime;
-
- private String commentText;
- private String streamUrl;
-
- private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
- @Override
- public String transformUrl(final Matcher match, final String url) {
- try {
- final TimestampExtractor.TimestampMatchDTO timestampMatchDTO =
- TimestampExtractor.getTimestampFromMatcher(match, commentText);
-
- if (timestampMatchDTO == null) {
- return url;
- }
-
- return streamUrl + url.replace(
- match.group(0),
- "#timestamp=" + timestampMatchDTO.seconds());
- } catch (final Exception ex) {
- Log.e(TAG, "Unable to process url='" + url + "' as timestampLink", ex);
- return url;
- }
- }
- };
-
- CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
- final ViewGroup parent) {
- super(infoItemBuilder, layoutId, parent);
-
- itemRoot = itemView.findViewById(R.id.itemRoot);
- itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
- itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
- itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
- itemContentView = itemView.findViewById(R.id.itemCommentContentView);
-
- commentHorizontalPadding = (int) infoItemBuilder.getContext()
- .getResources().getDimension(R.dimen.comments_horizontal_padding);
- commentVerticalPadding = (int) infoItemBuilder.getContext()
- .getResources().getDimension(R.dimen.comments_vertical_padding);
- }
-
- public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
- final ViewGroup parent) {
- this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
- }
-
- @Override
- public void updateFromItem(final InfoItem infoItem,
- final HistoryRecordManager historyRecordManager) {
- if (!(infoItem instanceof CommentsInfoItem)) {
- return;
- }
- final CommentsInfoItem item = (CommentsInfoItem) infoItem;
-
- PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView);
- if (PicassoHelper.getShouldLoadImages()) {
- itemThumbnailView.setVisibility(View.VISIBLE);
- itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
- commentVerticalPadding, commentVerticalPadding);
- } else {
- itemThumbnailView.setVisibility(View.GONE);
- itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
- commentHorizontalPadding, commentVerticalPadding);
- }
-
-
- itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
-
- streamUrl = item.getUrl();
-
- itemContentView.setLines(COMMENT_DEFAULT_LINES);
- commentText = item.getCommentText();
- itemContentView.setText(commentText);
- itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
-
- if (itemContentView.getLineCount() == 0) {
- itemContentView.post(this::ellipsize);
- } else {
- ellipsize();
- }
-
- if (item.getLikeCount() >= 0) {
- itemLikesCountView.setText(
- Localization.shortCount(
- itemBuilder.getContext(),
- item.getLikeCount()));
- } else {
- itemLikesCountView.setText("-");
- }
-
- if (item.getUploadDate() != null) {
- itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
- .offsetDateTime()));
- } else {
- itemPublishedTime.setText(item.getTextualUploadDate());
- }
-
- itemView.setOnClickListener(view -> {
- toggleEllipsize();
- if (itemBuilder.getOnCommentsSelectedListener() != null) {
- itemBuilder.getOnCommentsSelectedListener().selected(item);
- }
- });
-
-
- itemView.setOnLongClickListener(view -> {
- if (DeviceUtils.isTv(itemBuilder.getContext())) {
- openCommentAuthor(item);
- } else {
- ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
- }
- return true;
- });
- }
-
- private void openCommentAuthor(final CommentsInfoItem item) {
- if (TextUtils.isEmpty(item.getUploaderUrl())) {
- return;
- }
- final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
- try {
- NavigationHelper.openChannelFragment(
- activity.getSupportFragmentManager(),
- item.getServiceId(),
- item.getUploaderUrl(),
- item.getUploaderName());
- } catch (final Exception e) {
- ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
- }
- }
-
- private void allowLinkFocus() {
- itemContentView.setMovementMethod(LinkMovementMethod.getInstance());
- }
-
- private void denyLinkFocus() {
- itemContentView.setMovementMethod(null);
- }
-
- private boolean shouldFocusLinks() {
- if (itemView.isInTouchMode()) {
- return false;
- }
-
- final URLSpan[] urls = itemContentView.getUrls();
-
- return urls != null && urls.length != 0;
- }
-
- private void determineLinkFocus() {
- if (shouldFocusLinks()) {
- allowLinkFocus();
- } else {
- denyLinkFocus();
- }
- }
-
- private void ellipsize() {
- boolean hasEllipsis = false;
-
- if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
- final int endOfLastLine
- = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
- int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
- if (end == -1) {
- end = Math.max(endOfLastLine - 2, 0);
- }
- final String newVal = itemContentView.getText().subSequence(0, end) + " …";
- itemContentView.setText(newVal);
- hasEllipsis = true;
- }
-
- linkify();
-
- if (hasEllipsis) {
- denyLinkFocus();
- } else {
- determineLinkFocus();
- }
- }
-
- private void toggleEllipsize() {
- if (itemContentView.getText().toString().equals(commentText)) {
- if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
- ellipsize();
- }
- } else {
- expand();
- }
- }
-
- private void expand() {
- itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
- itemContentView.setText(commentText);
- linkify();
- determineLinkFocus();
- }
-
- private void linkify() {
- Linkify.addLinks(
- itemContentView,
- Linkify.WEB_URLS);
- Linkify.addLinks(
- itemContentView,
- TimestampExtractor.TIMESTAMPS_PATTERN,
- null,
- null,
- timestampLink);
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistCardInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistCardInfoItemHolder.java
new file mode 100644
index 000000000..f1682b4e4
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistCardInfoItemHolder.java
@@ -0,0 +1,17 @@
+package org.schabi.newpipe.info_list.holder;
+
+import android.view.ViewGroup;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.info_list.InfoItemBuilder;
+
+/**
+ * Playlist card layout.
+ */
+public class PlaylistCardInfoItemHolder extends PlaylistMiniInfoItemHolder {
+
+ public PlaylistCardInfoItemHolder(final InfoItemBuilder infoItemBuilder,
+ final ViewGroup parent) {
+ super(infoItemBuilder, R.layout.list_playlist_card_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 bf5f57db3..c9216d9a9 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
@@ -9,7 +9,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.util.PicassoHelper;
+import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.Localization;
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
@@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
itemUploaderView.setText(item.getUploaderName());
- PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
+ PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamCardInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamCardInfoItemHolder.java
new file mode 100644
index 000000000..807bad6e0
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamCardInfoItemHolder.java
@@ -0,0 +1,16 @@
+package org.schabi.newpipe.info_list.holder;
+
+import android.view.ViewGroup;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.info_list.InfoItemBuilder;
+
+/**
+ * Card layout for stream.
+ */
+public class StreamCardInfoItemHolder extends StreamInfoItemHolder {
+
+ public StreamCardInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
+ super(infoItemBuilder, R.layout.list_stream_card_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 a84c98404..80f62eed3 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
@@ -12,10 +12,6 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization;
-import androidx.preference.PreferenceManager;
-
-import static org.schabi.newpipe.MainActivity.DEBUG;
-
/*
* Created by Christian Schabesberger on 01.08.16.
*
@@ -81,7 +77,9 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
}
}
- final String uploadDate = getFormattedRelativeUploadDate(infoItem);
+ final String uploadDate = Localization.relativeTimeOrTextual(itemBuilder.getContext(),
+ infoItem.getUploadDate(),
+ infoItem.getTextualUploadDate());
if (!TextUtils.isEmpty(uploadDate)) {
if (viewsAndDate.isEmpty()) {
return uploadDate;
@@ -92,20 +90,4 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
return viewsAndDate;
}
-
- private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
- if (infoItem.getUploadDate() != null) {
- String formattedRelativeTime = Localization
- .relativeTime(infoItem.getUploadDate().offsetDateTime());
-
- if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
- .getBoolean(itemBuilder.getContext()
- .getString(R.string.show_original_time_ago_key), false)) {
- formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
- }
- return formattedRelativeTime;
- } else {
- return infoItem.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 79772a6a3..01f3be6b3 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
@@ -11,12 +11,13 @@ 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.ktx.ViewUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.util.PicassoHelper;
+import org.schabi.newpipe.util.DependentPreferenceHelper;
import org.schabi.newpipe.util.Localization;
+import org.schabi.newpipe.util.image.PicassoHelper;
+import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.util.concurrent.TimeUnit;
@@ -60,8 +61,12 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
- final StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
- .blockingGet()[0];
+ StreamStateEntity state2 = null;
+ if (DependentPreferenceHelper
+ .getPositionsInListsEnabled(itemProgressView.getContext())) {
+ state2 = historyRecordManager.loadStreamState(infoItem)
+ .blockingGet()[0];
+ }
if (state2 != null) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getDuration());
@@ -70,8 +75,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
} else {
itemProgressView.setVisibility(View.GONE);
}
- } else if (item.getStreamType() == StreamType.LIVE_STREAM
- || item.getStreamType() == StreamType.AUDIO_LIVE_STREAM) {
+ } else if (StreamTypeUtil.isLiveStream(item.getStreamType())) {
itemDurationView.setText(R.string.duration_live);
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
R.color.live_duration_background_color));
@@ -83,7 +87,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
// Default thumbnail is shown on error, while loading and if the url is empty
- PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
+ PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnStreamSelectedListener() != null) {
@@ -96,9 +100,10 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
case VIDEO_STREAM:
case LIVE_STREAM:
case AUDIO_LIVE_STREAM:
+ case POST_LIVE_STREAM:
+ case POST_LIVE_AUDIO_STREAM:
enableLongClick(item);
break;
- case FILE:
case NONE:
default:
disableLongClick();
@@ -111,10 +116,14 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
final HistoryRecordManager historyRecordManager) {
final StreamInfoItem item = (StreamInfoItem) infoItem;
- final StreamStateEntity state
- = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
+ StreamStateEntity state = null;
+ if (DependentPreferenceHelper.getPositionsInListsEnabled(itemProgressView.getContext())) {
+ state = historyRecordManager
+ .loadStreamState(infoItem)
+ .blockingGet()[0];
+ }
if (state != null && item.getDuration() > 0
- && item.getStreamType() != StreamType.LIVE_STREAM) {
+ && !StreamTypeUtil.isLiveStream(item.getStreamType())) {
itemProgressView.setMax((int) item.getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt
new file mode 100644
index 000000000..61721d546
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt
@@ -0,0 +1,9 @@
+package org.schabi.newpipe.ktx
+
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.core.os.BundleCompat
+
+inline fun Bundle.parcelableArrayList(key: String?): ArrayList? {
+ return BundleCompat.getParcelableArrayList(this, key, T::class.java)
+}
diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt
index 8dcc9d85c..bf0dcb201 100644
--- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt
+++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt
@@ -21,10 +21,6 @@ import org.schabi.newpipe.MainActivity
private const val TAG = "ViewUtils"
-inline var View.backgroundTintListCompat: ColorStateList?
- get() = ViewCompat.getBackgroundTintList(this)
- set(value) = ViewCompat.setBackgroundTintList(this, value)
-
/**
* Animate the view.
*
@@ -96,62 +92,43 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo
if (MainActivity.DEBUG) {
Log.d(
TAG,
- "animateBackgroundColor() called with: " +
- "view = [" + this + "], duration = [" + duration + "], " +
- "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]"
+ "animateBackgroundColor() called with: view = [$this], duration = [$duration], " +
+ "colorStart = [$colorStart], colorEnd = [$colorEnd]"
)
}
- val empty = arrayOf(IntArray(0))
val viewPropertyAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorStart, colorEnd)
viewPropertyAnimator.interpolator = FastOutSlowInInterpolator()
viewPropertyAnimator.duration = duration
- viewPropertyAnimator.addUpdateListener { animation: ValueAnimator ->
- backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int))
+
+ fun listenerAction(color: Int) {
+ ViewCompat.setBackgroundTintList(this, ColorStateList.valueOf(color))
}
- viewPropertyAnimator.addListener(
- onCancel = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) },
- onEnd = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) }
- )
+ viewPropertyAnimator.addUpdateListener { listenerAction(it.animatedValue as Int) }
+ viewPropertyAnimator.addListener(onCancel = { listenerAction(colorEnd) }, onEnd = { listenerAction(colorEnd) })
viewPropertyAnimator.start()
}
fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
if (MainActivity.DEBUG) {
- Log.d(
- TAG,
- "animateHeight: duration = [" + duration + "], " +
- "from " + height + " to → " + targetHeight + " in: " + this
- )
+ Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this")
}
val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat())
animator.interpolator = FastOutSlowInInterpolator()
animator.duration = duration
- animator.addUpdateListener { animation: ValueAnimator ->
- val value = animation.animatedValue as Float
- layoutParams.height = value.toInt()
+
+ fun listenerAction(value: Int) {
+ layoutParams.height = value
requestLayout()
}
- animator.addListener(
- onCancel = {
- layoutParams.height = targetHeight
- requestLayout()
- },
- onEnd = {
- layoutParams.height = targetHeight
- requestLayout()
- }
- )
+ animator.addUpdateListener { listenerAction((it.animatedValue as Float).toInt()) }
+ animator.addListener(onCancel = { listenerAction(targetHeight) }, onEnd = { listenerAction(targetHeight) })
animator.start()
return animator
}
fun View.animateRotation(duration: Long, targetRotation: Int) {
if (MainActivity.DEBUG) {
- Log.d(
- TAG,
- "animateRotation: duration = [" + duration + "], " +
- "from " + rotation + " to → " + targetRotation + " in: " + this
- )
+ Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this")
}
animate().setListener(null).cancel()
animate()
@@ -172,20 +149,13 @@ private fun View.animateAlpha(enterOrExit: Boolean, duration: Long, delay: Long,
if (enterOrExit) {
animate().setInterpolator(FastOutSlowInInterpolator()).alpha(1f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- execOnEnd?.run()
- }
- }).start()
+ .setListener(ExecOnEndListener(execOnEnd))
+ .start()
} else {
animate().setInterpolator(FastOutSlowInInterpolator()).alpha(0f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- isGone = true
- execOnEnd?.run()
- }
- }).start()
+ .setListener(HideAndExecOnEndListener(this, execOnEnd))
+ .start()
}
}
@@ -197,11 +167,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela
.setInterpolator(FastOutSlowInInterpolator())
.alpha(1f).scaleX(1f).scaleY(1f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- execOnEnd?.run()
- }
- }).start()
+ .setListener(ExecOnEndListener(execOnEnd))
+ .start()
} else {
scaleX = 1f
scaleY = 1f
@@ -209,12 +176,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela
.setInterpolator(FastOutSlowInInterpolator())
.alpha(0f).scaleX(.8f).scaleY(.8f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- isGone = true
- execOnEnd?.run()
- }
- }).start()
+ .setListener(HideAndExecOnEndListener(this, execOnEnd))
+ .start()
}
}
@@ -227,11 +190,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long,
.setInterpolator(FastOutSlowInInterpolator())
.alpha(1f).scaleX(1f).scaleY(1f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- execOnEnd?.run()
- }
- }).start()
+ .setListener(ExecOnEndListener(execOnEnd))
+ .start()
} else {
alpha = 1f
scaleX = 1f
@@ -240,12 +200,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long,
.setInterpolator(FastOutSlowInInterpolator())
.alpha(0f).scaleX(.95f).scaleY(.95f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- isGone = true
- execOnEnd?.run()
- }
- }).start()
+ .setListener(HideAndExecOnEndListener(this, execOnEnd))
+ .start()
}
}
@@ -256,22 +212,15 @@ private fun View.animateSlideAndAlpha(enterOrExit: Boolean, duration: Long, dela
animate()
.setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- execOnEnd?.run()
- }
- }).start()
+ .setListener(ExecOnEndListener(execOnEnd))
+ .start()
} else {
animate()
.setInterpolator(FastOutSlowInInterpolator())
.alpha(0f).translationY(-height.toFloat())
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- isGone = true
- execOnEnd?.run()
- }
- }).start()
+ .setListener(HideAndExecOnEndListener(this, execOnEnd))
+ .start()
}
}
@@ -282,32 +231,18 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long,
animate()
.setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- execOnEnd?.run()
- }
- }).start()
+ .setListener(ExecOnEndListener(execOnEnd))
+ .start()
} else {
animate().setInterpolator(FastOutSlowInInterpolator())
.alpha(0f).translationY(-height / 2.0f)
.setDuration(duration).setStartDelay(delay)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- isGone = true
- execOnEnd?.run()
- }
- }).start()
+ .setListener(HideAndExecOnEndListener(this, execOnEnd))
+ .start()
}
}
-fun View.slideUp(
- duration: Long,
- delay: Long,
- @FloatRange(from = 0.0, to = 1.0) translationPercent: Float
-) {
- slideUp(duration, delay, translationPercent, null)
-}
-
+@JvmOverloads
fun View.slideUp(
duration: Long,
delay: Long = 0L,
@@ -325,11 +260,7 @@ fun View.slideUp(
.setStartDelay(delay)
.setDuration(duration)
.setInterpolator(FastOutSlowInInterpolator())
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- execOnEnd?.run()
- }
- })
+ .setListener(ExecOnEndListener(execOnEnd))
.start()
}
@@ -343,6 +274,20 @@ fun View.animateHideRecyclerViewAllowingScrolling() {
animate().alpha(0.0f).setDuration(200).start()
}
+private open class ExecOnEndListener(private val execOnEnd: Runnable?) : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ execOnEnd?.run()
+ }
+}
+
+private class HideAndExecOnEndListener(private val view: View, execOnEnd: Runnable?) :
+ ExecOnEndListener(execOnEnd) {
+ override fun onAnimationEnd(animation: Animator) {
+ view.isGone = true
+ super.onAnimationEnd(animation)
+ }
+}
+
enum class AnimationType {
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
}
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 8790c3059..53fe1677b 100644
--- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
@@ -22,10 +22,11 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.list.ListViewContract;
+import org.schabi.newpipe.info_list.ItemViewMode;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
-import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
+import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode;
/**
* This fragment is design to be used with persistent data such as
@@ -77,16 +78,23 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
super.onResume();
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
- final boolean useGrid = shouldUseGridLayout(requireContext());
- itemsList.setLayoutManager(
- useGrid ? getGridLayoutManager() : getListLayoutManager());
- itemListAdapter.setUseGridVariant(useGrid);
- itemListAdapter.notifyDataSetChanged();
+ refreshItemViewMode();
}
updateFlags = 0;
}
}
+ /**
+ * Updates the item view mode based on user preference.
+ */
+ private void refreshItemViewMode() {
+ final ItemViewMode itemViewMode = getItemViewMode(requireContext());
+ itemsList.setLayoutManager((itemViewMode == ItemViewMode.GRID)
+ ? getGridLayoutManager() : getListLayoutManager());
+ itemListAdapter.setItemViewMode(itemViewMode);
+ itemListAdapter.notifyDataSetChanged();
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Lifecycle - View
//////////////////////////////////////////////////////////////////////////*/
@@ -104,8 +112,7 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
- final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
- / (double) width);
+ final int spanCount = Math.floorDiv(resources.getDisplayMetrics().widthPixels, width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
return lm;
@@ -121,11 +128,9 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
itemListAdapter = new LocalItemListAdapter(activity);
- final boolean useGrid = shouldUseGridLayout(requireContext());
itemsList = rootView.findViewById(R.id.items_list);
- itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
+ refreshItemViewMode();
- itemListAdapter.setUseGridVariant(useGrid);
headerRootBinding = getListHeader();
if (headerRootBinding != null) {
itemListAdapter.setHeader(headerRootBinding.getRoot());
@@ -256,7 +261,7 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
- if (key.equals(getString(R.string.list_view_mode_key))) {
+ if (getString(R.string.list_view_mode_key).equals(key)) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
index 5c22cee24..b33619dea 100644
--- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
@@ -12,16 +12,21 @@ import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
+import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
import org.schabi.newpipe.local.holder.LocalItemHolder;
+import org.schabi.newpipe.local.holder.LocalPlaylistCardItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
+import org.schabi.newpipe.local.holder.LocalPlaylistStreamCardItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
+import org.schabi.newpipe.local.holder.LocalStatisticStreamCardItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
+import org.schabi.newpipe.local.holder.RemotePlaylistCardItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
import org.schabi.newpipe.util.FallbackViewHolder;
@@ -63,13 +68,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter localItems;
@@ -77,10 +88,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter() {
+ itemListAdapter.setSelectedListener(new OnClickGesture<>() {
@Override
public void selected(final LocalItem selectedItem) {
final FragmentManager fragmentManager = getFM();
@@ -518,24 +519,53 @@ public final class BookmarkFragment extends BaseLocalListFragment items = new ArrayList<>();
+ items.add(rename);
+ items.add(delete);
+ if (isThumbnailPermanent) {
+ items.add(unsetThumbnail);
+ }
+
+ final DialogInterface.OnClickListener action = (d, index) -> {
+ if (items.get(index).equals(rename)) {
+ showRenameDialog(selectedItem);
+ } else if (items.get(index).equals(delete)) {
+ showDeleteDialog(selectedItem.name, selectedItem);
+ } else if (isThumbnailPermanent && items.get(index).equals(unsetThumbnail)) {
+ final long thumbnailStreamId = localPlaylistManager
+ .getAutomaticPlaylistThumbnailStreamId(selectedItem.getUid());
+ localPlaylistManager
+ .changePlaylistThumbnail(selectedItem.getUid(), thumbnailStreamId, false)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe();
+ }
+ };
+
+ new AlertDialog.Builder(activity)
+ .setItems(items.toArray(new String[0]), action)
+ .show();
+ }
+
+ private void showRenameDialog(final PlaylistMetadataEntry selectedItem) {
+ final DialogEditTextBinding dialogBinding =
+ DialogEditTextBinding.inflate(getLayoutInflater());
dialogBinding.dialogEditText.setHint(R.string.name);
dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
dialogBinding.dialogEditText.setText(selectedItem.name);
- final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setView(dialogBinding.getRoot())
+ new AlertDialog.Builder(activity)
+ .setView(dialogBinding.getRoot())
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
changeLocalPlaylistName(
selectedItem.getUid(),
dialogBinding.dialogEditText.getText().toString()))
.setNegativeButton(R.string.cancel, null)
- .setNeutralButton(R.string.delete, (dialog, which) -> {
- showDeleteDialog(selectedItem.name, selectedItem);
- dialog.dismiss();
- })
- .create()
.show();
}
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
index a778e6578..e7f73079f 100644
--- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
@@ -4,6 +4,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -13,12 +14,11 @@ 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.playlist.model.PlaylistEntity;
+import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.local.LocalItemListAdapter;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
-import org.schabi.newpipe.util.OnClickGesture;
import java.util.List;
@@ -30,6 +30,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
private RecyclerView playlistRecyclerView;
private LocalItemListAdapter playlistAdapter;
+ private TextView playlistDuplicateIndicator;
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
@@ -63,18 +64,11 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
playlistAdapter = new LocalItemListAdapter(getActivity());
- playlistAdapter.setSelectedListener(new OnClickGesture() {
- @Override
- public void selected(final LocalItem selectedItem) {
- if (!(selectedItem instanceof PlaylistMetadataEntry)
- || getStreamEntities() == null) {
- return;
- }
- onPlaylistSelected(
- playlistManager,
- (PlaylistMetadataEntry) selectedItem,
- getStreamEntities()
- );
+ playlistAdapter.setSelectedListener(selectedItem -> {
+ final List entities = getStreamEntities();
+ if (selectedItem instanceof PlaylistDuplicatesEntry && entities != null) {
+ onPlaylistSelected(playlistManager,
+ (PlaylistDuplicatesEntry) selectedItem, entities);
}
});
@@ -82,10 +76,13 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
playlistRecyclerView.setAdapter(playlistAdapter);
+ playlistDuplicateIndicator = view.findViewById(R.id.playlist_duplicate);
+
final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog());
- playlistDisposables.add(playlistManager.getDisplayIndexOrderedPlaylists()
+ playlistDisposables.add(playlistManager
+ .getPlaylistDuplicates(getStreamEntities().get(0).getUrl())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onPlaylistsReceived));
}
@@ -127,34 +124,50 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
requireDialog().dismiss();
}
- private void onPlaylistsReceived(@NonNull final List playlists) {
- if (playlistAdapter != null && playlistRecyclerView != null) {
+ private void onPlaylistsReceived(@NonNull final List playlists) {
+ if (playlistAdapter != null
+ && playlistRecyclerView != null
+ && playlistDuplicateIndicator != null) {
playlistAdapter.clearStreamItemList();
playlistAdapter.addItems(playlists);
playlistRecyclerView.setVisibility(View.VISIBLE);
+ playlistDuplicateIndicator.setVisibility(
+ anyPlaylistContainsDuplicates(playlists) ? View.VISIBLE : View.GONE);
}
}
+ private boolean anyPlaylistContainsDuplicates(final List playlists) {
+ return playlists.stream()
+ .anyMatch(playlist -> playlist.timesStreamIsContained > 0);
+ }
+
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
- @NonNull final PlaylistMetadataEntry playlist,
+ @NonNull final PlaylistDuplicatesEntry playlist,
@NonNull final List streams) {
- if (getStreamEntities() == null) {
- return;
+
+ final String toastText;
+ if (playlist.timesStreamIsContained > 0) {
+ toastText = getString(R.string.playlist_add_stream_success_duplicate,
+ playlist.timesStreamIsContained);
+ } else {
+ toastText = getString(R.string.playlist_add_stream_success);
}
- final Toast successToast = Toast.makeText(getContext(),
- R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
-
- if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) {
- playlistDisposables.add(manager
- .changePlaylistThumbnail(playlist.getUid(), streams.get(0).getThumbnailUrl())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(ignored -> successToast.show()));
- }
+ final Toast successToast = Toast.makeText(getContext(), toastText, Toast.LENGTH_SHORT);
playlistDisposables.add(manager.appendToPlaylist(playlist.getUid(), streams)
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(ignored -> successToast.show()));
+ .subscribe(ignored -> {
+ successToast.show();
+
+ if (playlist.thumbnailUrl.equals(PlaylistEntity.DEFAULT_THUMBNAIL)) {
+ playlistDisposables.add(manager
+ .changePlaylistThumbnail(playlist.getUid(), streams.get(0).getUid(),
+ false)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(ignore -> successToast.show()));
+ }
+ }));
requireDialog().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
index 0c09f3f0d..0d5cfac23 100644
--- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java
@@ -45,8 +45,8 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
return super.onCreateDialog(savedInstanceState);
}
- final DialogEditTextBinding dialogBinding
- = DialogEditTextBinding.inflate(getLayoutInflater());
+ final DialogEditTextBinding dialogBinding =
+ DialogEditTextBinding.inflate(getLayoutInflater());
dialogBinding.getRoot().getContext().setTheme(ThemeHelper.getDialogTheme(requireContext()));
dialogBinding.dialogEditText.setHint(R.string.name);
dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
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
index f568ef81a..612c38181 100644
--- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java
@@ -9,15 +9,20 @@ import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.util.StateSaver;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
@@ -131,13 +136,13 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
* @param context context used for accessing the database
* @param streamEntities used for crating the dialog
* @param onExec execution that should occur after a dialog got created, e.g. showing it
- * @return Disposable
+ * @return the disposable that was created
*/
public static Disposable createCorrespondingDialog(
final Context context,
final List streamEntities,
- final Consumer onExec
- ) {
+ final Consumer onExec) {
+
return new LocalPlaylistManager(NewPipeDatabase.getInstance(context))
.hasPlaylists()
.observeOn(AndroidSchedulers.mainThread())
@@ -147,4 +152,30 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
: PlaylistCreationDialog.newInstance(streamEntities))
);
}
+
+ /**
+ * Creates a {@link PlaylistAppendDialog} when playlists exists,
+ * otherwise a {@link PlaylistCreationDialog}. If the player's play queue is null or empty, no
+ * dialog will be created.
+ *
+ * @param player the player from which to extract the context and the play queue
+ * @param fragmentManager the fragment manager to use to show the dialog
+ * @return the disposable that was created
+ */
+ public static Disposable showForPlayQueue(
+ final Player player,
+ @NonNull final FragmentManager fragmentManager) {
+
+ final List streamEntities = Stream.of(player.getPlayQueue())
+ .filter(Objects::nonNull)
+ .flatMap(playQueue -> playQueue.getStreams().stream())
+ .map(StreamEntity::new)
+ .collect(Collectors.toList());
+ if (streamEntities.isEmpty()) {
+ return Disposable.empty();
+ }
+
+ return PlaylistDialog.createCorrespondingDialog(player.getContext(), streamEntities,
+ dialog -> dialog.show(fragmentManager, "PlaylistDialog"));
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
index 7a8723ceb..ed65d4048 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
@@ -41,19 +41,17 @@ class FeedDatabaseManager(context: Context) {
fun database() = database
fun getStreams(
- groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
- getPlayedStreams: Boolean = true
+ groupId: Long,
+ includePlayedStreams: Boolean,
+ includePartiallyPlayedStreams: Boolean,
+ includeFutureStreams: Boolean
): Maybe> {
- return when (groupId) {
- FeedGroupEntity.GROUP_ALL_ID -> {
- if (getPlayedStreams) feedTable.getAllStreams()
- else feedTable.getLiveOrNotPlayedStreams()
- }
- else -> {
- if (getPlayedStreams) feedTable.getAllStreamsForGroup(groupId)
- else feedTable.getLiveOrNotPlayedStreamsForGroup(groupId)
- }
- }
+ return feedTable.getStreams(
+ groupId,
+ includePlayedStreams,
+ includePartiallyPlayedStreams,
+ if (includeFutureStreams) null else OffsetDateTime.now()
+ )
}
fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold)
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 e97629f31..0b61f45fb 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
@@ -25,7 +25,6 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Typeface
-import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.os.Parcelable
@@ -37,10 +36,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
-import androidx.annotation.AttrRes
-import androidx.annotation.Nullable
import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
@@ -63,12 +59,14 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.databinding.FragmentFeedBinding
import org.schabi.newpipe.error.ErrorInfo
+import org.schabi.newpipe.error.ErrorUtil
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty
import org.schabi.newpipe.fragments.BaseStateFragment
+import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.info_list.dialog.InfoItemDialog
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
@@ -80,6 +78,8 @@ import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
+import org.schabi.newpipe.util.ThemeHelper.getItemViewMode
+import org.schabi.newpipe.util.ThemeHelper.resolveDrawable
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
import java.time.OffsetDateTime
import java.util.function.Consumer
@@ -98,7 +98,6 @@ class FeedFragment : BaseStateFragment() {
private var oldestSubscriptionUpdate: OffsetDateTime? = null
private lateinit var groupAdapter: GroupieAdapter
- @State @JvmField var showPlayedItems: Boolean = true
private var onSettingsChangeListener: SharedPreferences.OnSharedPreferenceChangeListener? = null
private var updateListViewModeOnResume = false
@@ -118,7 +117,7 @@ class FeedFragment : BaseStateFragment