From a5402c13ff2ecb1742049c060e9a7e43c2f56292 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 0cf0ec5470b35c4af86cd002a4387b93ed50e7c0 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 0755c854dbf0b2011368b59d0843e41ec8fdf08a 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 31eb99034fef9f2e768824e5740330916d392941 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 12425eaadc10c0a074dac73c44fe0774846f0600 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 fe5e1df9c25b319cc1cb96cac13df25a9cb1fdf5 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 154e672c9146b11bf8864df9f9c1656f30bb4545 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 68f8c384452081f2a5f10abab3d1d0483d550669 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 346df8a36c655741deb449cc3b12d1daefa20854 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 6ab457e51c1aec9c9154bf681852f717a6ad3756 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 9b4c7e664444ccfd10eec95b6367aa831e02b4fa 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 ad2e1dcf03675e6f399ebbed62a1f312a44f4f0b 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 fb84d8ff36187c5046a6a7f0f226633ad7ccdd4a 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 c96e58374a64c089e236b993a110580a129b1b06 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 81d3e812e0052062845c9dd48d54589b713e729c 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 471a72fcfbc8da04fbd185feae69c6b57c242191 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" /> + + + + +