From a3c4a10721f4edb99d316dcc1e53b8047207d355 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jan 2021 06:29:50 +0530 Subject: [PATCH 001/116] Convert the abstract class DAOs to interfaces. --- .../database/playlist/dao/PlaylistDAO.java | 14 +++++++------- .../database/playlist/dao/PlaylistRemoteDAO.java | 16 ++++++++-------- .../database/playlist/dao/PlaylistStreamDAO.java | 16 ++++++++-------- .../database/stream/dao/StreamStateDAO.java | 16 ++++++++-------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java index 078385e56..70aaa3b2d 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java @@ -14,26 +14,26 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; @Dao -public abstract class PlaylistDAO implements BasicDAO { +public interface PlaylistDAO extends BasicDAO { @Override @Query("SELECT * FROM " + PLAYLIST_TABLE) - public abstract Flowable> getAll(); + Flowable> getAll(); @Override @Query("DELETE FROM " + PLAYLIST_TABLE) - public abstract int deleteAll(); + int deleteAll(); @Override - public Flowable> listByService(final int serviceId) { + default Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } @Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") - public abstract Flowable> getPlaylist(long playlistId); + Flowable> getPlaylist(long playlistId); @Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") - public abstract int deletePlaylist(long playlistId); + int deletePlaylist(long playlistId); @Query("SELECT COUNT(*) FROM " + PLAYLIST_TABLE) - public abstract Flowable getCount(); + Flowable getCount(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java index a488f00fc..6bb849428 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java @@ -17,31 +17,31 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL; @Dao -public abstract class PlaylistRemoteDAO implements BasicDAO { +public interface PlaylistRemoteDAO extends BasicDAO { @Override @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE) - public abstract Flowable> getAll(); + Flowable> getAll(); @Override @Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE) - public abstract int deleteAll(); + int deleteAll(); @Override @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") - public abstract Flowable> listByService(int serviceId); + Flowable> listByService(int serviceId); @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") - public abstract Flowable> getPlaylist(long serviceId, String url); + Flowable> getPlaylist(long serviceId, String url); @Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_URL + " = :url " + "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") - abstract Long getPlaylistIdInternal(long serviceId, String url); + Long getPlaylistIdInternal(long serviceId, String url); @Transaction - public long upsert(final PlaylistRemoteEntity playlist) { + default long upsert(final PlaylistRemoteEntity playlist) { final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl()); if (playlistId == null) { @@ -55,5 +55,5 @@ public abstract class PlaylistRemoteDAO implements BasicDAO { +public interface PlaylistStreamDAO extends BasicDAO { @Override @Query("SELECT * FROM " + PLAYLIST_STREAM_JOIN_TABLE) - public abstract Flowable> getAll(); + Flowable> getAll(); @Override @Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE) - public abstract int deleteAll(); + int deleteAll(); @Override - public Flowable> listByService(final int serviceId) { + default Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } @Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") - public abstract void deleteBatch(long playlistId); + void deleteBatch(long playlistId); @Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" + " FROM " + PLAYLIST_STREAM_JOIN_TABLE + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") - public abstract Flowable getMaximumIndexOf(long playlistId); + Flowable getMaximumIndexOf(long playlistId); @Transaction @Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " @@ -69,7 +69,7 @@ public abstract class PlaylistStreamDAO implements BasicDAO> getOrderedStreamsOf(long playlistId); + Flowable> getOrderedStreamsOf(long playlistId); @Transaction @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", " @@ -80,5 +80,5 @@ public abstract class PlaylistStreamDAO implements BasicDAO> getPlaylistMetadata(); + Flowable> getPlaylistMetadata(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java index a6b36e3ff..06371248d 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java @@ -17,31 +17,31 @@ import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_ST import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; @Dao -public abstract class StreamStateDAO implements BasicDAO { +public interface StreamStateDAO extends BasicDAO { @Override @Query("SELECT * FROM " + STREAM_STATE_TABLE) - public abstract Flowable> getAll(); + Flowable> getAll(); @Override @Query("DELETE FROM " + STREAM_STATE_TABLE) - public abstract int deleteAll(); + int deleteAll(); @Override - public Flowable> listByService(final int serviceId) { + default Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } @Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") - public abstract Flowable> getState(long streamId); + Flowable> getState(long streamId); @Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") - public abstract int deleteState(long streamId); + int deleteState(long streamId); @Insert(onConflict = OnConflictStrategy.IGNORE) - abstract void silentInsertInternal(StreamStateEntity streamState); + void silentInsertInternal(StreamStateEntity streamState); @Transaction - public long upsert(final StreamStateEntity stream) { + default long upsert(final StreamStateEntity stream) { silentInsertInternal(stream); return update(stream); } From cd0a87785e16ca7813da2be9ec266c5c07616b3c Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 16 May 2021 11:04:52 +0530 Subject: [PATCH 002/116] Update Room to 2.3.0. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c9ca07b42..972b4eaaa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,7 +102,7 @@ ext { checkstyleVersion = '8.38' androidxLifecycleVersion = '2.2.0' - androidxRoomVersion = '2.3.0-alpha03' + androidxRoomVersion = '2.3.0' icepickVersion = '3.2.0' exoPlayerVersion = '2.13.3' From e8b83918682a00b9858a72082a76a67c1d16aabc Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 16 May 2021 11:14:15 +0530 Subject: [PATCH 003/116] Rename .java to .kt --- .../schabi/newpipe/database/{Converters.java => Converters.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/org/schabi/newpipe/database/{Converters.java => Converters.kt} (100%) diff --git a/app/src/main/java/org/schabi/newpipe/database/Converters.java b/app/src/main/java/org/schabi/newpipe/database/Converters.kt similarity index 100% rename from app/src/main/java/org/schabi/newpipe/database/Converters.java rename to app/src/main/java/org/schabi/newpipe/database/Converters.kt From 3b1c4b043d7e2fe33f6f0e4b385d6dddc31ee98e Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 16 May 2021 11:14:15 +0530 Subject: [PATCH 004/116] Convert Converters to a Kotlin object. --- .../org/schabi/newpipe/database/Converters.kt | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/Converters.kt b/app/src/main/java/org/schabi/newpipe/database/Converters.kt index c46b5f427..0eafcede1 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Converters.kt +++ b/app/src/main/java/org/schabi/newpipe/database/Converters.kt @@ -1,64 +1,52 @@ -package org.schabi.newpipe.database; +package org.schabi.newpipe.database -import androidx.room.TypeConverter; - -import org.schabi.newpipe.extractor.stream.StreamType; -import org.schabi.newpipe.local.subscription.FeedGroupIcon; - -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; - -public final class Converters { - private Converters() { } +import androidx.room.TypeConverter +import org.schabi.newpipe.extractor.stream.StreamType +import org.schabi.newpipe.local.subscription.FeedGroupIcon +import java.time.Instant +import java.time.OffsetDateTime +import java.time.ZoneOffset +object Converters { /** - * Convert a long value to a {@link OffsetDateTime}. + * Convert a long value to a [OffsetDateTime]. * * @param value the long value - * @return the {@code OffsetDateTime} + * @return the `OffsetDateTime` */ @TypeConverter - public static OffsetDateTime offsetDateTimeFromTimestamp(final Long value) { - return value == null ? null : OffsetDateTime.ofInstant(Instant.ofEpochMilli(value), - ZoneOffset.UTC); + fun offsetDateTimeFromTimestamp(value: Long?): OffsetDateTime? { + return value?.let { OffsetDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC) } } /** - * Convert a {@link OffsetDateTime} to a long value. + * Convert a [OffsetDateTime] to a long value. * - * @param offsetDateTime the {@code OffsetDateTime} + * @param offsetDateTime the `OffsetDateTime` * @return the long value */ @TypeConverter - public static Long offsetDateTimeToTimestamp(final OffsetDateTime offsetDateTime) { - return offsetDateTime == null ? null : offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC) - .toInstant().toEpochMilli(); + fun offsetDateTimeToTimestamp(offsetDateTime: OffsetDateTime?): Long? { + return offsetDateTime?.withOffsetSameInstant(ZoneOffset.UTC)?.toInstant()?.toEpochMilli() } @TypeConverter - public static StreamType streamTypeOf(final String value) { - return StreamType.valueOf(value); + fun streamTypeOf(value: String): StreamType { + return StreamType.valueOf(value) } @TypeConverter - public static String stringOf(final StreamType streamType) { - return streamType.name(); + fun stringOf(streamType: StreamType): String { + return streamType.name } @TypeConverter - public static Integer integerOf(final FeedGroupIcon feedGroupIcon) { - return feedGroupIcon.getId(); + fun integerOf(feedGroupIcon: FeedGroupIcon): Int { + return feedGroupIcon.id } @TypeConverter - public static FeedGroupIcon feedGroupIconOf(final Integer id) { - for (final FeedGroupIcon icon : FeedGroupIcon.values()) { - if (icon.getId() == id) { - return icon; - } - } - - throw new IllegalArgumentException("There's no feed group icon with the id \"" + id + "\""); + fun feedGroupIconOf(id: Int): FeedGroupIcon { + return FeedGroupIcon.values().first { it.id == id } } } From 96d731dfc7e265cd0333499d70f6a13c3515e81c Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:06:59 -0400 Subject: [PATCH 005/116] Refactor equals method --- .../main/java/org/schabi/newpipe/settings/tabs/Tab.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index b289009ce..faa5aeba9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -112,12 +112,11 @@ public abstract class Tab { @Override public boolean equals(final Object obj) { - if (obj == this) { - return true; + if (!(obj instanceof Tab)) { + return false; } - - return obj instanceof Tab && obj.getClass() == this.getClass() - && ((Tab) obj).getTabId() == this.getTabId(); + final Tab other = (Tab) obj; + return getTabId() == other.getTabId(); } /*////////////////////////////////////////////////////////////////////////// From 01aab25889aa0daabf3c476bcda0e1b12ee6dfd6 Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:07:56 -0400 Subject: [PATCH 006/116] Add Tab.hashCode() to go with equals --- app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index faa5aeba9..ef683c033 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -119,6 +119,11 @@ public abstract class Tab { return getTabId() == other.getTabId(); } + @Override + public int hashCode() { + return Objects.hashCode(getTabId()); + } + /*////////////////////////////////////////////////////////////////////////// // JSON Handling //////////////////////////////////////////////////////////////////////////*/ From 1cac3895dccfe848bdbc1170dfd49873c4287e5f Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:13:17 -0400 Subject: [PATCH 007/116] Refactor KioskTab.equals --- .../main/java/org/schabi/newpipe/settings/tabs/Tab.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index ef683c033..ae5cd5e9c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -362,8 +362,13 @@ public abstract class Tab { @Override public boolean equals(final Object obj) { - return super.equals(obj) && kioskServiceId == ((KioskTab) obj).kioskServiceId - && Objects.equals(kioskId, ((KioskTab) obj).kioskId); + if (!(obj instanceof KioskTab)) { + return false; + } + final KioskTab other = (KioskTab) obj; + return super.equals(obj) + && kioskServiceId == other.kioskServiceId + && kioskId.equals(other.kioskId); } public int getKioskServiceId() { From d2b03afcf44a2e33720666afdad0b97cd7817b70 Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:13:49 -0400 Subject: [PATCH 008/116] Add KioskTab.hashCode() matching equals --- app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index ae5cd5e9c..60d18747b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -371,6 +371,11 @@ public abstract class Tab { && kioskId.equals(other.kioskId); } + @Override + public int hashCode() { + return Objects.hash(getTabId(), kioskServiceId, kioskId); + } + public int getKioskServiceId() { return kioskServiceId; } From 14bbaccb9f8fbe7f7f5716319078f547cfc28221 Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:22:44 -0400 Subject: [PATCH 009/116] Refactor ChannelTab.equals --- .../java/org/schabi/newpipe/settings/tabs/Tab.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index 60d18747b..f2aec4a12 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -446,9 +446,14 @@ public abstract class Tab { @Override public boolean equals(final Object obj) { - return super.equals(obj) && channelServiceId == ((ChannelTab) obj).channelServiceId - && Objects.equals(channelUrl, ((ChannelTab) obj).channelUrl) - && Objects.equals(channelName, ((ChannelTab) obj).channelName); + if (!(obj instanceof ChannelTab)) { + return false; + } + final ChannelTab other = (ChannelTab) obj; + return super.equals(obj) + && channelServiceId == other.channelServiceId + && channelUrl.equals(other.channelName) + && channelName.equals(other.channelName); } public int getChannelServiceId() { From 1812249d3787464bed9796b694df7e08caec339a Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:23:32 -0400 Subject: [PATCH 010/116] Add ChannelTab.hashCode() matching equals --- app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index f2aec4a12..ded5431a3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -456,6 +456,11 @@ public abstract class Tab { && channelName.equals(other.channelName); } + @Override + public int hashCode() { + return Objects.hash(getTabId(), channelServiceId, channelUrl, channelName); + } + public int getChannelServiceId() { return channelServiceId; } From 2fc26bc154829e2f0848e7021fe850803f8f7d69 Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:34:19 -0400 Subject: [PATCH 011/116] Refactor PlaylistTab.equals --- .../org/schabi/newpipe/settings/tabs/Tab.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index ded5431a3..7c72706f2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -600,15 +600,22 @@ public abstract class Tab { @Override public boolean equals(final Object obj) { - if (!(super.equals(obj) - && Objects.equals(playlistType, ((PlaylistTab) obj).playlistType) - && Objects.equals(playlistName, ((PlaylistTab) obj).playlistName))) { - return false; // base objects are different + if (!(obj instanceof PlaylistTab)) { + return false; } - return (playlistId == ((PlaylistTab) obj).playlistId) // local - || (playlistServiceId == ((PlaylistTab) obj).playlistServiceId // remote - && Objects.equals(playlistUrl, ((PlaylistTab) obj).playlistUrl)); + final PlaylistTab other = (PlaylistTab) obj; + + if (!(super.equals(obj))) { + // Base objects are different + return false; + } + + return playlistServiceId == other.playlistServiceId // Remote + && playlistId == other.playlistId // Local + && playlistUrl.equals(other.playlistUrl) + && playlistName.equals(other.playlistName) + && playlistType == other.playlistType; } public int getPlaylistServiceId() { From 448989f32f73259aa164eb6319ccdc91bc67b91b Mon Sep 17 00:00:00 2001 From: Eric Xu Date: Sat, 22 May 2021 00:37:39 -0400 Subject: [PATCH 012/116] Add PlaylistTab.hashCode() matching equals --- .../java/org/schabi/newpipe/settings/tabs/Tab.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index 7c72706f2..2e84c1e11 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -618,6 +618,18 @@ public abstract class Tab { && playlistType == other.playlistType; } + @Override + public int hashCode() { + return Objects.hash( + getTabId(), + playlistServiceId, + playlistId, + playlistUrl, + playlistName, + playlistType + ); + } + public int getPlaylistServiceId() { return playlistServiceId; } From a102fc9cad2ebfd9c464a6bddff9db7c273d3bd2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 7 Jun 2021 07:57:26 +0200 Subject: [PATCH 013/116] Use constraint layout for play queue item Also remove invalid ic_selected attribute --- .../subscription/SubscriptionFragment.kt | 6 +- .../player/playqueue/PlayQueueAdapter.java | 8 +- .../player/playqueue/PlayQueueItemHolder.java | 2 - app/src/main/res/layout/play_queue_item.xml | 93 +++++++++---------- app/src/main/res/values/attrs.xml | 3 - app/src/main/res/values/dimens.xml | 4 +- 6 files changed, 56 insertions(+), 60 deletions(-) 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 13643af15..8a235fa8a 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 @@ -294,8 +294,10 @@ class SubscriptionFragment : BaseStateFragment() { } private fun showLongTapDialog(selectedItem: ChannelInfoItem) { - val commands = arrayOf(getString(R.string.share), getString(R.string.open_in_browser), - getString(R.string.unsubscribe)) + val commands = arrayOf( + getString(R.string.share), getString(R.string.open_in_browser), + getString(R.string.unsubscribe) + ) val actions = DialogInterface.OnClickListener { _, i -> when (i) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java index 462b9eb53..dd95fb4d5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java @@ -182,8 +182,10 @@ public class PlayQueueAdapter extends RecyclerView.Adapter - - - + android:foreground="?attr/selectableItemBackground"> - - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintBottom_toTopOf="@+id/itemAdditionalDetails" + app:layout_constraintEnd_toStartOf="@+id/itemHandle" + app:layout_constraintStart_toEndOf="@+id/itemThumbnailView" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="Lorem ipsum dolor sit amet, consectetur adipisci elit. " /> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/itemHandle" + app:layout_constraintStart_toEndOf="@+id/itemThumbnailView" + app:layout_constraintTop_toBottomOf="@+id/itemVideoTitleView" + tools:text="Uploader uploader uploader uploader uploader · Youtube" /> - + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 80572e14e..01dbfce15 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,8 +1,5 @@ - - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ea26be57e..b55ad781c 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -114,9 +114,9 @@ 24dp 28dp - + 62dp - 40dp + 35dp 30sp From 3b0045917c6107ccf1183a10644fd36d5944d46e Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 7 Jun 2021 09:25:49 +0200 Subject: [PATCH 014/116] Increase buffer for playback after rebuffer --- .../java/org/schabi/newpipe/player/helper/LoadController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java index b04812a12..e4ae27750 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java @@ -32,7 +32,8 @@ public class LoadController implements LoadControl { final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder(); builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs, - initialPlaybackBufferMs, initialPlaybackBufferMs); + initialPlaybackBufferMs, + DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); internalLoadControl = builder.build(); } From 6ad4b425e49b3a69863fe97c77dc930ceb08e778 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 2 Apr 2021 21:41:06 +0200 Subject: [PATCH 015/116] Better error handling of terminated channels when loading feed --- app/build.gradle | 2 +- .../schabi/newpipe/local/feed/FeedFragment.kt | 75 ++++++++++++++++++- .../local/feed/service/FeedLoadService.kt | 21 +++++- app/src/main/res/values/strings.xml | 5 ++ 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ed9fad797..054d91fa5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -183,7 +183,7 @@ dependencies { /** NewPipe libraries **/ // You can use a local version by uncommenting a few lines in settings.gradle implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.4' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:d4186d100b6c6dddfcf3cf4b004f5960a8bf441d' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" 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 1df999144..f0637e6ba 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 @@ -19,6 +19,7 @@ package org.schabi.newpipe.local.feed +import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.os.Parcelable @@ -28,6 +29,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.annotation.Nullable import androidx.appcompat.app.AlertDialog import androidx.core.content.edit import androidx.core.os.bundleOf @@ -35,15 +37,24 @@ import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager import icepick.State +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.R 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.UserAction +import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.fragments.list.BaseListFragment import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling import org.schabi.newpipe.local.feed.service.FeedLoadService +import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.Localization import java.time.OffsetDateTime @@ -51,6 +62,8 @@ class FeedFragment : BaseListFragment() { private var _feedBinding: FragmentFeedBinding? = null private val feedBinding get() = _feedBinding!! + private val disposables = CompositeDisposable() + private lateinit var viewModel: FeedViewModel @State @JvmField @@ -158,6 +171,7 @@ class FeedFragment : BaseListFragment() { } override fun onDestroy() { + disposables.dispose() super.onDestroy() activity?.supportActionBar?.subtitle = null } @@ -243,9 +257,9 @@ class FeedFragment : BaseListFragment() { oldestSubscriptionUpdate = loadedState.oldestUpdate - val loadedCount = loadedState.notLoadedCount > 0 - feedBinding.refreshSubtitleText.isVisible = loadedCount - if (loadedCount) { + val feedsNotLoaded = loadedState.notLoadedCount > 0 + feedBinding.refreshSubtitleText.isVisible = feedsNotLoaded + if (feedsNotLoaded) { feedBinding.refreshSubtitleText.text = getString( R.string.feed_subscription_not_loaded_count, loadedState.notLoadedCount @@ -264,11 +278,64 @@ class FeedFragment : BaseListFragment() { hideLoading() false } else { - showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) + if (errorState.error is FeedLoadService.RequestException) { + disposables.add( + Single.fromCallable { + NewPipeDatabase.getInstance(requireContext()).subscriptionDAO() + .getSubscription(errorState.error.subscriptionId) + }.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + subscriptionEntity -> + handleFeedNotAvailable( + subscriptionEntity, + errorState.error.cause?.cause + ) + }, + { throwable -> throwable.printStackTrace() } + ) + ) + } else { + showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) + } true } } + private fun handleFeedNotAvailable( + subscriptionEntity: SubscriptionEntity, + @Nullable cause: Throwable? + ) { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + val isFastFeedModeEnabled = sharedPreferences.getBoolean( + getString(R.string.feed_use_dedicated_fetch_method_key), false + ) + val builder = AlertDialog.Builder(requireContext()) + .setTitle(R.string.feed_load_error) + .setPositiveButton( + R.string.unsubscribe, + DialogInterface.OnClickListener { + _, _ -> + SubscriptionManager(requireContext()).deleteSubscription( + subscriptionEntity.serviceId, subscriptionEntity.url + ).subscribe() + } + ) + .setNegativeButton(R.string.cancel, DialogInterface.OnClickListener { _, _ -> }) + if (cause is AccountTerminatedException) { + builder.setMessage(R.string.feed_load_error_terminated) + } else if (cause is ContentNotAvailableException && isFastFeedModeEnabled) { + builder.setMessage(R.string.feed_load_error_fast_unknown) + .setNeutralButton(R.string.feed_use_dedicated_fetch_method_disable_button) { _, _ -> + sharedPreferences.edit { + putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false) + } + } + } + builder.create().show() + } + private fun updateRelativeTimeViews() { updateRefreshViewState() infoListAdapter.notifyDataSetChanged() diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt index 5ed7998d2..7ca583317 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt @@ -48,6 +48,7 @@ import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.extractor.ListInfo +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.ktx.isNetworkRelated @@ -162,7 +163,7 @@ class FeedLoadService : Service() { // Loading & Handling // ///////////////////////////////////////////////////////////////////////// - private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) { + public class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) { companion object { fun wrapList(subscriptionId: Long, info: ListInfo): List { val toReturn = ArrayList(info.errors.size) @@ -334,8 +335,9 @@ class FeedLoadService : Service() { private val errorHandlingConsumer: Consumer>>> get() = Consumer { if (it.isOnError) { - var error = it.error!! - if (error is RequestException) error = error.cause!! + var maybeWrapper = it.error!! + val error = if (maybeWrapper is RequestException) maybeWrapper.cause!! + else maybeWrapper val cause = error.cause when { @@ -345,6 +347,19 @@ class FeedLoadService : Service() { error is IOException -> throw error cause is IOException -> throw cause error.isNetworkRelated -> throw IOException(error) + + cause is ContentNotAvailableException -> { + // maybeWrapper is definitely a RequestException, + // because this is an exception thrown in the extractor + if (maybeWrapper is RequestException) { + throw maybeWrapper + } else { + if (DEBUG) { + Log.d(TAG, "Cause is ContentNotAvailableException, but maybeWrapper is not a RequestException") + } + throw cause // should never be the case + } + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4fb4019bd..6fc4e73cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -690,11 +690,16 @@ Feed update threshold Time after last update before a subscription is considered outdated — %s Always update + Error loading feed + Could not load feed for \'%s\'. + The author\'s account has been terminated.\nNewPipe will not be able to load this feed in the future.\Do you want to unsubscribe from this channel? + The fast feed mode does not provide more info on this. Fetch from dedicated feed when available Available in some services, it is usually much faster but may return a limited amount of items and often incomplete information (e.g. no duration, item type, no live status). Enable fast mode Disable fast mode Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information. + This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. Channel\'s avatar thumbnail Created by %s From ccc46971b4990f4e28eec09e6db3c7fe94e7ec2d Mon Sep 17 00:00:00 2001 From: TobiGr Date: Wed, 31 Mar 2021 22:46:52 +0200 Subject: [PATCH 016/116] Show detailed error message when an account has been terminated by the service --- .../org/schabi/newpipe/error/ErrorInfo.kt | 2 ++ .../schabi/newpipe/error/ErrorPanelHelper.kt | 33 +++++++++++++++++++ app/src/main/res/layout/error_panel.xml | 24 +++++++++++++- app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index e1249bc83..487e7c7fb 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -6,6 +6,7 @@ import kotlinx.android.parcel.Parcelize import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.NewPipe +import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException import org.schabi.newpipe.extractor.exceptions.ExtractionException @@ -95,6 +96,7 @@ class ErrorInfo( action: UserAction ): Int { return when { + throwable is AccountTerminatedException -> R.string.account_terminated throwable is ContentNotAvailableException -> R.string.content_not_available throwable != null && throwable.isNetworkRelated -> R.string.network_error throwable is ContentNotSupportedException -> R.string.content_not_supported diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index 49bcfa926..e790c5fc5 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -13,6 +13,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.NewPipe +import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException @@ -22,9 +24,11 @@ import org.schabi.newpipe.extractor.exceptions.PrivateContentException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException +import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.ktx.isInterruptedCaused import org.schabi.newpipe.ktx.isNetworkRelated +import org.schabi.newpipe.util.ServiceHelper import java.util.concurrent.TimeUnit class ErrorPanelHelper( @@ -35,6 +39,8 @@ class ErrorPanelHelper( private val context: Context = rootView.context!! private val errorPanelRoot: View = rootView.findViewById(R.id.error_panel) private val errorTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_view) + private val errorServiceInfoTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_service_info_view) + private val errorServiceExplenationTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_service_explenation_view) private val errorButtonAction: Button = errorPanelRoot.findViewById(R.id.error_button_action) private val errorButtonRetry: Button = errorPanelRoot.findViewById(R.id.error_button_retry) @@ -70,13 +76,40 @@ class ErrorPanelHelper( errorButtonAction.setOnClickListener(null) } errorTextView.setText(R.string.recaptcha_request_toast) + // additional info is only provided by AccountTerminatedException + errorServiceInfoTextView.isVisible = false + errorServiceExplenationTextView.isVisible = false errorButtonRetry.isVisible = true + } else if (errorInfo.throwable is AccountTerminatedException) { + errorButtonRetry.isVisible = false + errorButtonAction.isVisible = false + errorTextView.setText(R.string.account_terminated) + if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) { + errorServiceInfoTextView.setText( + context.resources.getString( + R.string.service_provides_reason, + NewPipe.getNameOfService(ServiceHelper.getSelectedServiceId(context)) + ) + ) + errorServiceExplenationTextView.setText( + (errorInfo.throwable as AccountTerminatedException).message + ) + errorServiceInfoTextView.isVisible = true + errorServiceExplenationTextView.isVisible = true + } else { + errorServiceInfoTextView.isVisible = false + errorServiceExplenationTextView.isVisible = false + } } else { errorButtonAction.setText(R.string.error_snackbar_action) errorButtonAction.setOnClickListener { ErrorActivity.reportError(context, errorInfo) } + // additional info is only provided by AccountTerminatedException + errorServiceInfoTextView.isVisible = false + errorServiceExplenationTextView.isVisible = false + // hide retry button by default, then show only if not unavailable/unsupported content errorButtonRetry.isVisible = false errorTextView.setText( diff --git a/app/src/main/res/layout/error_panel.xml b/app/src/main/res/layout/error_panel.xml index 5141b66b8..355dd17e3 100644 --- a/app/src/main/res/layout/error_panel.xml +++ b/app/src/main/res/layout/error_panel.xml @@ -15,7 +15,29 @@ android:text="@string/general_error" android:textSize="16sp" android:textStyle="bold" - tools:text="Network error" /> + tools:text="Account terminated" /> + + + + +