From a37ea96446569b22d3d551c652a038d513a40dc2 Mon Sep 17 00:00:00 2001 From: 75ohmantenna <75ohmantenna@proton.me> Date: Wed, 19 Aug 2026 16:30:46 -0700 Subject: [PATCH 1/2] fix: show paid badges in local playlists Show the paid badge instead of duration or playback progress for paid local-playlist entries, and preserve the membership flag when converting playlist entries. Bug report: https://github.com/InfinityLoop1308/PipePipeClient/issues/90 --- .../database/playlist/PlaylistStreamEntry.kt | 1 + .../newpipe/info_list/ComposeItemUiHelper.kt | 49 ++++++++++++++----- .../holder/LocalPlaylistStreamItemHolder.java | 12 ++++- .../playlist/PlaylistStreamEntryTest.kt | 29 +++++++++++ .../PlaylistStreamPresentationTest.kt | 23 +++++++++ 5 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt create mode 100644 app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt index d2543ae6d6..37e3114efe 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt @@ -29,6 +29,7 @@ data class PlaylistStreamEntry( item.uploaderName = streamEntity.uploader item.uploaderUrl = streamEntity.uploaderUrl item.thumbnailUrl = streamEntity.thumbnailUrl + item.setRequiresMembership(streamEntity.isPaid) return item } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt b/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt index c7f300f297..97b912e63f 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt @@ -215,6 +215,30 @@ data class ComposeItemState( val isChannel: Boolean ) +internal data class PlaylistStreamPresentation( + val durationText: String?, + val showPaidBadge: Boolean, + val progress: Float? +) + +internal fun buildPlaylistStreamPresentation( + isPaid: Boolean, + durationSeconds: Long, + progressMillis: Long, + durationText: String?, + paidText: String +): PlaylistStreamPresentation { + return PlaylistStreamPresentation( + durationText = if (isPaid) paidText else durationText, + showPaidBadge = isPaid, + progress = if (!isPaid && progressMillis > 0 && durationSeconds > 0) { + TimeUnit.MILLISECONDS.toSeconds(progressMillis).toFloat() / durationSeconds.toFloat() + } else { + null + } + ) +} + fun buildInfoItemState( context: Context, item: InfoItem, @@ -321,6 +345,17 @@ fun buildLocalItemState( return when (item.localItemType) { LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM -> { item as PlaylistStreamEntry + val presentation = buildPlaylistStreamPresentation( + isPaid = item.streamEntity.isPaid, + durationSeconds = item.streamEntity.duration, + progressMillis = item.progressMillis, + durationText = if (item.streamEntity.duration > 0) { + Localization.getDurationString(item.streamEntity.duration) + } else { + null + }, + paidText = context.getString(R.string.paid_video) + ) ComposeItemState( title = item.streamEntity.title, subtitle = null, @@ -329,18 +364,10 @@ fun buildLocalItemState( NewPipe.getNameOfService(item.streamEntity.serviceId) ), imageUrl = item.streamEntity.thumbnailUrl, - durationText = if (item.streamEntity.duration > 0) { - Localization.getDurationString(item.streamEntity.duration) - } else { - null - }, + durationText = presentation.durationText, showLiveBadge = false, - showPaidBadge = false, - progress = if (item.progressMillis > 0 && item.streamEntity.duration > 0) { - TimeUnit.MILLISECONDS.toSeconds(item.progressMillis).toFloat() / item.streamEntity.duration.toFloat() - } else { - null - }, + showPaidBadge = presentation.showPaidBadge, + progress = presentation.progress, playlistCount = null, isChannel = false ) diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index e681f6fe48..143036ccdc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -66,7 +66,13 @@ public void updateFromItem(final LocalItem localItem, .concatenateStrings(item.getStreamEntity().getUploader(), NewPipe.getNameOfService(item.getStreamEntity().getServiceId()))); - if (item.getStreamEntity().getDuration() > 0) { + if (item.getStreamEntity().isPaid()) { + itemDurationView.setText(R.string.paid_video); + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + R.color.paid_video_background_color)); + itemDurationView.setVisibility(View.VISIBLE); + itemProgressView.setVisibility(View.GONE); + } else if (item.getStreamEntity().getDuration() > 0) { itemDurationView.setText(Localization .getDurationString(item.getStreamEntity().getDuration())); itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), @@ -114,7 +120,9 @@ public void updateState(final LocalItem localItem, } final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; - if (item.getProgressMillis() > 0 && item.getStreamEntity().getDuration() > 0) { + if (!item.getStreamEntity().isPaid() + && item.getProgressMillis() > 0 + && item.getStreamEntity().getDuration() > 0) { itemProgressView.setMax((int) item.getStreamEntity().getDuration()); if (itemProgressView.getVisibility() == View.VISIBLE) { itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS diff --git a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt new file mode 100644 index 0000000000..781e6fa984 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt @@ -0,0 +1,29 @@ +package org.schabi.newpipe.database.playlist + +import org.junit.Assert.assertTrue +import org.junit.Test +import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.extractor.stream.StreamType + +class PlaylistStreamEntryTest { + @Test + fun `toStreamInfoItem preserves paid membership flag`() { + val stream = StreamEntity( + serviceId = 0, + url = "https://example.com/watch?v=paid", + title = "Paid video", + streamType = StreamType.VIDEO_STREAM, + duration = 60, + uploader = "Channel", + isPaid = true + ) + val entry = PlaylistStreamEntry( + streamEntity = stream, + progressMillis = 0, + streamId = 1, + joinIndex = 0 + ) + + assertTrue(entry.toStreamInfoItem().requiresMembership()) + } +} diff --git a/app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt b/app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt new file mode 100644 index 0000000000..5e670c1d7e --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt @@ -0,0 +1,23 @@ +package org.schabi.newpipe.info_list + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test + +class PlaylistStreamPresentationTest { + @Test + fun `paid playlist stream hides duration and progress`() { + val presentation = buildPlaylistStreamPresentation( + isPaid = true, + durationSeconds = 60, + progressMillis = 30_000, + durationText = "1:00", + paidText = "Paid" + ) + + assertEquals("Paid", presentation.durationText) + assertTrue(presentation.showPaidBadge) + assertNull(presentation.progress) + } +} From 306b7bc52ab0154f94ac4ad14958999342e30a8c Mon Sep 17 00:00:00 2001 From: InfinityLoop1308 <96324692+InfinityLoop1308@users.noreply.github.com> Date: Sat, 22 Aug 2026 08:35:04 +0800 Subject: [PATCH 2/2] chore: unsupported tests and deprecated Compose changes included --- .../newpipe/info_list/ComposeItemUiHelper.kt | 49 +++++-------------- .../playlist/PlaylistStreamEntryTest.kt | 29 ----------- .../PlaylistStreamPresentationTest.kt | 23 --------- 3 files changed, 11 insertions(+), 90 deletions(-) delete mode 100644 app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt delete mode 100644 app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt b/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt index 97b912e63f..c7f300f297 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/ComposeItemUiHelper.kt @@ -215,30 +215,6 @@ data class ComposeItemState( val isChannel: Boolean ) -internal data class PlaylistStreamPresentation( - val durationText: String?, - val showPaidBadge: Boolean, - val progress: Float? -) - -internal fun buildPlaylistStreamPresentation( - isPaid: Boolean, - durationSeconds: Long, - progressMillis: Long, - durationText: String?, - paidText: String -): PlaylistStreamPresentation { - return PlaylistStreamPresentation( - durationText = if (isPaid) paidText else durationText, - showPaidBadge = isPaid, - progress = if (!isPaid && progressMillis > 0 && durationSeconds > 0) { - TimeUnit.MILLISECONDS.toSeconds(progressMillis).toFloat() / durationSeconds.toFloat() - } else { - null - } - ) -} - fun buildInfoItemState( context: Context, item: InfoItem, @@ -345,17 +321,6 @@ fun buildLocalItemState( return when (item.localItemType) { LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM -> { item as PlaylistStreamEntry - val presentation = buildPlaylistStreamPresentation( - isPaid = item.streamEntity.isPaid, - durationSeconds = item.streamEntity.duration, - progressMillis = item.progressMillis, - durationText = if (item.streamEntity.duration > 0) { - Localization.getDurationString(item.streamEntity.duration) - } else { - null - }, - paidText = context.getString(R.string.paid_video) - ) ComposeItemState( title = item.streamEntity.title, subtitle = null, @@ -364,10 +329,18 @@ fun buildLocalItemState( NewPipe.getNameOfService(item.streamEntity.serviceId) ), imageUrl = item.streamEntity.thumbnailUrl, - durationText = presentation.durationText, + durationText = if (item.streamEntity.duration > 0) { + Localization.getDurationString(item.streamEntity.duration) + } else { + null + }, showLiveBadge = false, - showPaidBadge = presentation.showPaidBadge, - progress = presentation.progress, + showPaidBadge = false, + progress = if (item.progressMillis > 0 && item.streamEntity.duration > 0) { + TimeUnit.MILLISECONDS.toSeconds(item.progressMillis).toFloat() / item.streamEntity.duration.toFloat() + } else { + null + }, playlistCount = null, isChannel = false ) diff --git a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt deleted file mode 100644 index 781e6fa984..0000000000 --- a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntryTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.schabi.newpipe.database.playlist - -import org.junit.Assert.assertTrue -import org.junit.Test -import org.schabi.newpipe.database.stream.model.StreamEntity -import org.schabi.newpipe.extractor.stream.StreamType - -class PlaylistStreamEntryTest { - @Test - fun `toStreamInfoItem preserves paid membership flag`() { - val stream = StreamEntity( - serviceId = 0, - url = "https://example.com/watch?v=paid", - title = "Paid video", - streamType = StreamType.VIDEO_STREAM, - duration = 60, - uploader = "Channel", - isPaid = true - ) - val entry = PlaylistStreamEntry( - streamEntity = stream, - progressMillis = 0, - streamId = 1, - joinIndex = 0 - ) - - assertTrue(entry.toStreamInfoItem().requiresMembership()) - } -} diff --git a/app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt b/app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt deleted file mode 100644 index 5e670c1d7e..0000000000 --- a/app/src/test/java/org/schabi/newpipe/info_list/PlaylistStreamPresentationTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.schabi.newpipe.info_list - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test - -class PlaylistStreamPresentationTest { - @Test - fun `paid playlist stream hides duration and progress`() { - val presentation = buildPlaylistStreamPresentation( - isPaid = true, - durationSeconds = 60, - progressMillis = 30_000, - durationText = "1:00", - paidText = "Paid" - ) - - assertEquals("Paid", presentation.durationText) - assertTrue(presentation.showPaidBadge) - assertNull(presentation.progress) - } -}