mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 07:13:00 +00:00 
			
		
		
		
	Update most dependencies
This commit is contained in:
		| @@ -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" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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() { } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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)) | ||||||
|   | |||||||
| @@ -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> { | ||||||
|   | |||||||
| @@ -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>> | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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> { | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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>> { | ||||||
|   | |||||||
| @@ -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 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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?) { | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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> { | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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 { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 -> | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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) } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) } | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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() } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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 { | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /** |         /** | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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`() { | ||||||
|   | |||||||
| @@ -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") | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Stypox
					Stypox