Skip to content

Commit 59dd6e4

Browse files
committed
feat: allow filtering out paid feed streams
This commit adds a new "Members only" filter to the feed page. To achieve this, a database migration is required to save the content availability of streams. Closes #12883 Relates to #12011 Relates to #12040
1 parent 515bb6e commit 59dd6e4

11 files changed

Lines changed: 798 additions & 26 deletions

File tree

app/schemas/org.schabi.newpipe.database.AppDatabase/10.json

Lines changed: 706 additions & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.schabi.newpipe.database.Migrations.MIGRATION_5_6
1818
import org.schabi.newpipe.database.Migrations.MIGRATION_6_7
1919
import org.schabi.newpipe.database.Migrations.MIGRATION_7_8
2020
import org.schabi.newpipe.database.Migrations.MIGRATION_8_9
21+
import org.schabi.newpipe.database.Migrations.MIGRATION_9_10
2122

2223
object NewPipeDatabase {
2324

@@ -37,7 +38,8 @@ object NewPipeDatabase {
3738
MIGRATION_5_6,
3839
MIGRATION_6_7,
3940
MIGRATION_7_8,
40-
MIGRATION_8_9
41+
MIGRATION_8_9,
42+
MIGRATION_9_10
4143
).build()
4244
}
4345

app/src/main/java/org/schabi/newpipe/database/AppDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
3434

3535
@TypeConverters(Converters::class)
3636
@Database(
37-
version = Migrations.DB_VER_9,
37+
version = Migrations.DB_VER_10,
3838
entities = [
3939
SubscriptionEntity::class,
4040
SearchHistoryEntry::class,

app/src/main/java/org/schabi/newpipe/database/Migrations.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ object Migrations {
2929
const val DB_VER_7 = 7
3030
const val DB_VER_8 = 8
3131
const val DB_VER_9 = 9
32+
const val DB_VER_10 = 10
3233

3334
private val TAG = Migrations::class.java.getName()
3435
private val isDebug = MainActivity.DEBUG
@@ -348,4 +349,20 @@ object Migrations {
348349
db.endTransaction()
349350
}
350351
}
352+
353+
val MIGRATION_9_10 = Migration(DB_VER_9, DB_VER_10) { db ->
354+
try {
355+
db.beginTransaction()
356+
357+
// Create a new column content_availability
358+
db.execSQL(
359+
"ALTER TABLE `streams` ADD COLUMN `availability` " +
360+
"TEXT NOT NULL DEFAULT 'UNKNOWN'"
361+
)
362+
363+
db.setTransactionSuccessful()
364+
} finally {
365+
db.endTransaction()
366+
}
367+
}
351368
}

app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@ abstract class FeedDAO {
2323
abstract fun deleteAll(): Int
2424

2525
/**
26-
* @param groupId the group id to get feed streams of; use
27-
* [FeedGroupEntity.GROUP_ALL_ID] to not filter by group
28-
* @param includePlayed if false, only return all of the live, never-played or non-finished
29-
* feed streams (see `@see` items); if true no filter is applied
30-
* @param uploadDateBefore get only streams uploaded before this date (useful to filter out
31-
* future streams); use null to not filter by upload date
26+
* @param groupId the group id to get feed streams of; use
27+
* [FeedGroupEntity.GROUP_ALL_ID] to not filter by group
28+
* @param includePlayed if false, only return all of the live, non-finished
29+
* feed streams (see `@see` items); if true no filter is applied
30+
* @param includePartiallyPlayed if false, only return all of the never-played
31+
* feed streams (see `@see` items); if true no filter is applied
32+
* @param uploadDateBefore get only streams uploaded before this date (useful to filter out
33+
* future streams); use null to not filter by upload date
34+
* @param includeMembersOnly if false, only return feed streams that are publicly accessible;
35+
* if true no filter is applied
3236
* @return the feed streams filtered according to the conditions provided in the parameters
3337
* @see StreamStateEntity.isFinished()
3438
* @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS
@@ -81,6 +85,11 @@ abstract class FeedDAO {
8185
OR s.upload_date IS NULL
8286
OR s.upload_date < :uploadDateBefore
8387
)
88+
AND (
89+
:includeMembersOnly
90+
OR (s.availability <> 'MEMBERSHIP'
91+
AND s.availability <> 'PAID')
92+
)
8493
8594
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
8695
LIMIT 500
@@ -90,7 +99,8 @@ abstract class FeedDAO {
9099
groupId: Long,
91100
includePlayed: Boolean,
92101
includePartiallyPlayed: Boolean,
93-
uploadDateBefore: OffsetDateTime?
102+
uploadDateBefore: OffsetDateTime?,
103+
includeMembersOnly: Boolean
94104
): Maybe<List<StreamWithState>>
95105

96106
/**

app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SE
1111
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
1212
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
1313
import org.schabi.newpipe.extractor.localization.DateWrapper
14+
import org.schabi.newpipe.extractor.stream.ContentAvailability
1415
import org.schabi.newpipe.extractor.stream.StreamInfo
1516
import org.schabi.newpipe.extractor.stream.StreamInfoItem
1617
import org.schabi.newpipe.extractor.stream.StreamType
@@ -52,6 +53,9 @@ data class StreamEntity(
5253
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
5354
var thumbnailUrl: String? = null,
5455

56+
@ColumnInfo(name = STREAM_AVAILABILITY)
57+
var contentAvailability: ContentAvailability = ContentAvailability.UNKNOWN,
58+
5559
@ColumnInfo(name = STREAM_VIEWS)
5660
var viewCount: Long? = null,
5761

@@ -69,7 +73,8 @@ data class StreamEntity(
6973
serviceId = item.serviceId, url = item.url, title = item.name,
7074
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
7175
uploaderUrl = item.uploaderUrl,
72-
thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails), viewCount = item.viewCount,
76+
thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails),
77+
contentAvailability = item.contentAvailability, viewCount = item.viewCount,
7378
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
7479
isUploadDateApproximation = item.uploadDate?.isApproximation
7580
)
@@ -79,7 +84,8 @@ data class StreamEntity(
7984
serviceId = info.serviceId, url = info.url, title = info.name,
8085
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
8186
uploaderUrl = info.uploaderUrl,
82-
thumbnailUrl = ImageStrategy.imageListToDbUrl(info.thumbnails), viewCount = info.viewCount,
87+
thumbnailUrl = ImageStrategy.imageListToDbUrl(info.thumbnails),
88+
contentAvailability = info.contentAvailability, viewCount = info.viewCount,
8389
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
8490
isUploadDateApproximation = info.uploadDate?.isApproximation
8591
)
@@ -102,6 +108,7 @@ data class StreamEntity(
102108
item.uploaderName = uploader
103109
item.uploaderUrl = uploaderUrl
104110
item.thumbnails = ImageStrategy.dbUrlToImageList(thumbnailUrl)
111+
item.contentAvailability = contentAvailability
105112

106113
if (viewCount != null) item.viewCount = viewCount as Long
107114
item.textualUploadDate = textualUploadDate
@@ -123,6 +130,7 @@ data class StreamEntity(
123130
const val STREAM_UPLOADER = "uploader"
124131
const val STREAM_UPLOADER_URL = "uploader_url"
125132
const val STREAM_THUMBNAIL_URL = "thumbnail_url"
133+
const val STREAM_AVAILABILITY = "availability"
126134

127135
const val STREAM_VIEWS = "view_count"
128136
const val STREAM_TEXTUAL_UPLOAD_DATE = "textual_upload_date"

app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@ class FeedDatabaseManager(context: Context) {
4444
groupId: Long,
4545
includePlayedStreams: Boolean,
4646
includePartiallyPlayedStreams: Boolean,
47-
includeFutureStreams: Boolean
47+
includeFutureStreams: Boolean,
48+
includeMembersOnlyStreams: Boolean
4849
): Maybe<List<StreamWithState>> {
4950
return feedTable.getStreams(
5051
groupId,
5152
includePlayedStreams,
5253
includePartiallyPlayedStreams,
53-
if (includeFutureStreams) null else OffsetDateTime.now()
54+
if (includeFutureStreams) null else OffsetDateTime.now(),
55+
includeMembersOnlyStreams
5456
)
5557
}
5658

app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,15 @@ class FeedFragment : BaseStateFragment<FeedState>() {
246246
val dialogItems = arrayOf(
247247
getString(R.string.feed_show_watched),
248248
getString(R.string.feed_show_partially_watched),
249-
getString(R.string.feed_show_upcoming)
249+
getString(R.string.feed_show_upcoming),
250+
getString(R.string.feed_show_members_only)
250251
)
251252

252253
val checkedDialogItems = booleanArrayOf(
253254
viewModel.getShowPlayedItemsFromPreferences(),
254255
viewModel.getShowPartiallyPlayedItemsFromPreferences(),
255-
viewModel.getShowFutureItemsFromPreferences()
256+
viewModel.getShowFutureItemsFromPreferences(),
257+
viewModel.getShowMembersOnlyItemsFromPreferences()
256258
)
257259

258260
AlertDialog.Builder(requireContext())
@@ -264,6 +266,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
264266
viewModel.setSaveShowPlayedItems(checkedDialogItems[0])
265267
viewModel.setSaveShowPartiallyPlayedItems(checkedDialogItems[1])
266268
viewModel.setSaveShowFutureItems(checkedDialogItems[2])
269+
viewModel.setSaveShowMembersOnlyItems(checkedDialogItems[3])
267270
}
268271
.setNegativeButton(R.string.cancel, null)
269272
.show()

app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import androidx.lifecycle.viewmodel.viewModelFactory
1111
import androidx.preference.PreferenceManager
1212
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
1313
import io.reactivex.rxjava3.core.Flowable
14-
import io.reactivex.rxjava3.functions.Function6
14+
import io.reactivex.rxjava3.functions.Function7
1515
import io.reactivex.rxjava3.processors.BehaviorProcessor
1616
import io.reactivex.rxjava3.schedulers.Schedulers
1717
import java.time.OffsetDateTime
@@ -33,7 +33,8 @@ class FeedViewModel(
3333
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
3434
initialShowPlayedItems: Boolean,
3535
initialShowPartiallyPlayedItems: Boolean,
36-
initialShowFutureItems: Boolean
36+
initialShowFutureItems: Boolean,
37+
initialShowMembersOnlyItems: Boolean
3738
) : ViewModel() {
3839
private val feedDatabaseManager = FeedDatabaseManager(application)
3940

@@ -52,6 +53,11 @@ class FeedViewModel(
5253
.startWithItem(initialShowFutureItems)
5354
.distinctUntilChanged()
5455

56+
private val showMembersOnlyItems = BehaviorProcessor.create<Boolean>()
57+
private val showMembersOnlyItemsFlowable = showMembersOnlyItems
58+
.startWithItem(initialShowMembersOnlyItems)
59+
.distinctUntilChanged()
60+
5561
private val mutableStateLiveData = MutableLiveData<FeedState>()
5662
val stateLiveData: LiveData<FeedState> = mutableStateLiveData
5763

@@ -61,27 +67,29 @@ class FeedViewModel(
6167
showPlayedItemsFlowable,
6268
showPartiallyPlayedItemsFlowable,
6369
showFutureItemsFlowable,
70+
showMembersOnlyItemsFlowable,
6471
feedDatabaseManager.notLoadedCount(groupId),
6572
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
6673

67-
Function6 {
74+
Function7 {
6875
t1: FeedEventManager.Event,
6976
t2: Boolean,
7077
t3: Boolean,
7178
t4: Boolean,
72-
t5: Long,
73-
t6: List<OffsetDateTime?>
79+
t5: Boolean,
80+
t6: Long,
81+
t7: List<OffsetDateTime?>
7482
->
75-
return@Function6 CombineResultEventHolder(t1, t2, t3, t4, t5, t6.firstOrNull())
83+
return@Function7 CombineResultEventHolder(t1, t2, t3, t4, t5, t6, t7.firstOrNull())
7684
}
7785
)
7886
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
7987
.subscribeOn(Schedulers.io())
8088
.observeOn(Schedulers.io())
81-
.map { (event, showPlayedItems, showPartiallyPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) ->
89+
.map { (event, showPlayedItems, showPartiallyPlayedItems, showFutureItems, showMembersOnlyItems, notLoadedCount, oldestUpdate) ->
8290
val streamItems = if (event is SuccessResultEvent || event is IdleEvent) {
8391
feedDatabaseManager
84-
.getStreams(groupId, showPlayedItems, showPartiallyPlayedItems, showFutureItems)
92+
.getStreams(groupId, showPlayedItems, showPartiallyPlayedItems, showFutureItems, showMembersOnlyItems)
8593
.blockingGet(arrayListOf())
8694
} else {
8795
arrayListOf()
@@ -115,8 +123,9 @@ class FeedViewModel(
115123
val t2: Boolean,
116124
val t3: Boolean,
117125
val t4: Boolean,
118-
val t5: Long,
119-
val t6: OffsetDateTime?
126+
val t5: Boolean,
127+
val t6: Long,
128+
val t7: OffsetDateTime?
120129
)
121130

122131
private data class CombineResultDataHolder(
@@ -153,6 +162,15 @@ class FeedViewModel(
153162

154163
fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(application)
155164

165+
fun setSaveShowMembersOnlyItems(showMembersOnlyItems: Boolean) {
166+
this.showMembersOnlyItems.onNext(showMembersOnlyItems)
167+
PreferenceManager.getDefaultSharedPreferences(application).edit {
168+
putBoolean(application.getString(R.string.feed_show_members_only_items_key), showMembersOnlyItems)
169+
}
170+
}
171+
172+
fun getShowMembersOnlyItemsFromPreferences() = getShowMembersOnlyItemsFromPreferences(application)
173+
156174
companion object {
157175
private fun getShowPlayedItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context)
158176
.getBoolean(context.getString(R.string.feed_show_watched_items_key), true)
@@ -163,6 +181,9 @@ class FeedViewModel(
163181
private fun getShowFutureItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context)
164182
.getBoolean(context.getString(R.string.feed_show_future_items_key), true)
165183

184+
private fun getShowMembersOnlyItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context)
185+
.getBoolean(context.getString(R.string.feed_show_members_only_items_key), true)
186+
166187
fun getFactory(context: Context, groupId: Long) = viewModelFactory {
167188
initializer {
168189
FeedViewModel(
@@ -171,7 +192,8 @@ class FeedViewModel(
171192
// Read initial value from preferences
172193
getShowPlayedItemsFromPreferences(context.applicationContext),
173194
getShowPartiallyPlayedItemsFromPreferences(context.applicationContext),
174-
getShowFutureItemsFromPreferences(context.applicationContext)
195+
getShowFutureItemsFromPreferences(context.applicationContext),
196+
getShowMembersOnlyItemsFromPreferences(context.applicationContext)
175197
)
176198
}
177199
}

app/src/main/res/values/settings_keys.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
<string name="feed_show_watched_items_key">feed_show_played_items</string>
365365
<string name="feed_show_partially_watched_items_key">feed_show_partially_watched_items</string>
366366
<string name="feed_show_future_items_key">feed_show_future_items</string>
367+
<string name="feed_show_members_only_items_key">feed_show_members_only_items</string>
367368

368369
<string name="show_thumbnail_key">show_thumbnail_key</string>
369370

0 commit comments

Comments
 (0)