Update most dependencies

This commit is contained in:
Stypox 2020-10-31 21:55:45 +01:00
parent c88b4032ef
commit f0ca916432
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
87 changed files with 853 additions and 712 deletions

View File

@ -91,18 +91,18 @@ ext {
icepickVersion = '3.2.0' icepickVersion = '3.2.0'
checkstyleVersion = '8.36.2' checkstyleVersion = '8.36.2'
stethoVersion = '1.5.1' stethoVersion = '1.5.1'
leakCanaryVersion = '2.2' leakCanaryVersion = '2.5'
exoPlayerVersion = '2.11.8' exoPlayerVersion = '2.11.8'
androidxLifecycleVersion = '2.2.0' androidxLifecycleVersion = '2.2.0'
androidxRoomVersion = '2.2.5' androidxRoomVersion = '2.3.0-alpha03'
groupieVersion = '2.8.0' groupieVersion = '2.8.1'
markwonVersion = '4.3.1' markwonVersion = '4.6.0'
googleAutoServiceVersion = '1.0-rc7' googleAutoServiceVersion = '1.0-rc7'
} }
configurations { configurations {
checkstyle checkstyle
// ktlint ktlint
} }
checkstyle { checkstyle {
@ -130,24 +130,31 @@ task runCheckstyle(type: Checkstyle) {
} }
} }
//task runKtlint(type: JavaExec) { def outputDir = "${project.buildDir}/reports/ktlint/"
// main = "com.pinterest.ktlint.Main" def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
// classpath = configurations.ktlint
// args "src/**/*.kt" task runKtlint(type: JavaExec) {
//} inputs.files(inputFiles)
// outputs.dir(outputDir)
//task formatKtlint(type: JavaExec) { main = "com.pinterest.ktlint.Main"
// main = "com.pinterest.ktlint.Main" classpath = configurations.ktlint
// classpath = configurations.ktlint args "src/**/*.kt"
// args "-F", "src/**/*.kt" }
//}
task formatKtlint(type: JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "-F", "src/**/*.kt"
}
afterEvaluate { afterEvaluate {
preDebugBuild.dependsOn runCheckstyle //, runKtlint preDebugBuild.dependsOn runCheckstyle, runKtlint
} }
dependencies { dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@ -155,22 +162,23 @@ dependencies {
kapt "frankiesardo:icepick-processor:${icepickVersion}" kapt "frankiesardo:icepick-processor:${icepickVersion}"
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
// ktlint "com.pinterest:ktlint:0.35.0" ktlint "com.pinterest:ktlint:0.39.0"
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}" debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}" debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}" debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}" implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.mockito:mockito-core:3.3.3' testImplementation 'org.mockito:mockito-core:3.5.13'
androidTestImplementation "androidx.test.ext:junit:1.1.1" androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}" androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", { androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0", {
exclude module: 'support-annotations' exclude module: 'support-annotations'
} }
@ -181,28 +189,31 @@ dependencies {
implementation "org.jsoup:jsoup:1.13.1" implementation "org.jsoup:jsoup:1.13.1"
//noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users
implementation "com.squareup.okhttp3:okhttp:3.12.12" implementation "com.squareup.okhttp3:okhttp:3.12.12"
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}" implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}" implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
implementation "com.google.android.material:material:1.1.0" implementation "com.google.android.material:material:1.2.1"
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}" compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}" kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.preference:preference:1.1.1" implementation "androidx.preference:preference:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
implementation "androidx.room:room-runtime:${androidxRoomVersion}" implementation "androidx.room:room-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}" implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
kapt "androidx.room:room-compiler:${androidxRoomVersion}" kapt "androidx.room:room-compiler:${androidxRoomVersion}"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
@ -218,11 +229,11 @@ dependencies {
implementation "com.nononsenseapps:filepicker:4.2.1" implementation "com.nononsenseapps:filepicker:4.2.1"
implementation "ch.acra:acra-core:5.5.0" implementation "ch.acra:acra-core:5.7.0"
implementation "io.reactivex.rxjava2:rxjava:2.2.19" implementation "io.reactivex.rxjava3:rxjava:3.0.7"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0" implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final" implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final"
} }

View File

@ -31,49 +31,62 @@ class AppDatabaseTest {
} }
@get:Rule @get:Rule
val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), val testHelper = MigrationTestHelper(
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()) InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()
)
@Test @Test
fun migrateDatabaseFrom2to3() { fun migrateDatabaseFrom2to3() {
val databaseInV2 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_2) val databaseInV2 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_2)
databaseInV2.run { databaseInV2.run {
insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { insert(
// put("uid", null) "streams", SQLiteDatabase.CONFLICT_FAIL,
put("service_id", DEFAULT_SERVICE_ID) ContentValues().apply {
put("url", DEFAULT_URL) // put("uid", null)
put("title", DEFAULT_TITLE) put("service_id", DEFAULT_SERVICE_ID)
put("stream_type", DEFAULT_TYPE.name) put("url", DEFAULT_URL)
put("duration", DEFAULT_DURATION) put("title", DEFAULT_TITLE)
put("uploader", DEFAULT_UPLOADER_NAME) put("stream_type", DEFAULT_TYPE.name)
put("thumbnail_url", DEFAULT_THUMBNAIL) put("duration", DEFAULT_DURATION)
}) put("uploader", DEFAULT_UPLOADER_NAME)
insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("thumbnail_url", DEFAULT_THUMBNAIL)
// put("uid", null) }
put("service_id", DEFAULT_SECOND_SERVICE_ID) )
put("url", DEFAULT_SECOND_URL) insert(
// put("title", null) "streams", SQLiteDatabase.CONFLICT_FAIL,
// put("stream_type", null) ContentValues().apply {
// put("duration", null) // put("uid", null)
// put("uploader", null) put("service_id", DEFAULT_SECOND_SERVICE_ID)
// put("thumbnail_url", null) put("url", DEFAULT_SECOND_URL)
}) // put("title", null)
insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { // put("stream_type", null)
// put("uid", null) // put("duration", null)
put("service_id", DEFAULT_SERVICE_ID) // put("uploader", null)
// put("url", null) // put("thumbnail_url", null)
// put("title", null) }
// put("stream_type", null) )
// put("duration", null) insert(
// put("uploader", null) "streams", SQLiteDatabase.CONFLICT_FAIL,
// put("thumbnail_url", null) ContentValues().apply {
}) // put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
// put("url", null)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
}
)
close() close()
} }
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3, testHelper.runMigrationsAndValidate(
true, Migrations.MIGRATION_2_3) AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3
)
val migratedDatabaseV3 = getMigratedDatabase() val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst() val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
@ -110,9 +123,11 @@ class AppDatabaseTest {
} }
private fun getMigratedDatabase(): AppDatabase { private fun getMigratedDatabase(): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(ApplicationProvider.getApplicationContext(), val database: AppDatabase = Room.databaseBuilder(
AppDatabase::class.java, AppDatabase.DATABASE_NAME) ApplicationProvider.getApplicationContext(),
.build() AppDatabase::class.java, AppDatabase.DATABASE_NAME
)
.build()
testHelper.closeWhenFinished(database) testHelper.closeWhenFinished(database)
return database return database
} }

View File

@ -15,14 +15,22 @@ class DebugApp : App() {
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it // Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000) AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager LeakCanary.config = LeakCanary.config.copy(
.getDefaultSharedPreferences(this).getBoolean(getString( dumpHeap = PreferenceManager
R.string.allow_heap_dumping_key), false)) .getDefaultSharedPreferences(this).getBoolean(
getString(
R.string.allow_heap_dumping_key
),
false
)
)
} }
override fun getDownloader(): Downloader { override fun getDownloader(): Downloader {
val downloader = DownloaderImpl.init(OkHttpClient.Builder() val downloader = DownloaderImpl.init(
.addNetworkInterceptor(StethoInterceptor())) OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor())
)
setCookiesToDownloader(downloader) setCookiesToDownloader(downloader)
return downloader return downloader
} }
@ -36,7 +44,8 @@ class DebugApp : App() {
// Enable command line interface // Enable command line interface
initializerBuilder.enableDumpapp( initializerBuilder.enableDumpapp(
Stetho.defaultDumperPluginsProvider(applicationContext)) Stetho.defaultDumperPluginsProvider(applicationContext)
)
// Use the InitializerBuilder to generate an Initializer // Use the InitializerBuilder to generate an Initializer
val initializer = initializerBuilder.build() val initializer = initializerBuilder.build()
@ -47,6 +56,6 @@ class DebugApp : App() {
override fun isDisposedRxExceptionsReported(): Boolean { override fun isDisposedRxExceptionsReported(): Boolean {
return PreferenceManager.getDefaultSharedPreferences(this) return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false) .getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
} }
} }

View File

@ -38,13 +38,13 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
import io.reactivex.exceptions.OnErrorNotImplementedException; import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.exceptions.UndeliverableException; import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.functions.Consumer; import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.rxjava3.plugins.RxJavaPlugins;
/* /*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org> * Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>

View File

@ -35,10 +35,10 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import io.reactivex.Maybe; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public final class CheckForNewAppVersion { public final class CheckForNewAppVersion {
private CheckForNewAppVersion() { } private CheckForNewAppVersion() { }

View File

@ -66,13 +66,13 @@ import java.util.List;
import icepick.Icepick; import icepick.Icepick;
import icepick.State; import icepick.State;
import io.reactivex.Observable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.functions.Consumer; import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;

View File

@ -20,7 +20,7 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
/** /**
* Fragment containing the software licenses. * Fragment containing the software licenses.

View File

@ -16,11 +16,10 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import io.reactivex.Observable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.disposables.Disposables; import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@ -55,7 +54,7 @@ public final class LicenseFragmentHelper {
} }
/** /**
* @param context * @param context the Android context
* @return String which is a CSS stylesheet according to the context's theme * @return String which is a CSS stylesheet according to the context's theme
*/ */
private static String getLicenseStylesheet(@NonNull final Context context) { private static String getLicenseStylesheet(@NonNull final Context context) {
@ -86,7 +85,7 @@ public final class LicenseFragmentHelper {
static Disposable showLicense(@Nullable final Context context, @NonNull final License license) { static Disposable showLicense(@Nullable final Context context, @NonNull final License license) {
if (context == null) { if (context == null) {
return Disposables.empty(); return Disposable.empty();
} }
return Observable.fromCallable(() -> getFormattedLicense(context, license)) return Observable.fromCallable(() -> getFormattedLicense(context, license))

View File

@ -9,7 +9,7 @@ import androidx.room.Update;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
@Dao @Dao
public interface BasicDAO<Entity> { public interface BasicDAO<Entity> {

View File

@ -1,24 +1,20 @@
package org.schabi.newpipe.database.feed.dao package org.schabi.newpipe.database.feed.dao
import androidx.room.Dao import androidx.room.*
import androidx.room.Insert import io.reactivex.rxjava3.core.Flowable
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedEntity import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Dao @Dao
abstract class FeedDAO { abstract class FeedDAO {
@Query("DELETE FROM feed") @Query("DELETE FROM feed")
abstract fun deleteAll(): Int abstract fun deleteAll(): Int
@Query(""" @Query(
"""
SELECT s.* FROM streams s SELECT s.* FROM streams s
INNER JOIN feed f INNER JOIN feed f
@ -27,10 +23,12 @@ abstract class FeedDAO {
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
LIMIT 500 LIMIT 500
""") """
)
abstract fun getAllStreams(): Flowable<List<StreamEntity>> abstract fun getAllStreams(): Flowable<List<StreamEntity>>
@Query(""" @Query(
"""
SELECT s.* FROM streams s SELECT s.* FROM streams s
INNER JOIN feed f INNER JOIN feed f
@ -46,10 +44,12 @@ abstract class FeedDAO {
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
LIMIT 500 LIMIT 500
""") """
)
abstract fun getAllStreamsFromGroup(groupId: Long): Flowable<List<StreamEntity>> abstract fun getAllStreamsFromGroup(groupId: Long): Flowable<List<StreamEntity>>
@Query(""" @Query(
"""
DELETE FROM feed WHERE DELETE FROM feed WHERE
feed.stream_id IN ( feed.stream_id IN (
@ -60,10 +60,12 @@ abstract class FeedDAO {
WHERE s.upload_date < :offsetDateTime WHERE s.upload_date < :offsetDateTime
) )
""") """
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime) abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
@Query(""" @Query(
"""
DELETE FROM feed DELETE FROM feed
WHERE feed.subscription_id = :subscriptionId WHERE feed.subscription_id = :subscriptionId
@ -76,7 +78,8 @@ abstract class FeedDAO {
WHERE s.stream_type = "LIVE_STREAM" OR s.stream_type = "AUDIO_LIVE_STREAM" WHERE s.stream_type = "LIVE_STREAM" OR s.stream_type = "AUDIO_LIVE_STREAM"
) )
""") """
)
abstract fun unlinkOldLivestreams(subscriptionId: Long) abstract fun unlinkOldLivestreams(subscriptionId: Long)
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
@ -100,12 +103,14 @@ abstract class FeedDAO {
} }
} }
@Query(""" @Query(
"""
SELECT MIN(lu.last_updated) FROM feed_last_updated lu SELECT MIN(lu.last_updated) FROM feed_last_updated lu
INNER JOIN feed_group_subscription_join fgs INNER JOIN feed_group_subscription_join fgs
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
""") """
)
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
@Query("SELECT MIN(last_updated) FROM feed_last_updated") @Query("SELECT MIN(last_updated) FROM feed_last_updated")
@ -114,7 +119,8 @@ abstract class FeedDAO {
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL") @Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
abstract fun notLoadedCount(): Flowable<Long> abstract fun notLoadedCount(): Flowable<Long>
@Query(""" @Query(
"""
SELECT COUNT(*) FROM subscriptions s SELECT COUNT(*) FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs INNER JOIN feed_group_subscription_join fgs
@ -124,20 +130,24 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL WHERE lu.last_updated IS NULL
""") """
)
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long> abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
@Query(""" @Query(
"""
SELECT s.* FROM subscriptions s SELECT s.* FROM subscriptions s
LEFT JOIN feed_last_updated lu LEFT JOIN feed_last_updated lu
ON s.uid = lu.subscription_id ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""") """
)
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>> abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
@Query(""" @Query(
"""
SELECT s.* FROM subscriptions s SELECT s.* FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs INNER JOIN feed_group_subscription_join fgs
@ -147,6 +157,7 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""") """
)
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>> abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
} }

View File

@ -6,8 +6,8 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import io.reactivex.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.Maybe import io.reactivex.rxjava3.core.Maybe
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity

View File

@ -10,21 +10,24 @@ import org.schabi.newpipe.database.feed.model.FeedEntity.Companion.SUBSCRIPTION_
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity
@Entity(tableName = FEED_TABLE, @Entity(
primaryKeys = [STREAM_ID, SUBSCRIPTION_ID], tableName = FEED_TABLE,
indices = [Index(SUBSCRIPTION_ID)], primaryKeys = [STREAM_ID, SUBSCRIPTION_ID],
foreignKeys = [ indices = [Index(SUBSCRIPTION_ID)],
ForeignKey( foreignKeys = [
entity = StreamEntity::class, ForeignKey(
parentColumns = [StreamEntity.STREAM_ID], entity = StreamEntity::class,
childColumns = [STREAM_ID], parentColumns = [StreamEntity.STREAM_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true), childColumns = [STREAM_ID],
ForeignKey( onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
entity = SubscriptionEntity::class, ),
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID], ForeignKey(
childColumns = [SUBSCRIPTION_ID], entity = SubscriptionEntity::class,
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true) parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
] childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
)
]
) )
data class FeedEntity( data class FeedEntity(
@ColumnInfo(name = STREAM_ID) @ColumnInfo(name = STREAM_ID)

View File

@ -9,8 +9,8 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.SORT_ORD
import org.schabi.newpipe.local.subscription.FeedGroupIcon import org.schabi.newpipe.local.subscription.FeedGroupIcon
@Entity( @Entity(
tableName = FEED_GROUP_TABLE, tableName = FEED_GROUP_TABLE,
indices = [Index(SORT_ORDER)] indices = [Index(SORT_ORDER)]
) )
data class FeedGroupEntity( data class FeedGroupEntity(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -11,22 +11,24 @@ import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity.Compan
import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity
@Entity( @Entity(
tableName = FEED_GROUP_SUBSCRIPTION_TABLE, tableName = FEED_GROUP_SUBSCRIPTION_TABLE,
primaryKeys = [GROUP_ID, SUBSCRIPTION_ID], primaryKeys = [GROUP_ID, SUBSCRIPTION_ID],
indices = [Index(SUBSCRIPTION_ID)], indices = [Index(SUBSCRIPTION_ID)],
foreignKeys = [ foreignKeys = [
ForeignKey( ForeignKey(
entity = FeedGroupEntity::class, entity = FeedGroupEntity::class,
parentColumns = [FeedGroupEntity.ID], parentColumns = [FeedGroupEntity.ID],
childColumns = [GROUP_ID], childColumns = [GROUP_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true), onDelete = CASCADE, onUpdate = CASCADE, deferred = true
),
ForeignKey( ForeignKey(
entity = SubscriptionEntity::class, entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID], parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID], childColumns = [SUBSCRIPTION_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true) onDelete = CASCADE, onUpdate = CASCADE, deferred = true
] )
]
) )
data class FeedGroupSubscriptionEntity( data class FeedGroupSubscriptionEntity(
@ColumnInfo(name = GROUP_ID) @ColumnInfo(name = GROUP_ID)

View File

@ -4,20 +4,21 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Entity( @Entity(
tableName = FEED_LAST_UPDATED_TABLE, tableName = FEED_LAST_UPDATED_TABLE,
foreignKeys = [ foreignKeys = [
ForeignKey( ForeignKey(
entity = SubscriptionEntity::class, entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID], parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID], childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true) onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
] )
]
) )
data class FeedLastUpdatedEntity( data class FeedLastUpdatedEntity(
@PrimaryKey @PrimaryKey

View File

@ -8,7 +8,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE; import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID; import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID;

View File

@ -10,7 +10,7 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;

View File

@ -2,8 +2,8 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Embedded import androidx.room.Embedded
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import java.time.OffsetDateTime
data class StreamHistoryEntry( data class StreamHistoryEntry(
@Embedded @Embedded
@ -25,6 +25,6 @@ data class StreamHistoryEntry(
fun hasEqualValues(other: StreamHistoryEntry): Boolean { fun hasEqualValues(other: StreamHistoryEntry): Boolean {
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId && return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.isEqual(other.accessDate) accessDate.isEqual(other.accessDate)
} }
} }

View File

@ -8,7 +8,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;

View File

@ -9,7 +9,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID; import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;

View File

@ -11,7 +11,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT; import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;

View File

@ -1,19 +1,14 @@
package org.schabi.newpipe.database.stream.dao package org.schabi.newpipe.database.stream.dao
import androidx.room.ColumnInfo import androidx.room.*
import androidx.room.Dao import io.reactivex.rxjava3.core.Flowable
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import java.time.OffsetDateTime
import org.schabi.newpipe.database.BasicDAO import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import java.time.OffsetDateTime
@Dao @Dao
abstract class StreamDAO : BasicDAO<StreamEntity> { abstract class StreamDAO : BasicDAO<StreamEntity> {
@ -35,10 +30,12 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
internal abstract fun silentInsertAllInternal(streams: List<StreamEntity>): List<Long> internal abstract fun silentInsertAllInternal(streams: List<StreamEntity>): List<Long>
@Query(""" @Query(
"""
SELECT uid, stream_type, textual_upload_date, upload_date, is_upload_date_approximation, duration SELECT uid, stream_type, textual_upload_date, upload_date, is_upload_date_approximation, duration
FROM streams WHERE url = :url AND service_id = :serviceId FROM streams WHERE url = :url AND service_id = :serviceId
""") """
)
internal abstract fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed? internal abstract fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed?
@Transaction @Transaction
@ -79,7 +76,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
private fun compareAndUpdateStream(newerStream: StreamEntity) { private fun compareAndUpdateStream(newerStream: StreamEntity) {
val existentMinimalStream = getMinimalStreamForCompare(newerStream.serviceId, newerStream.url) val existentMinimalStream = getMinimalStreamForCompare(newerStream.serviceId, newerStream.url)
?: throw IllegalStateException("Stream cannot be null just after insertion.") ?: throw IllegalStateException("Stream cannot be null just after insertion.")
newerStream.uid = existentMinimalStream.uid newerStream.uid = existentMinimalStream.uid
val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM
@ -88,7 +85,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
// Use the existent upload date if the newer stream does not have a better precision // Use the existent upload date if the newer stream does not have a better precision
// (i.e. is an approximation). This is done to prevent unnecessary changes. // (i.e. is an approximation). This is done to prevent unnecessary changes.
val hasBetterPrecision = val hasBetterPrecision =
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) { if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) {
newerStream.uploadDate = existentMinimalStream.uploadDate newerStream.uploadDate = existentMinimalStream.uploadDate
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
@ -101,7 +98,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
} }
} }
@Query(""" @Query(
"""
DELETE FROM streams WHERE DELETE FROM streams WHERE
NOT EXISTS (SELECT 1 FROM stream_history sh NOT EXISTS (SELECT 1 FROM stream_history sh
@ -112,7 +110,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
AND NOT EXISTS (SELECT 1 FROM feed f AND NOT EXISTS (SELECT 1 FROM feed f
WHERE f.stream_id = streams.uid) WHERE f.stream_id = streams.uid)
""") """
)
abstract fun deleteOrphans(): Int abstract fun deleteOrphans(): Int
/** /**

View File

@ -11,7 +11,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID; import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;

View File

@ -1,12 +1,6 @@
package org.schabi.newpipe.database.stream.model package org.schabi.newpipe.database.stream.model
import androidx.room.ColumnInfo import androidx.room.*
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
@ -15,11 +9,14 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem import org.schabi.newpipe.player.playqueue.PlayQueueItem
import java.io.Serializable
import java.time.OffsetDateTime
@Entity(tableName = STREAM_TABLE, @Entity(
indices = [ tableName = STREAM_TABLE,
Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true) indices = [
] Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
]
) )
data class StreamEntity( data class StreamEntity(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -61,27 +58,27 @@ data class StreamEntity(
) : Serializable { ) : Serializable {
@Ignore @Ignore
constructor(item: StreamInfoItem) : this( constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name, serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName, streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount, thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(), textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation isUploadDateApproximation = item.uploadDate?.isApproximation
) )
@Ignore @Ignore
constructor(info: StreamInfo) : this( constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name, serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName, streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount, thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(), textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation isUploadDateApproximation = info.uploadDate?.isApproximation
) )
@Ignore @Ignore
constructor(item: PlayQueueItem) : this( constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title, serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader, streamType = item.streamType, duration = item.duration, uploader = item.uploader,
thumbnailUrl = item.thumbnailUrl thumbnailUrl = item.thumbnailUrl
) )
fun toStreamInfoItem(): StreamInfoItem { fun toStreamInfoItem(): StreamInfoItem {

View File

@ -5,8 +5,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import io.reactivex.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.Maybe import io.reactivex.rxjava3.core.Maybe
import org.schabi.newpipe.database.BasicDAO import org.schabi.newpipe.database.BasicDAO
@Dao @Dao
@ -20,16 +20,19 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC") @Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
abstract override fun getAll(): Flowable<List<SubscriptionEntity>> abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
@Query(""" @Query(
"""
SELECT * FROM subscriptions SELECT * FROM subscriptions
WHERE name LIKE '%' || :filter || '%' WHERE name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC ORDER BY name COLLATE NOCASE ASC
""") """
)
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>> abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
@Query(""" @Query(
"""
SELECT * FROM subscriptions s SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs LEFT JOIN feed_group_subscription_join fgs
@ -38,12 +41,14 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId) WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
ORDER BY name COLLATE NOCASE ASC ORDER BY name COLLATE NOCASE ASC
""") """
)
abstract fun getSubscriptionsOnlyUngrouped( abstract fun getSubscriptionsOnlyUngrouped(
currentGroupId: Long currentGroupId: Long
): Flowable<List<SubscriptionEntity>> ): Flowable<List<SubscriptionEntity>>
@Query(""" @Query(
"""
SELECT * FROM subscriptions s SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs LEFT JOIN feed_group_subscription_join fgs
@ -53,7 +58,8 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
AND s.name LIKE '%' || :filter || '%' AND s.name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC ORDER BY name COLLATE NOCASE ASC
""") """
)
abstract fun getSubscriptionsOnlyUngroupedFiltered( abstract fun getSubscriptionsOnlyUngroupedFiltered(
currentGroupId: Long, currentGroupId: Long,
filter: String filter: String

View File

@ -10,7 +10,6 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.IBinder; import android.os.IBinder;
import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -34,6 +33,7 @@ import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.nononsenseapps.filepicker.Utils; import com.nononsenseapps.filepicker.Utils;
@ -70,7 +70,7 @@ import java.util.Locale;
import icepick.Icepick; import icepick.Icepick;
import icepick.State; import icepick.State;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredDirectoryHelper; import us.shandian.giga.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.io.StoredFileHelper;

View File

@ -13,7 +13,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
@ -34,8 +34,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State; import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;

View File

@ -15,9 +15,6 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.ViewTreeObserver;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.util.Linkify; import android.text.util.Linkify;
@ -27,6 +24,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -45,7 +43,9 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
@ -116,11 +116,11 @@ import java.util.concurrent.TimeUnit;
import icepick.State; import icepick.State;
import io.noties.markwon.Markwon; import io.noties.markwon.Markwon;
import io.noties.markwon.linkify.LinkifyPlugin; import io.noties.markwon.linkify.LinkifyPlugin;
import io.reactivex.Single; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;

View File

@ -16,10 +16,10 @@ import org.schabi.newpipe.views.NewPipeRecyclerView;
import java.util.Queue; import java.util.Queue;
import icepick.State; import icepick.State;
import io.reactivex.Single; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public abstract class BaseListInfoFragment<I extends ListInfo> public abstract class BaseListInfoFragment<I extends ListInfo>
extends BaseListFragment<I, ListExtractor.InfoItemsPage> { extends BaseListFragment<I, ListExtractor.InfoItemsPage> {

View File

@ -24,7 +24,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.database.subscription.SubscriptionEntity;
@ -53,15 +53,15 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.reactivex.Observable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.functions.Action; import io.reactivex.rxjava3.functions.Action;
import io.reactivex.functions.Consumer; import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.functions.Function; import io.reactivex.rxjava3.functions.Function;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor; import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor; import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;

View File

@ -20,8 +20,8 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> { public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private final CompositeDisposable disposables = new CompositeDisposable(); private final CompositeDisposable disposables = new CompositeDisposable();

View File

@ -26,7 +26,7 @@ import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import icepick.State; import icepick.State;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;

View File

@ -51,12 +51,11 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import io.reactivex.Flowable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
@ -460,7 +459,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
.doFinally(() -> playlistEntity = null) .doFinally(() -> playlistEntity = null)
.subscribe(ignored -> { /* Do nothing */ }, this::onError); .subscribe(ignored -> { /* Do nothing */ }, this::onError);
} else { } else {
action = Disposables.empty(); action = Disposable.empty();
} }
disposables.add(action); disposables.add(action);

View File

@ -68,13 +68,13 @@ import java.util.Queue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import icepick.State; import icepick.State;
import io.reactivex.Flowable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.Observable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject; import io.reactivex.rxjava3.subjects.PublishSubject;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags; import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@ -709,7 +709,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
final Observable<String> observable = suggestionPublisher final Observable<String> observable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWith(searchString != null .startWithItem(searchString != null
? searchString ? searchString
: "") : "")
.filter(ss -> isSuggestionsEnabled); .filter(ss -> isSuggestionsEnabled);

View File

@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list.videos;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -13,6 +12,7 @@ import android.widget.Switch;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
@ -25,8 +25,8 @@ import org.schabi.newpipe.util.RelatedStreamInfo;
import java.io.Serializable; import java.io.Serializable;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {

View File

@ -33,11 +33,11 @@ import org.schabi.newpipe.util.OnClickGesture;
import java.util.List; import java.util.List;
import icepick.State; import icepick.State;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> { public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
@State @State

View File

@ -28,9 +28,9 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
public final class PlaylistAppendDialog extends PlaylistDialog { public final class PlaylistAppendDialog extends PlaylistDialog {
private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); private static final String TAG = PlaylistAppendDialog.class.getCanonicalName();
@ -98,7 +98,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
final LocalPlaylistManager playlistManager = final LocalPlaylistManager playlistManager =
new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
playlistAdapter = new LocalItemListAdapter(getActivity()); playlistAdapter = new LocalItemListAdapter(getActivity());
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() { playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@ -113,7 +113,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
}); });
playlistRecyclerView = view.findViewById(R.id.playlist_list); playlistRecyclerView = view.findViewById(R.id.playlist_list);
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
playlistRecyclerView.setAdapter(playlistAdapter); playlistRecyclerView.setAdapter(playlistAdapter);
final View newPlaylistButton = view.findViewById(R.id.newPlaylist); final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
@ -146,12 +146,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void openCreatePlaylistDialog() { public void openCreatePlaylistDialog() {
if (getStreams() == null || getFragmentManager() == null) { if (getStreams() == null || !isAdded()) {
return; return;
} }
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG); PlaylistCreationDialog.newInstance(getStreams()).show(getParentFragmentManager(), TAG);
getDialog().dismiss(); requireDialog().dismiss();
} }
private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) { private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) {
@ -183,6 +183,6 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> successToast.show())); .subscribe(ignored -> successToast.show()));
getDialog().dismiss(); requireDialog().dismiss();
} }
} }

View File

@ -17,7 +17,7 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import java.util.List; import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
public final class PlaylistCreationDialog extends PlaylistDialog { public final class PlaylistCreationDialog extends PlaylistDialog {
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) { public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {

View File

@ -2,11 +2,11 @@ package org.schabi.newpipe.local.feed
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import io.reactivex.Completable import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.Flowable import io.reactivex.rxjava3.core.Completable
import io.reactivex.Maybe import io.reactivex.rxjava3.core.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Maybe
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import java.time.LocalDate import java.time.LocalDate
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
@ -44,9 +44,9 @@ class FeedDatabaseManager(context: Context) {
else -> feedTable.getAllStreamsFromGroup(groupId) else -> feedTable.getAllStreamsFromGroup(groupId)
} }
return streams.map<List<StreamInfoItem>> { return streams.map {
val items = ArrayList<StreamInfoItem>(it.size) val items = ArrayList<StreamInfoItem>(it.size)
it.mapTo(items) { it.toStreamInfoItem() } it.mapTo(items) { stream -> stream.toStreamInfoItem() }
return@map items return@map items
} }
} }
@ -61,10 +61,10 @@ class FeedDatabaseManager(context: Context) {
} }
fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: OffsetDateTime) = fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: OffsetDateTime) =
feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold) feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
fun markAsOutdated(subscriptionId: Long) = feedTable fun markAsOutdated(subscriptionId: Long) = feedTable
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null)) .setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
fun upsertAll( fun upsertAll(
subscriptionId: Long, subscriptionId: Long,
@ -113,38 +113,38 @@ class FeedDatabaseManager(context: Context) {
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> { fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
return feedGroupTable.getSubscriptionIdsFor(groupId) return feedGroupTable.getSubscriptionIdsFor(groupId)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun updateSubscriptionsForGroup(groupId: Long, subscriptionIds: List<Long>): Completable { fun updateSubscriptionsForGroup(groupId: Long, subscriptionIds: List<Long>): Completable {
return Completable.fromCallable { feedGroupTable.updateSubscriptionsForGroup(groupId, subscriptionIds) } return Completable.fromCallable { feedGroupTable.updateSubscriptionsForGroup(groupId, subscriptionIds) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun createGroup(name: String, icon: FeedGroupIcon): Maybe<Long> { fun createGroup(name: String, icon: FeedGroupIcon): Maybe<Long> {
return Maybe.fromCallable { feedGroupTable.insert(FeedGroupEntity(0, name, icon)) } return Maybe.fromCallable { feedGroupTable.insert(FeedGroupEntity(0, name, icon)) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun getGroup(groupId: Long): Maybe<FeedGroupEntity> { fun getGroup(groupId: Long): Maybe<FeedGroupEntity> {
return feedGroupTable.getGroup(groupId) return feedGroupTable.getGroup(groupId)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun updateGroup(feedGroupEntity: FeedGroupEntity): Completable { fun updateGroup(feedGroupEntity: FeedGroupEntity): Completable {
return Completable.fromCallable { feedGroupTable.update(feedGroupEntity) } return Completable.fromCallable { feedGroupTable.update(feedGroupEntity) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun deleteGroup(groupId: Long): Completable { fun deleteGroup(groupId: Long): Completable {
return Completable.fromCallable { feedGroupTable.delete(groupId) } return Completable.fromCallable { feedGroupTable.delete(groupId) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun updateGroupsOrder(groupIdList: List<Long>): Completable { fun updateGroupsOrder(groupIdList: List<Long>): Completable {
@ -152,8 +152,8 @@ class FeedDatabaseManager(context: Context) {
val orderMap = groupIdList.associateBy({ it }, { index++ }) val orderMap = groupIdList.associateBy({ it }, { index++ })
return Completable.fromCallable { feedGroupTable.updateOrder(orderMap) } return Completable.fromCallable { feedGroupTable.updateOrder(orderMap) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> { fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> {

View File

@ -37,7 +37,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import icepick.State import icepick.State
import java.util.Calendar
import kotlinx.android.synthetic.main.error_retry.error_button_retry import kotlinx.android.synthetic.main.error_retry.error_button_retry
import kotlinx.android.synthetic.main.error_retry.error_message_view import kotlinx.android.synthetic.main.error_retry.error_message_view
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
@ -55,6 +54,7 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.AnimationUtils.animateView import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
import java.util.Calendar
class FeedFragment : BaseListFragment<FeedState, Unit>() { class FeedFragment : BaseListFragment<FeedState, Unit>() {
private lateinit var viewModel: FeedViewModel private lateinit var viewModel: FeedViewModel
@ -76,7 +76,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
?: FeedGroupEntity.GROUP_ALL_ID ?: FeedGroupEntity.GROUP_ALL_ID
groupName = arguments?.getString(KEY_GROUP_NAME) ?: "" groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
} }
@ -144,15 +144,15 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
} }
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setMessage(R.string.feed_use_dedicated_fetch_method_help_text) .setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
.setNeutralButton(enableDisableButtonText) { _, _ -> .setNeutralButton(enableDisableButtonText) { _, _ ->
sharedPreferences.edit { sharedPreferences.edit {
putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod) putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
}
} }
.setPositiveButton(resources.getString(R.string.finish), null) }
.create() .setPositiveButton(resources.getString(R.string.finish), null)
.show() .create()
.show()
return true return true
} }
@ -234,7 +234,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
showLoading() showLoading()
val isIndeterminate = progressState.currentProgress == -1 && val isIndeterminate = progressState.currentProgress == -1 &&
progressState.maxProgress == -1 progressState.maxProgress == -1
if (!isIndeterminate) { if (!isIndeterminate) {
loading_progress_text.text = "${progressState.currentProgress}/${progressState.maxProgress}" loading_progress_text.text = "${progressState.currentProgress}/${progressState.maxProgress}"
@ -245,7 +245,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
} }
loading_progress_bar.isIndeterminate = isIndeterminate || loading_progress_bar.isIndeterminate = isIndeterminate ||
(progressState.maxProgress > 0 && progressState.currentProgress == 0) (progressState.maxProgress > 0 && progressState.currentProgress == 0)
loading_progress_bar.progress = progressState.currentProgress loading_progress_bar.progress = progressState.currentProgress
loading_progress_bar.max = progressState.maxProgress loading_progress_bar.max = progressState.maxProgress
@ -267,8 +267,10 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
} }
if (loadedState.itemsErrors.isNotEmpty()) { if (loadedState.itemsErrors.isNotEmpty()) {
showSnackBarError(loadedState.itemsErrors, UserAction.REQUESTED_FEED, showSnackBarError(
"none", "Loading feed", R.string.general_error) loadedState.itemsErrors, UserAction.REQUESTED_FEED,
"none", "Loading feed", R.string.general_error
)
} }
if (loadedState.items.isEmpty()) { if (loadedState.items.isEmpty()) {
@ -311,9 +313,11 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
override fun hasMoreItems() = false override fun hasMoreItems() = false
private fun triggerUpdate() { private fun triggerUpdate() {
getActivity()?.startService(Intent(requireContext(), FeedLoadService::class.java).apply { getActivity()?.startService(
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId) Intent(requireContext(), FeedLoadService::class.java).apply {
}) putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
}
)
listState = null listState = null
} }

View File

@ -1,8 +1,8 @@
package org.schabi.newpipe.local.feed package org.schabi.newpipe.local.feed
import androidx.annotation.StringRes import androidx.annotation.StringRes
import java.util.Calendar
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import java.util.Calendar
sealed class FeedState { sealed class FeedState {
data class ProgressState( data class ProgressState(

View File

@ -5,21 +5,18 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.reactivex.Flowable import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable
import io.reactivex.functions.Function4 import io.reactivex.rxjava3.functions.Function4
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.ktx.toCalendar import org.schabi.newpipe.ktx.toCalendar
import org.schabi.newpipe.local.feed.service.FeedEventManager import org.schabi.newpipe.local.feed.service.FeedEventManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() { class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory { class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
@ -35,34 +32,34 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
val stateLiveData: LiveData<FeedState> = mutableStateLiveData val stateLiveData: LiveData<FeedState> = mutableStateLiveData
private var combineDisposable = Flowable private var combineDisposable = Flowable
.combineLatest( .combineLatest(
FeedEventManager.events(), FeedEventManager.events(),
feedDatabaseManager.asStreamItems(groupId), feedDatabaseManager.asStreamItems(groupId),
feedDatabaseManager.notLoadedCount(groupId), feedDatabaseManager.notLoadedCount(groupId),
feedDatabaseManager.oldestSubscriptionUpdate(groupId), feedDatabaseManager.oldestSubscriptionUpdate(groupId),
Function4 { t1: FeedEventManager.Event, t2: List<StreamInfoItem>, t3: Long, t4: List<OffsetDateTime> ->
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
}
)
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
val oldestUpdateCalendar = oldestUpdate?.toCalendar()
Function4 { t1: FeedEventManager.Event, t2: List<StreamInfoItem>, t3: Long, t4: List<OffsetDateTime> -> mutableStateLiveData.postValue(
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull()) when (event) {
}
)
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
val oldestUpdateCalendar = oldestUpdate?.toCalendar()
mutableStateLiveData.postValue(when (event) {
is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount) is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount)
is ProgressEvent -> FeedState.ProgressState(event.currentProgress, event.maxProgress, event.progressMessage) is ProgressEvent -> FeedState.ProgressState(event.currentProgress, event.maxProgress, event.progressMessage)
is SuccessResultEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount, event.itemsErrors) is SuccessResultEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount, event.itemsErrors)
is ErrorResultEvent -> FeedState.ErrorState(event.error) is ErrorResultEvent -> FeedState.ErrorState(event.error)
})
if (event is ErrorResultEvent || event is SuccessResultEvent) {
FeedEventManager.reset()
} }
)
if (event is ErrorResultEvent || event is SuccessResultEvent) {
FeedEventManager.reset()
} }
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()

View File

@ -1,15 +1,15 @@
package org.schabi.newpipe.local.feed.service package org.schabi.newpipe.local.feed.service
import androidx.annotation.StringRes import androidx.annotation.StringRes
import io.reactivex.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.processors.BehaviorProcessor import io.reactivex.rxjava3.processors.BehaviorProcessor
import java.util.concurrent.atomic.AtomicBoolean
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import java.util.concurrent.atomic.AtomicBoolean
object FeedEventManager { object FeedEventManager {
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create() private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
private var ignoreUpstream = AtomicBoolean() private var ignoreUpstream = AtomicBoolean()
private var eventsFlowable = processor.startWith(IdleEvent) private var eventsFlowable = processor.startWithItem(IdleEvent)
fun postEvent(event: Event) { fun postEvent(event: Event) {
processor.onNext(event) processor.onNext(event)

View File

@ -31,21 +31,15 @@ import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import io.reactivex.Flowable import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.Notification import io.reactivex.rxjava3.core.Flowable
import io.reactivex.Single import io.reactivex.rxjava3.core.Notification
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single
import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.functions.Consumer import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.functions.Function import io.reactivex.rxjava3.functions.Function
import io.reactivex.processors.PublishProcessor import io.reactivex.rxjava3.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import java.io.IOException
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import org.reactivestreams.Subscriber import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription import org.reactivestreams.Subscription
import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.MainActivity.DEBUG
@ -55,14 +49,17 @@ import org.schabi.newpipe.extractor.ListInfo
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExceptionUtils import org.schabi.newpipe.util.ExceptionUtils
import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ExtractorHelper
import java.io.IOException
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
class FeedLoadService : Service() { class FeedLoadService : Service() {
companion object { companion object {
@ -109,8 +106,11 @@ class FeedLoadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "]," + Log.d(
" flags = [" + flags + "], startId = [" + startId + "]") TAG,
"onStartCommand() called with: intent = [" + intent + "]," +
" flags = [" + flags + "], startId = [" + startId + "]"
)
} }
if (intent == null || loadingSubscription != null) { if (intent == null || loadingSubscription != null) {
@ -123,10 +123,10 @@ class FeedLoadService : Service() {
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
val useFeedExtractor = defaultSharedPreferences val useFeedExtractor = defaultSharedPreferences
.getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false) .getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
val thresholdOutdatedSecondsString = defaultSharedPreferences val thresholdOutdatedSecondsString = defaultSharedPreferences
.getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value)) .getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value))
val thresholdOutdatedSeconds = thresholdOutdatedSecondsString!!.toInt() val thresholdOutdatedSeconds = thresholdOutdatedSecondsString!!.toInt()
startLoading(groupId, useFeedExtractor, thresholdOutdatedSeconds) startLoading(groupId, useFeedExtractor, thresholdOutdatedSeconds)
@ -181,63 +181,63 @@ class FeedLoadService : Service() {
} }
subscriptions subscriptions
.limit(1) .take(1)
.doOnNext { .doOnNext {
currentProgress.set(0) currentProgress.set(0)
maxProgress.set(it.size) maxProgress.set(it.size)
}
.filter { it.isNotEmpty() }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
startForeground(NOTIFICATION_ID, notificationBuilder.build())
updateNotificationProgress(null)
broadcastProgress()
}
.observeOn(Schedulers.io())
.flatMap { Flowable.fromIterable(it) }
.takeWhile { !cancelSignal.get() }
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
.filter { !cancelSignal.get() }
.map { subscriptionEntity ->
try {
val listInfo = if (useFeedExtractor) {
ExtractorHelper
.getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
.blockingGet()
} else {
ExtractorHelper
.getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
.blockingGet()
} as ListInfo<StreamInfoItem>
return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
} catch (e: Throwable) {
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
val wrapper = RequestException(subscriptionEntity.uid, request, e)
return@map Notification.createOnError<Pair<Long, ListInfo<StreamInfoItem>>>(wrapper)
} }
.filter { it.isNotEmpty() } }
.sequential()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { .doOnNext(errorHandlingConsumer)
startForeground(NOTIFICATION_ID, notificationBuilder.build())
updateNotificationProgress(null)
broadcastProgress()
}
.observeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
.flatMap { Flowable.fromIterable(it) } .doOnNext(notificationsConsumer)
.takeWhile { !cancelSignal.get() }
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2) .observeOn(Schedulers.io())
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2) .buffer(BUFFER_COUNT_BEFORE_INSERT)
.filter { !cancelSignal.get() } .doOnNext(databaseConsumer)
.map { subscriptionEntity -> .subscribeOn(Schedulers.io())
try { .observeOn(AndroidSchedulers.mainThread())
val listInfo = if (useFeedExtractor) { .subscribe(resultSubscriber)
ExtractorHelper
.getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
.blockingGet()
} else {
ExtractorHelper
.getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
.blockingGet()
} as ListInfo<StreamInfoItem>
return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
} catch (e: Throwable) {
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
val wrapper = RequestException(subscriptionEntity.uid, request, e)
return@map Notification.createOnError<Pair<Long, ListInfo<StreamInfoItem>>>(wrapper)
}
}
.sequential()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(errorHandlingConsumer)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(notificationsConsumer)
.observeOn(Schedulers.io())
.buffer(BUFFER_COUNT_BEFORE_INSERT)
.doOnNext(databaseConsumer)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(resultSubscriber)
} }
private fun broadcastProgress() { private fun broadcastProgress() {
@ -274,7 +274,8 @@ class FeedLoadService : Service() {
notificationUpdater.onNext(getString(R.string.feed_processing_message)) notificationUpdater.onNext(getString(R.string.feed_processing_message))
postEvent(ProgressEvent(R.string.feed_processing_message)) postEvent(ProgressEvent(R.string.feed_processing_message))
disposables.add(Single disposables.add(
Single
.fromCallable { .fromCallable {
feedResultsHolder.ready() feedResultsHolder.ready()
@ -293,7 +294,8 @@ class FeedLoadService : Service() {
return@subscribe return@subscribe
} }
stopService() stopService()
}) }
)
} }
} }
@ -364,16 +366,18 @@ class FeedLoadService : Service() {
private var maxProgress = AtomicInteger(-1) private var maxProgress = AtomicInteger(-1)
private fun createNotification(): NotificationCompat.Builder { private fun createNotification(): NotificationCompat.Builder {
val cancelActionIntent = PendingIntent.getBroadcast(this, val cancelActionIntent = PendingIntent.getBroadcast(
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0) this,
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0
)
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true) .setOngoing(true)
.setProgress(-1, -1, true) .setProgress(-1, -1, true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.addAction(0, getString(R.string.cancel), cancelActionIntent) .addAction(0, getString(R.string.cancel), cancelActionIntent)
.setContentTitle(getString(R.string.feed_notification_loading)) .setContentTitle(getString(R.string.feed_notification_loading))
} }
private fun setupNotification() { private fun setupNotification() {
@ -381,13 +385,15 @@ class FeedLoadService : Service() {
notificationBuilder = createNotification() notificationBuilder = createNotification()
val throttleAfterFirstEmission = Function { flow: Flowable<String> -> val throttleAfterFirstEmission = Function { flow: Flowable<String> ->
flow.limit(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS)) flow.take(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS))
} }
disposables.add(notificationUpdater disposables.add(
notificationUpdater
.publish(throttleAfterFirstEmission) .publish(throttleAfterFirstEmission)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNotificationProgress)) .subscribe(this::updateNotificationProgress)
)
} }
private fun updateNotificationProgress(updateDescription: String?) { private fun updateNotificationProgress(updateDescription: String?) {

View File

@ -50,11 +50,11 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import io.reactivex.Completable; import io.reactivex.rxjava3.core.Completable;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.Maybe; import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class HistoryRecordManager { public class HistoryRecordManager {
private final AppDatabase database; private final AppDatabase database;

View File

@ -49,9 +49,9 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import icepick.State; import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
public class StatisticsPlaylistFragment public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> { extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {

View File

@ -55,13 +55,12 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State; import icepick.State;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.disposables.Disposables; import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.subjects.PublishSubject;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
@ -641,7 +640,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private Disposable getDebouncedSaver() { private Disposable getDebouncedSaver() {
if (debouncedSaveSignal == null) { if (debouncedSaveSignal == null) {
return Disposables.empty(); return Disposable.empty();
} }
return debouncedSaveSignal return debouncedSaveSignal

View File

@ -15,11 +15,11 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.reactivex.Completable; import io.reactivex.rxjava3.core.Completable;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.Maybe; import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class LocalPlaylistManager { public class LocalPlaylistManager {
private final AppDatabase database; private final AppDatabase database;

View File

@ -7,9 +7,9 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class RemotePlaylistManager { public class RemotePlaylistManager {

View File

@ -28,13 +28,7 @@ import com.xwray.groupie.Item
import com.xwray.groupie.Section import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.State import icepick.State
import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.floor
import kotlin.math.max
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
import kotlinx.android.synthetic.main.fragment_subscription.items_list import kotlinx.android.synthetic.main.fragment_subscription.items_list
@ -68,6 +62,12 @@ import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.floor
import kotlin.math.max
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() { class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var viewModel: SubscriptionViewModel private lateinit var viewModel: SubscriptionViewModel
@ -208,14 +208,19 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
if (!exportFile.parentFile.canWrite() || !exportFile.parentFile.canRead()) { if (!exportFile.parentFile.canWrite() || !exportFile.parentFile.canRead()) {
Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show()
} else { } else {
activity.startService(Intent(activity, SubscriptionsExportService::class.java) activity.startService(
.putExtra(KEY_FILE_PATH, exportFile.absolutePath)) Intent(activity, SubscriptionsExportService::class.java)
.putExtra(KEY_FILE_PATH, exportFile.absolutePath)
)
} }
} else if (requestCode == REQUEST_IMPORT_CODE) { } else if (requestCode == REQUEST_IMPORT_CODE) {
val path = Utils.getFileForUri(data.data!!).absolutePath val path = Utils.getFileForUri(data.data!!).absolutePath
ImportConfirmationDialog.show(this, Intent(activity, SubscriptionsImportService::class.java) ImportConfirmationDialog.show(
this,
Intent(activity, SubscriptionsImportService::class.java)
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE) .putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
.putExtra(KEY_VALUE, path)) .putExtra(KEY_VALUE, path)
)
} }
} }
} }
@ -247,9 +252,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter) feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
feedGroupsSortMenuItem = HeaderWithMenuItem( feedGroupsSortMenuItem = HeaderWithMenuItem(
getString(R.string.feed_groups_header_title), getString(R.string.feed_groups_header_title),
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort), ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
menuItemOnClickListener = ::openReorderDialog menuItemOnClickListener = ::openReorderDialog
) )
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))
@ -260,10 +265,11 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.setHideWhenEmpty(true) subscriptionsSection.setHideWhenEmpty(true)
importExportItem = FeedImportExportItem( importExportItem = FeedImportExportItem(
{ onImportPreviousSelected() }, { onImportPreviousSelected() },
{ onImportFromServiceSelected(it) }, { onImportFromServiceSelected(it) },
{ onExportSelected() }, { onExportSelected() },
importExportItemExpandedState ?: false) importExportItemExpandedState ?: false
)
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection))) groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
} }
@ -284,8 +290,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private fun showLongTapDialog(selectedItem: ChannelInfoItem) { private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
val commands = arrayOf( val commands = arrayOf(
getString(R.string.share), getString(R.string.share),
getString(R.string.unsubscribe) getString(R.string.unsubscribe)
) )
val actions = DialogInterface.OnClickListener { _, i -> val actions = DialogInterface.OnClickListener { _, i ->
@ -301,16 +307,18 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
bannerView.itemAdditionalDetails.visibility = View.GONE bannerView.itemAdditionalDetails.visibility = View.GONE
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setCustomTitle(bannerView) .setCustomTitle(bannerView)
.setItems(commands, actions) .setItems(commands, actions)
.create() .create()
.show() .show()
} }
private fun deleteChannel(selectedItem: ChannelInfoItem) { private fun deleteChannel(selectedItem: ChannelInfoItem) {
disposables.add(subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe { disposables.add(
Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show() subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
}) Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
}
)
} }
override fun doInitialLoadLogic() = Unit override fun doInitialLoadLogic() = Unit
@ -332,8 +340,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
} }
private val listenerChannelItem = object : OnClickGesture<ChannelInfoItem>() { private val listenerChannelItem = object : OnClickGesture<ChannelInfoItem>() {
override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(fm, override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(
selectedItem.serviceId, selectedItem.url, selectedItem.name) fm,
selectedItem.serviceId, selectedItem.url, selectedItem.name
)
override fun held(selectedItem: ChannelInfoItem) = showLongTapDialog(selectedItem) override fun held(selectedItem: ChannelInfoItem) = showLongTapDialog(selectedItem)
} }
@ -420,14 +430,16 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private fun shouldUseGridLayout(): Boolean { private fun shouldUseGridLayout(): Boolean {
val listMode = PreferenceManager.getDefaultSharedPreferences(requireContext()) val listMode = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)) .getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value))
return when (listMode) { return when (listMode) {
getString(R.string.list_view_mode_auto_key) -> { getString(R.string.list_view_mode_auto_key) -> {
val configuration = resources.configuration val configuration = resources.configuration
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && (
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)) configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
)
} }
getString(R.string.list_view_mode_grid_key) -> true getString(R.string.list_view_mode_grid_key) -> true
else -> false else -> false

View File

@ -1,10 +1,10 @@
package org.schabi.newpipe.local.subscription package org.schabi.newpipe.local.subscription
import android.content.Context import android.content.Context
import io.reactivex.Completable import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.Flowable import io.reactivex.rxjava3.core.Completable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionDAO import org.schabi.newpipe.database.subscription.SubscriptionDAO
@ -32,7 +32,8 @@ class SubscriptionManager(context: Context) {
filterQuery.isNotEmpty() -> { filterQuery.isNotEmpty() -> {
return if (showOnlyUngrouped) { return if (showOnlyUngrouped) {
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered( subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
currentGroupId, filterQuery) currentGroupId, filterQuery
)
} else { } else {
subscriptionTable.getSubscriptionsFiltered(filterQuery) subscriptionTable.getSubscriptionsFiltered(filterQuery)
} }
@ -44,7 +45,8 @@ class SubscriptionManager(context: Context) {
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> { fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
val listEntities = subscriptionTable.upsertAll( val listEntities = subscriptionTable.upsertAll(
infoList.map { SubscriptionEntity.from(it) }) infoList.map { SubscriptionEntity.from(it) }
)
database.runInTransaction { database.runInTransaction {
infoList.forEachIndexed { index, info -> infoList.forEachIndexed { index, info ->

View File

@ -5,12 +5,12 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.xwray.groupie.Group import com.xwray.groupie.Group
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.item.ChannelItem import org.schabi.newpipe.local.subscription.item.ChannelItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import java.util.concurrent.TimeUnit
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) { class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application) private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
@ -22,22 +22,22 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
val feedGroupsLiveData: LiveData<List<Group>> = mutableFeedGroupsLiveData val feedGroupsLiveData: LiveData<List<Group>> = mutableFeedGroupsLiveData
private var feedGroupItemsDisposable = feedDatabaseManager.groups() private var feedGroupItemsDisposable = feedDatabaseManager.groups()
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map(::FeedGroupCardItem) } .map { it.map(::FeedGroupCardItem) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe( .subscribe(
{ mutableFeedGroupsLiveData.postValue(it) }, { mutableFeedGroupsLiveData.postValue(it) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) } { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
) )
private var stateItemsDisposable = subscriptionManager.subscriptions() private var stateItemsDisposable = subscriptionManager.subscriptions()
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } } .map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe( .subscribe(
{ mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) }, { mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) } { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
) )
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()

View File

@ -24,8 +24,6 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick import icepick.Icepick
import icepick.State import icepick.State
import java.io.Serializable
import kotlin.collections.contains
import kotlinx.android.synthetic.main.dialog_feed_group_create.* import kotlinx.android.synthetic.main.dialog_feed_group_create.*
import kotlinx.android.synthetic.main.toolbar_search_layout.* import kotlinx.android.synthetic.main.toolbar_search_layout.*
import org.schabi.newpipe.R import org.schabi.newpipe.R
@ -43,6 +41,8 @@ import org.schabi.newpipe.local.subscription.item.PickerIconItem
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
import org.schabi.newpipe.util.DeviceUtils import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
import java.io.Serializable
import kotlin.collections.contains
class FeedGroupDialog : DialogFragment(), BackPressable { class FeedGroupDialog : DialogFragment(), BackPressable {
private lateinit var viewModel: FeedGroupDialogViewModel private lateinit var viewModel: FeedGroupDialogViewModel
@ -116,21 +116,30 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, viewModel = ViewModelProvider(
FeedGroupDialogViewModel.Factory(requireContext(), this,
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped) FeedGroupDialogViewModel.Factory(
requireContext(),
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped
)
).get(FeedGroupDialogViewModel::class.java) ).get(FeedGroupDialogViewModel::class.java)
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup)) viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { viewModel.subscriptionsLiveData.observe(
setupSubscriptionPicker(it.first, it.second) viewLifecycleOwner,
}) Observer {
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second)
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
} }
}) )
viewModel.dialogEventLiveData.observe(
viewLifecycleOwner,
Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
}
)
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply { subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
add(subscriptionMainSection) add(subscriptionMainSection)
@ -141,8 +150,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
// Disable animations, too distracting. // Disable animations, too distracting.
itemAnimator = null itemAnimator = null
adapter = subscriptionGroupAdapter adapter = subscriptionGroupAdapter
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount, layoutManager = GridLayoutManager(
RecyclerView.VERTICAL, false).apply { requireContext(), subscriptionGroupAdapter.spanCount,
RecyclerView.VERTICAL, false
).apply {
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
} }
} }
@ -346,7 +357,8 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
val selectedCount = this.selectedSubscriptions.size val selectedCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString( val selectedCountText = resources.getQuantityString(
R.plurals.feed_group_dialog_selection_count, R.plurals.feed_group_dialog_selection_count,
selectedCount, selectedCount) selectedCount, selectedCount
)
selected_subscription_count_view.text = selectedCountText selected_subscription_count_view.text = selectedCountText
subscriptions_header_info.text = selectedCountText subscriptions_header_info.text = selectedCountText
} }
@ -401,10 +413,12 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
separator.onlyVisibleIn(SubscriptionsPickerScreen, IconPickerScreen) separator.onlyVisibleIn(SubscriptionsPickerScreen, IconPickerScreen)
cancel_button.onlyVisibleIn(InitialScreen, DeleteScreen) cancel_button.onlyVisibleIn(InitialScreen, DeleteScreen)
confirm_button.setText(when { confirm_button.setText(
currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create when {
else -> android.R.string.ok currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
}) else -> android.R.string.ok
}
)
delete_button.isGone = currentScreen != InitialScreen || groupId == NO_GROUP_SELECTED delete_button.isGone = currentScreen != InitialScreen || groupId == NO_GROUP_SELECTED
@ -454,8 +468,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
} }
private fun hideKeyboardSearch() { private fun hideKeyboardSearch() {
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken, inputMethodManager.hideSoftInputFromWindow(
InputMethodManager.RESULT_UNCHANGED_SHOWN) toolbar_search_edit_text.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
toolbar_search_edit_text.clearFocus() toolbar_search_edit_text.clearFocus()
} }
@ -466,8 +482,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
} }
private fun hideKeyboard() { private fun hideKeyboard() {
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, inputMethodManager.hideSoftInputFromWindow(
InputMethodManager.RESULT_UNCHANGED_SHOWN) group_name_input.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
group_name_input.clearFocus() group_name_input.clearFocus()
} }

View File

@ -5,12 +5,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.reactivex.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.functions.BiFunction import io.reactivex.rxjava3.functions.BiFunction
import io.reactivex.processors.BehaviorProcessor import io.reactivex.rxjava3.processors.BehaviorProcessor
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.FeedGroupIcon import org.schabi.newpipe.local.subscription.FeedGroupIcon
@ -32,9 +32,9 @@ class FeedGroupDialogViewModel(
private var subscriptionsFlowable = Flowable private var subscriptionsFlowable = Flowable
.combineLatest( .combineLatest(
filterSubscriptions.startWith(initialQuery), filterSubscriptions.startWithItem(initialQuery),
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped), toggleShowOnlyUngrouped.startWithItem(initialShowOnlyUngrouped),
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) } BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
) )
.distinctUntilChanged() .distinctUntilChanged()
.switchMap { (query, showOnlyUngrouped) -> .switchMap { (query, showOnlyUngrouped) ->
@ -55,8 +55,10 @@ class FeedGroupDialogViewModel(
.subscribe(mutableGroupLiveData::postValue) .subscribe(mutableGroupLiveData::postValue)
private var subscriptionsDisposable = Flowable private var subscriptionsDisposable = Flowable
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId), .combineLatest(
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() }) subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() }
)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue) .subscribe(mutableSubscriptionsLiveData::postValue)
@ -68,15 +70,19 @@ class FeedGroupDialogViewModel(
} }
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) { fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
doAction(feedDatabaseManager.createGroup(name, selectedIcon) doAction(
.flatMapCompletable { feedDatabaseManager.createGroup(name, selectedIcon)
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList()) .flatMapCompletable {
}) feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
}
)
} }
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) { fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList()) doAction(
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder)))) feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder)))
)
} }
fun deleteGroup() { fun deleteGroup() {
@ -120,8 +126,10 @@ class FeedGroupDialogViewModel(
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T { override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FeedGroupDialogViewModel(context.applicationContext, return FeedGroupDialogViewModel(
groupId, initialQuery, initialShowOnlyUngrouped) as T context.applicationContext,
groupId, initialQuery, initialShowOnlyUngrouped
) as T
} }
} }
} }

View File

@ -16,7 +16,6 @@ import com.xwray.groupie.TouchCallback
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick import icepick.Icepick
import icepick.State import icepick.State
import java.util.Collections
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
import org.schabi.newpipe.R import org.schabi.newpipe.R
@ -25,6 +24,7 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
import java.util.Collections
class FeedGroupReorderDialog : DialogFragment() { class FeedGroupReorderDialog : DialogFragment() {
private lateinit var viewModel: FeedGroupReorderDialogViewModel private lateinit var viewModel: FeedGroupReorderDialogViewModel
@ -51,12 +51,15 @@ class FeedGroupReorderDialog : DialogFragment() {
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java) viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups)) viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer { viewModel.dialogEventLiveData.observe(
when (it) { viewLifecycleOwner,
ProcessingEvent -> disableInput() Observer {
SuccessEvent -> dismiss() when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
} }
}) )
feed_groups_list.layoutManager = LinearLayoutManager(requireContext()) feed_groups_list.layoutManager = LinearLayoutManager(requireContext())
feed_groups_list.adapter = groupAdapter feed_groups_list.adapter = groupAdapter

View File

@ -4,9 +4,9 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.reactivex.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
@ -21,9 +21,9 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
private var actionProcessingDisposable: Disposable? = null private var actionProcessingDisposable: Disposable? = null
private var groupsDisposable = feedDatabaseManager.groups() private var groupsDisposable = feedDatabaseManager.groups()
.limit(1) .take(1)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(mutableGroupsLiveData::postValue) .subscribe(mutableGroupsLiveData::postValue)
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
@ -40,8 +40,8 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
actionProcessingDisposable = completable actionProcessingDisposable = completable
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) } .subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
} }
} }

View File

@ -36,8 +36,10 @@ class ChannelItem(
viewHolder.itemAdditionalDetails.text = getDetailLine(viewHolder.root.context) viewHolder.itemAdditionalDetails.text = getDetailLine(viewHolder.root.context)
if (itemVersion == ItemVersion.NORMAL) viewHolder.itemChannelDescriptionView.text = infoItem.description if (itemVersion == ItemVersion.NORMAL) viewHolder.itemChannelDescriptionView.text = infoItem.description
ImageLoader.getInstance().displayImage(infoItem.thumbnailUrl, viewHolder.itemThumbnailView, ImageLoader.getInstance().displayImage(
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS) infoItem.thumbnailUrl, viewHolder.itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
gesturesListener?.run { gesturesListener?.run {
viewHolder.containerView.setOnClickListener { selected(infoItem) } viewHolder.containerView.setOnClickListener { selected(infoItem) }

View File

@ -20,7 +20,7 @@ data class FeedGroupReorderItem(
val dragCallback: ItemTouchHelper val dragCallback: ItemTouchHelper
) : Item() { ) : Item() {
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) : constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback) this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
override fun getId(): Long { override fun getId(): Long {
return when (groupId) { return when (groupId) {

View File

@ -49,8 +49,10 @@ class FeedImportExportItem(
expandIconListener?.let { viewHolder.import_export_options.removeListener(it) } expandIconListener?.let { viewHolder.import_export_options.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState -> expandIconListener = CollapsibleView.StateListener { newState ->
AnimationUtils.animateRotation(viewHolder.import_export_expand_icon, AnimationUtils.animateRotation(
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180) viewHolder.import_export_expand_icon,
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
)
} }
viewHolder.import_export_options.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED viewHolder.import_export_options.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
@ -85,8 +87,10 @@ class FeedImportExportItem(
} }
private fun setupImportFromItems(listHolder: ViewGroup) { private fun setupImportFromItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(listHolder.context.getString(R.string.previous_export), val previousBackupItem = addItemView(
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder) listHolder.context.getString(R.string.previous_export),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder
)
previousBackupItem.setOnClickListener { onImportPreviousSelected() } previousBackupItem.setOnClickListener { onImportPreviousSelected() }
val iconColor = if (ThemeHelper.isLightThemeSelected(listHolder.context)) Color.BLACK else Color.WHITE val iconColor = if (ThemeHelper.isLightThemeSelected(listHolder.context)) Color.BLACK else Color.WHITE
@ -112,8 +116,10 @@ class FeedImportExportItem(
} }
private fun setupExportToItems(listHolder: ViewGroup) { private fun setupExportToItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(listHolder.context.getString(R.string.file), val previousBackupItem = addItemView(
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder) listHolder.context.getString(R.string.file),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder
)
previousBackupItem.setOnClickListener { onExportSelected() } previousBackupItem.setOnClickListener { onExportSelected() }
} }
} }

View File

@ -36,11 +36,11 @@ class HeaderWithMenuItem(
viewHolder.header_menu_item.setImageResource(itemIcon) viewHolder.header_menu_item.setImageResource(itemIcon)
val listener: OnClickListener? = val listener: OnClickListener? =
onClickListener?.let { OnClickListener { onClickListener.invoke() } } onClickListener?.let { OnClickListener { onClickListener.invoke() } }
viewHolder.root.setOnClickListener(listener) viewHolder.root.setOnClickListener(listener)
val menuItemListener: OnClickListener? = val menuItemListener: OnClickListener? =
menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } } menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
viewHolder.header_menu_item.setOnClickListener(menuItemListener) viewHolder.header_menu_item.setOnClickListener(menuItemListener)
updateMenuItemVisibility(viewHolder) updateMenuItemVisibility(viewHolder)
} }

View File

@ -22,8 +22,10 @@ data class PickerSubscriptionItem(
override fun getSpanSize(spanCount: Int, position: Int): Int = 1 override fun getSpanSize(spanCount: Int, position: Int): Int = 1
override fun bind(viewHolder: GroupieViewHolder, position: Int) { override fun bind(viewHolder: GroupieViewHolder, position: Int) {
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, ImageLoader.getInstance().displayImage(
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS) subscriptionEntity.avatarUrl,
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
)
viewHolder.title_view.text = subscriptionEntity.name viewHolder.title_view.text = subscriptionEntity.name
viewHolder.selected_highlight.isVisible = isSelected viewHolder.selected_highlight.isVisible = isSelected
@ -39,7 +41,9 @@ data class PickerSubscriptionItem(
fun updateSelected(containerView: View, isSelected: Boolean) { fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected this.isSelected = isSelected
animateView(containerView.selected_highlight, animateView(
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150) containerView.selected_highlight,
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150
)
} }
} }

View File

@ -46,11 +46,11 @@ import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.Flowable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.functions.Function; import io.reactivex.rxjava3.functions.Function;
import io.reactivex.processors.PublishProcessor; import io.reactivex.rxjava3.processors.PublishProcessor;
public abstract class BaseImportExportService extends Service { public abstract class BaseImportExportService extends Service {
protected final String TAG = this.getClass().getSimpleName(); protected final String TAG = this.getClass().getSimpleName();
@ -120,7 +120,7 @@ public abstract class BaseImportExportService extends Service {
startForeground(getNotificationId(), notificationBuilder.build()); startForeground(getNotificationId(), notificationBuilder.build());
final Function<Flowable<String>, Publisher<String>> throttleAfterFirstEmission = flow -> final Function<Flowable<String>, Publisher<String>> throttleAfterFirstEmission = flow ->
flow.limit(1).concatWith(flow.skip(1) flow.take(1).concatWith(flow.skip(1)
.throttleLast(NOTIFICATION_SAMPLING_PERIOD, TimeUnit.MILLISECONDS)); .throttleLast(NOTIFICATION_SAMPLING_PERIOD, TimeUnit.MILLISECONDS));
disposables.add(notificationUpdater disposables.add(notificationUpdater

View File

@ -37,9 +37,9 @@ import java.io.FileOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function; import io.reactivex.rxjava3.functions.Function;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;

View File

@ -46,12 +46,12 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.Notification; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Notification;
import io.reactivex.functions.Consumer; import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.functions.Function; import io.reactivex.rxjava3.functions.Function;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;

View File

@ -27,13 +27,13 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.media.AudioManager; import android.media.AudioManager;
import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
@ -77,17 +77,16 @@ import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException; import java.io.IOException;
import io.reactivex.Observable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable; import io.reactivex.rxjava3.disposables.SerialDisposable;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
import static io.reactivex.android.schedulers.AndroidSchedulers.mainThread;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
/** /**
@ -720,8 +719,9 @@ public abstract class BasePlayer implements
} }
private Disposable getProgressReactor() { private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS, mainThread()) return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
.observeOn(mainThread()) AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> triggerProgressUpdate(), .subscribe(ignored -> triggerProgressUpdate(),
error -> Log.e(TAG, "Progress update failure: ", error)); error -> Log.e(TAG, "Progress update failure: ", error));
} }
@ -1319,7 +1319,7 @@ public abstract class BasePlayer implements
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
final Disposable stateSaver = recordManager.saveStreamState(info, progress) final Disposable stateSaver = recordManager.saveStreamState(info, progress)
.observeOn(mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> { .doOnError((e) -> {
if (DEBUG) { if (DEBUG) {
e.printStackTrace(); e.printStackTrace();
@ -1339,7 +1339,7 @@ public abstract class BasePlayer implements
if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
final Disposable stateSaver = queueItem.getStream() final Disposable stateSaver = queueItem.getStream()
.flatMapCompletable(info -> recordManager.saveStreamState(info, 0)) .flatMapCompletable(info -> recordManager.saveStreamState(info, 0))
.observeOn(mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> { .doOnError((e) -> {
if (DEBUG) { if (DEBUG) {
e.printStackTrace(); e.printStackTrace();

View File

@ -30,14 +30,14 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import io.reactivex.Observable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.internal.subscriptions.EmptySubscription; import io.reactivex.rxjava3.internal.subscriptions.EmptySubscription;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject; import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException; import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException; import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;

View File

@ -14,8 +14,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.SingleObserver; import io.reactivex.rxjava3.core.SingleObserver;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue { abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
boolean isInitial; boolean isInitial;

View File

@ -9,8 +9,8 @@ import org.schabi.newpipe.util.ExtractorHelper;
import java.util.List; import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> { public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> {
public ChannelPlayQueue(final ChannelInfoItem item) { public ChannelPlayQueue(final ChannelInfoItem item) {

View File

@ -21,10 +21,10 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.BackpressureStrategy; import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.Flowable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.subjects.BehaviorSubject; import io.reactivex.rxjava3.subjects.BehaviorSubject;
/** /**
* PlayQueue is responsible for keeping track of a list of streams and the index of * PlayQueue is responsible for keeping track of a list of streams and the index of
@ -80,7 +80,7 @@ public abstract class PlayQueue implements Serializable {
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER) broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.startWith(new InitEvent()); .startWithItem(new InitEvent());
} }
/** /**

View File

@ -20,8 +20,8 @@ import org.schabi.newpipe.util.FallbackViewHolder;
import java.util.List; import java.util.List;
import io.reactivex.Observer; import io.reactivex.rxjava3.core.Observer;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
/** /**
* Created by Christian Schabesberger on 01.08.16. * Created by Christian Schabesberger on 01.08.16.

View File

@ -10,8 +10,8 @@ import org.schabi.newpipe.util.ExtractorHelper;
import java.io.Serializable; import java.io.Serializable;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class PlayQueueItem implements Serializable { public class PlayQueueItem implements Serializable {
public static final long RECOVERY_UNSET = Long.MIN_VALUE; public static final long RECOVERY_UNSET = Long.MIN_VALUE;

View File

@ -8,8 +8,8 @@ import org.schabi.newpipe.util.ExtractorHelper;
import java.util.List; import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, PlaylistInfoItem> { public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo, PlaylistInfoItem> {
public PlaylistPlayQueue(final PlaylistInfoItem item) { public PlaylistPlayQueue(final PlaylistInfoItem item) {

View File

@ -14,9 +14,9 @@ import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.InfoCache; import org.schabi.newpipe.util.InfoCache;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
public class HistorySettingsFragment extends BasePreferenceFragment { public class HistorySettingsFragment extends BasePreferenceFragment {
private String cacheWipeKey; private String cacheWipeKey;

View File

@ -16,4 +16,4 @@ class NotificationSettingsFragment : BasePreferenceFragment() {
} }
} }
} }
} }

View File

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.PreferenceManager;
import android.text.InputType; import android.text.InputType;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -26,6 +25,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -44,11 +44,11 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.Single; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class PeertubeInstanceListFragment extends Fragment { public class PeertubeInstanceListFragment extends Fragment {
private static final int MENU_ITEM_RESTORE_ID = 123456; private static final int MENU_ITEM_RESTORE_ID = 123456;

View File

@ -30,10 +30,10 @@ import java.util.List;
import java.util.Vector; import java.util.Vector;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
import io.reactivex.Observer; import io.reactivex.rxjava3.core.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
/** /**
* Created by Christian Schabesberger on 26.09.17. * Created by Christian Schabesberger on 26.09.17.

View File

@ -33,9 +33,9 @@ import org.schabi.newpipe.report.UserAction;
import java.util.List; import java.util.List;
import java.util.Vector; import java.util.Vector;
import io.reactivex.Flowable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
public class SelectPlaylistFragment extends DialogFragment { public class SelectPlaylistFragment extends DialogFragment {
/** /**

View File

@ -23,9 +23,9 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class CommentTextOnTouchListener implements View.OnTouchListener { public class CommentTextOnTouchListener implements View.OnTouchListener {
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();

View File

@ -10,9 +10,11 @@ class ExceptionUtils {
*/ */
@JvmStatic @JvmStatic
fun isInterruptedCaused(throwable: Throwable): Boolean { fun isInterruptedCaused(throwable: Throwable): Boolean {
return hasExactCause(throwable, return hasExactCause(
InterruptedIOException::class.java, throwable,
InterruptedException::class.java) InterruptedIOException::class.java,
InterruptedException::class.java
)
} }
/** /**
@ -20,8 +22,10 @@ class ExceptionUtils {
*/ */
@JvmStatic @JvmStatic
fun isNetworkRelated(throwable: Throwable): Boolean { fun isNetworkRelated(throwable: Throwable): Boolean {
return hasAssignableCause(throwable, return hasAssignableCause(
IOException::class.java) throwable,
IOException::class.java
)
} }
/** /**

View File

@ -57,8 +57,8 @@ import org.schabi.newpipe.report.UserAction;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.Maybe; import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.Single; import io.reactivex.rxjava3.core.Single;
public final class ExtractorHelper { public final class ExtractorHelper {
private static final String TAG = ExtractorHelper.class.getSimpleName(); private static final String TAG = ExtractorHelper.class.getSimpleName();

View File

@ -23,9 +23,9 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import io.reactivex.Single; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import us.shandian.giga.util.Utility; import us.shandian.giga.util.Utility;
/** /**

View File

@ -49,10 +49,10 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import io.reactivex.Observable; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission; import us.shandian.giga.get.Mission;

View File

@ -13,8 +13,10 @@ class FeedGroupIconTest {
val added = usedIds.add(currentIcon.id) val added = usedIds.add(currentIcon.id)
assertTrue("Repeated ids (current item: ${currentIcon.name} - ${currentIcon.id})", added) assertTrue("Repeated ids (current item: ${currentIcon.name} - ${currentIcon.id})", added)
assertEquals("Gap between ids detected (current item: ${currentIcon.name} - ${currentIcon.id} → should be: $shouldBeId)", assertEquals(
shouldBeId, currentIcon.id) "Gap between ids detected (current item: ${currentIcon.name} - ${currentIcon.id} → should be: $shouldBeId)",
shouldBeId, currentIcon.id
)
} }
} }

View File

@ -1,14 +1,14 @@
package org.schabi.newpipe.util package org.schabi.newpipe.util
import java.io.IOException
import java.io.InterruptedIOException
import java.net.SocketException
import javax.net.ssl.SSLException
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.schabi.newpipe.util.ExceptionUtils.Companion.hasAssignableCause import org.schabi.newpipe.util.ExceptionUtils.Companion.hasAssignableCause
import org.schabi.newpipe.util.ExceptionUtils.Companion.hasExactCause import org.schabi.newpipe.util.ExceptionUtils.Companion.hasExactCause
import java.io.IOException
import java.io.InterruptedIOException
import java.net.SocketException
import javax.net.ssl.SSLException
class ExceptionUtilsTest { class ExceptionUtilsTest {
@Test fun `assignable causes`() { @Test fun `assignable causes`() {

View File

@ -9,7 +9,8 @@ import org.junit.Test
class UrlFinderTest { class UrlFinderTest {
@Test fun `first url from long text`() { @Test fun `first url from long text`() {
val expected = "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_" val expected = "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_"
val result = UrlFinder.firstUrlFromInput(""" val result = UrlFinder.firstUrlFromInput(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|Eu tincidunt tortor aliquam nulla. URL: https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ Sed dictum consequat dui. |Eu tincidunt tortor aliquam nulla. URL: https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ Sed dictum consequat dui.
|Pharetra diam sit amet nisl suscipit adipiscing bibendum est. |Pharetra diam sit amet nisl suscipit adipiscing bibendum est.
@ -18,13 +19,15 @@ class UrlFinderTest {
|Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nisl purus in mollis nunc. |Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nisl purus in mollis nunc.
|Viverra nibh cras pulvinar mattis. ####!@!@!@!#### Not this one: https://www.youtube.com/playlist?list=SHOULD_NOT Nunc sed blandit libero volutpat. |Viverra nibh cras pulvinar mattis. ####!@!@!@!#### Not this one: https://www.youtube.com/playlist?list=SHOULD_NOT Nunc sed blandit libero volutpat.
|Nisl tincidunt eget nullam non nisi est sit amet. Purus in massa tempor nec feugiat nisl pretium fusce id. |Nisl tincidunt eget nullam non nisi est sit amet. Purus in massa tempor nec feugiat nisl pretium fusce id.
|Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin()) |Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin()
)
assertEquals(expected, result) assertEquals(expected, result)
} }
@Test fun `no url from long text`() { @Test fun `no url from long text`() {
val result = UrlFinder.firstUrlFromInput(""" val result = UrlFinder.firstUrlFromInput(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|Eu tincidunt tortor aliquam nulla. Sed dictum consequat dui. Pharetra diam sit amet nisl suscipit adipiscing bibendum est. |Eu tincidunt tortor aliquam nulla. Sed dictum consequat dui. Pharetra diam sit amet nisl suscipit adipiscing bibendum est.
|Volutpat sed cras ornare arcu dui vivamus. Nulla posuere sollicitudin aliquam ultrices sagittis. |Volutpat sed cras ornare arcu dui vivamus. Nulla posuere sollicitudin aliquam ultrices sagittis.
@ -32,7 +35,8 @@ class UrlFinderTest {
|Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nisl purus in mollis nunc. |Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nisl purus in mollis nunc.
|Viverra nibh cras pulvinar mattis. Not this one: sed blandit libero volutpat. |Viverra nibh cras pulvinar mattis. Not this one: sed blandit libero volutpat.
|Nisl tincidunt eget nullam non nisi est sit amet. Purus in massa tempor nec feugiat nisl pretium fusce id. |Nisl tincidunt eget nullam non nisi est sit amet. Purus in massa tempor nec feugiat nisl pretium fusce id.
|Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin()) |Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin()
)
assertEquals(null, result) assertEquals(null, result)
} }
@ -44,14 +48,20 @@ class UrlFinderTest {
} }
@Test fun `normal urls`() { @Test fun `normal urls`() {
assertEquals("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_", assertEquals(
UrlFinder.firstUrlFromInput("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_")) "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
UrlFinder.firstUrlFromInput("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_")
)
assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", assertEquals(
UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ")) "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
)
assertEquals("http://www.youtube.com/watch?v=dQw4w9WgXcQ", assertEquals(
UrlFinder.firstUrlFromInput("http://www.youtube.com/watch?v=dQw4w9WgXcQ")) "http://www.youtube.com/watch?v=dQw4w9WgXcQ",
UrlFinder.firstUrlFromInput("http://www.youtube.com/watch?v=dQw4w9WgXcQ")
)
assertEquals("https://www.google.com", UrlFinder.firstUrlFromInput("https://www.google.com")) assertEquals("https://www.google.com", UrlFinder.firstUrlFromInput("https://www.google.com"))
assertEquals("http://www.google.com/test/", UrlFinder.firstUrlFromInput("http://www.google.com/test/")) assertEquals("http://www.google.com/test/", UrlFinder.firstUrlFromInput("http://www.google.com/test/"))
@ -79,21 +89,33 @@ class UrlFinderTest {
} }
@Test fun `random prefixes and suffixes`() { @Test fun `random prefixes and suffixes`() {
assertEquals("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_", assertEquals(
UrlFinder.firstUrlFromInput("$#!@#@!#https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ @@@@@@@@@@@")) "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
UrlFinder.firstUrlFromInput("$#!@#@!#https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ @@@@@@@@@@@")
)
assertEquals("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_", assertEquals(
UrlFinder.firstUrlFromInput("(___\"https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_\")))_")) "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
UrlFinder.firstUrlFromInput("(___\"https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_\")))_")
)
assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", assertEquals(
UrlFinder.firstUrlFromInput(" https://www.youtube.com/watch?v=dQw4w9WgXcQ ")) "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
UrlFinder.firstUrlFromInput(" https://www.youtube.com/watch?v=dQw4w9WgXcQ ")
)
assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", assertEquals(
UrlFinder.firstUrlFromInput(" ------_---__-https://www.youtube.com/watch?v=dQw4w9WgXcQ !!!!!!")) "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
UrlFinder.firstUrlFromInput(" ------_---__-https://www.youtube.com/watch?v=dQw4w9WgXcQ !!!!!!")
)
assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", assertEquals(
UrlFinder.firstUrlFromInput("****https://www.youtube.com/watch?v=dQw4w9WgXcQ _")) "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", UrlFinder.firstUrlFromInput("****https://www.youtube.com/watch?v=dQw4w9WgXcQ _")
UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ\"Not PartOfTheUrl")) )
assertEquals(
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ\"Not PartOfTheUrl")
)
} }
} }

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.72' ext.kotlin_version = '1.4.10'
repositories { repositories {
jcenter() jcenter()
google() google()

View File

@ -1,4 +1,4 @@
#Sat Oct 17 06:10:46 IST 2020 #Thu Oct 15 11:41:05 CEST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME