Merge pull request #4768 from Stypox/update-dependencies
Update most dependencies
This commit is contained in:
commit
29376066e8
|
@ -89,20 +89,20 @@ android {
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
icepickVersion = '3.2.0'
|
icepickVersion = '3.2.0'
|
||||||
checkstyleVersion = '8.36.2'
|
checkstyleVersion = '8.37'
|
||||||
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,79 +130,81 @@ 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 formatKtlint, 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}"
|
||||||
|
|
||||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||||
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 'org.mockito:mockito-core:3.3.3'
|
|
||||||
|
|
||||||
androidTestImplementation "androidx.test.ext:junit:1.1.1"
|
|
||||||
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
|
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
|
|
||||||
exclude module: 'support-annotations'
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPipe dependencies
|
// NewPipe dependencies
|
||||||
// You can use a local version by uncommenting a few lines in settings.gradle
|
// You can use a local version by uncommenting a few lines in settings.gradle
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:6701b0fe718f6bdc385221341fa473e8aaab560e'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:650f0920fea535e08728d895d7b21f19c740817c'
|
||||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||||
|
|
||||||
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,13 +220,22 @@ 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"
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.13.1'
|
||||||
|
testImplementation 'org.mockito:mockito-core:3.6.0'
|
||||||
|
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||||
|
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0", {
|
||||||
|
exclude module: 'support-annotations'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getGitWorkingBranch() {
|
static String getGitWorkingBranch() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -2,8 +2,8 @@ package org.schabi.newpipe.about
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import java.io.Serializable
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for storing information about a software license.
|
* Class for storing information about a software license.
|
||||||
|
@ -12,8 +12,8 @@ import kotlinx.android.parcel.Parcelize
|
||||||
class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable {
|
class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable {
|
||||||
val contentUri: Uri
|
val contentUri: Uri
|
||||||
get() = Uri.Builder()
|
get() = Uri.Builder()
|
||||||
.scheme("file")
|
.scheme("file")
|
||||||
.path("/android_asset")
|
.path("/android_asset")
|
||||||
.appendPath(filename)
|
.appendPath(filename)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -6,19 +6,20 @@ 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 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 +28,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 +49,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 +65,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 +83,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 +108,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 +124,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 +135,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 +162,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;
|
||||||
|
|
|
@ -6,14 +6,14 @@ 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 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 +35,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 +81,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 +90,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 +103,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 +115,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;
|
||||||
|
|
|
@ -5,8 +5,6 @@ import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
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 +13,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 +62,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,14 +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.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import org.schabi.newpipe.MainActivity.DEBUG
|
import org.schabi.newpipe.MainActivity.DEBUG
|
||||||
import org.schabi.newpipe.NewPipeDatabase
|
import org.schabi.newpipe.NewPipeDatabase
|
||||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||||
|
@ -19,6 +16,9 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
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.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
class FeedDatabaseManager(context: Context) {
|
class FeedDatabaseManager(context: Context) {
|
||||||
private val database = NewPipeDatabase.getInstance(context)
|
private val database = NewPipeDatabase.getInstance(context)
|
||||||
|
@ -31,7 +31,7 @@ class FeedDatabaseManager(context: Context) {
|
||||||
* Only items that are newer than this will be saved.
|
* Only items that are newer than this will be saved.
|
||||||
*/
|
*/
|
||||||
val FEED_OLDEST_ALLOWED_DATE: OffsetDateTime = LocalDate.now().minusWeeks(13)
|
val FEED_OLDEST_ALLOWED_DATE: OffsetDateTime = LocalDate.now().minusWeeks(13)
|
||||||
.atStartOfDay().atOffset(ZoneOffset.UTC)
|
.atStartOfDay().atOffset(ZoneOffset.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun groups() = feedGroupTable.getAll()
|
fun groups() = feedGroupTable.getAll()
|
||||||
|
@ -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,
|
||||||
|
@ -92,8 +92,12 @@ class FeedDatabaseManager(context: Context) {
|
||||||
feedTable.insertAll(feedEntities)
|
feedTable.insertAll(feedEntities)
|
||||||
}
|
}
|
||||||
|
|
||||||
feedTable.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId,
|
feedTable.setLastUpdatedForSubscription(
|
||||||
OffsetDateTime.now(ZoneOffset.UTC)))
|
FeedLastUpdatedEntity(
|
||||||
|
subscriptionId,
|
||||||
|
OffsetDateTime.now(ZoneOffset.UTC)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
|
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
|
||||||
|
@ -113,38 +117,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 +156,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,12 +5,10 @@ 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
|
||||||
|
@ -20,6 +18,8 @@ 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.ProgressEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
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 +35,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
|
||||||
|
@ -56,13 +50,18 @@ 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.ErrorResultEvent
|
||||||
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.ProgressEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
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 +108,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 +125,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 +183,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() {
|
||||||
|
@ -262,7 +264,7 @@ class FeedLoadService : Service() {
|
||||||
|
|
||||||
override fun onComplete() {
|
override fun onComplete() {
|
||||||
if (maxProgress.get() == 0) {
|
if (maxProgress.get() == 0) {
|
||||||
postEvent(IdleEvent)
|
postEvent(FeedEventManager.Event.IdleEvent)
|
||||||
stopService()
|
stopService()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -274,7 +276,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 +296,8 @@ class FeedLoadService : Service() {
|
||||||
return@subscribe
|
return@subscribe
|
||||||
}
|
}
|
||||||
stopService()
|
stopService()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,16 +368,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 +387,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();
|
||||||
|
|
|
@ -118,22 +118,30 @@ abstract class BasePlayerGestureListener(
|
||||||
initSecPointerX = event.getX(1)
|
initSecPointerX = event.getX(1)
|
||||||
initSecPointerY = event.getY(1)
|
initSecPointerY = event.getY(1)
|
||||||
// record distance between fingers
|
// record distance between fingers
|
||||||
initPointerDistance = hypot(initFirstPointerX - initSecPointerX.toDouble(),
|
initPointerDistance = hypot(
|
||||||
initFirstPointerY - initSecPointerY.toDouble())
|
initFirstPointerX - initSecPointerX.toDouble(),
|
||||||
|
initFirstPointerY - initSecPointerY.toDouble()
|
||||||
|
)
|
||||||
|
|
||||||
isResizing = true
|
isResizing = true
|
||||||
}
|
}
|
||||||
if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
|
if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onTouch() ACTION_MOVE > v = [$v], e1.getRaw = [${event.rawX}" +
|
Log.d(
|
||||||
", ${event.rawY}]")
|
TAG,
|
||||||
|
"onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" +
|
||||||
|
"[${event.rawX}, ${event.rawY}]"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return handleMultiDrag(event)
|
return handleMultiDrag(event)
|
||||||
}
|
}
|
||||||
if (event.action == MotionEvent.ACTION_UP) {
|
if (event.action == MotionEvent.ACTION_UP) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onTouch() ACTION_UP > v = [$v], e1.getRaw = [${event.rawX}" +
|
Log.d(
|
||||||
", ${event.rawY}]")
|
TAG,
|
||||||
|
"onTouch() ACTION_UP > v = [$v], e1.getRaw =" +
|
||||||
|
" [${event.rawX}, ${event.rawY}]"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (isMovingInPopup) {
|
if (isMovingInPopup) {
|
||||||
isMovingInPopup = false
|
isMovingInPopup = false
|
||||||
|
@ -163,18 +171,24 @@ abstract class BasePlayerGestureListener(
|
||||||
private fun handleMultiDrag(event: MotionEvent): Boolean {
|
private fun handleMultiDrag(event: MotionEvent): Boolean {
|
||||||
if (initPointerDistance != -1.0 && event.pointerCount == 2) {
|
if (initPointerDistance != -1.0 && event.pointerCount == 2) {
|
||||||
// get the movements of the fingers
|
// get the movements of the fingers
|
||||||
val firstPointerMove = hypot(event.getX(0) - initFirstPointerX.toDouble(),
|
val firstPointerMove = hypot(
|
||||||
event.getY(0) - initFirstPointerY.toDouble())
|
event.getX(0) - initFirstPointerX.toDouble(),
|
||||||
val secPointerMove = hypot(event.getX(1) - initSecPointerX.toDouble(),
|
event.getY(0) - initFirstPointerY.toDouble()
|
||||||
event.getY(1) - initSecPointerY.toDouble())
|
)
|
||||||
|
val secPointerMove = hypot(
|
||||||
|
event.getX(1) - initSecPointerX.toDouble(),
|
||||||
|
event.getY(1) - initSecPointerY.toDouble()
|
||||||
|
)
|
||||||
|
|
||||||
// minimum threshold beyond which pinch gesture will work
|
// minimum threshold beyond which pinch gesture will work
|
||||||
val minimumMove = ViewConfiguration.get(service).scaledTouchSlop
|
val minimumMove = ViewConfiguration.get(service).scaledTouchSlop
|
||||||
|
|
||||||
if (max(firstPointerMove, secPointerMove) > minimumMove) {
|
if (max(firstPointerMove, secPointerMove) > minimumMove) {
|
||||||
// calculate current distance between the pointers
|
// calculate current distance between the pointers
|
||||||
val currentPointerDistance = hypot(event.getX(0) - event.getX(1).toDouble(),
|
val currentPointerDistance = hypot(
|
||||||
event.getY(0) - event.getY(1).toDouble())
|
event.getX(0) - event.getX(1).toDouble(),
|
||||||
|
event.getY(0) - event.getY(1).toDouble()
|
||||||
|
)
|
||||||
|
|
||||||
val popupWidth = playerImpl.popupWidth.toDouble()
|
val popupWidth = playerImpl.popupWidth.toDouble()
|
||||||
// change co-ordinates of popup so the center stays at the same position
|
// change co-ordinates of popup so the center stays at the same position
|
||||||
|
@ -187,7 +201,8 @@ abstract class BasePlayerGestureListener(
|
||||||
|
|
||||||
playerImpl.updatePopupSize(
|
playerImpl.updatePopupSize(
|
||||||
min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
|
min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
|
||||||
-1)
|
-1
|
||||||
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,22 +331,30 @@ abstract class BasePlayerGestureListener(
|
||||||
}
|
}
|
||||||
|
|
||||||
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
|
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
|
||||||
val isTouchingNavigationBar: Boolean = (initialEvent.y
|
val isTouchingNavigationBar: Boolean =
|
||||||
> playerImpl.rootView.height - getNavigationBarHeight(service))
|
initialEvent.y > (playerImpl.rootView.height - getNavigationBarHeight(service))
|
||||||
if (isTouchingStatusBar || isTouchingNavigationBar) {
|
if (isTouchingStatusBar || isTouchingNavigationBar) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
|
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
|
||||||
if (!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
|
if (
|
||||||
playerImpl.currentState == BasePlayer.STATE_COMPLETED) {
|
!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
|
||||||
|
playerImpl.currentState == BasePlayer.STATE_COMPLETED
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
isMovingInMain = true
|
isMovingInMain = true
|
||||||
|
|
||||||
onScroll(MainPlayer.PlayerType.VIDEO, getDisplayHalfPortion(initialEvent),
|
onScroll(
|
||||||
initialEvent, movingEvent, distanceX, distanceY)
|
MainPlayer.PlayerType.VIDEO,
|
||||||
|
getDisplayHalfPortion(initialEvent),
|
||||||
|
initialEvent,
|
||||||
|
movingEvent,
|
||||||
|
distanceX,
|
||||||
|
distanceY
|
||||||
|
)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -373,8 +396,14 @@ abstract class BasePlayerGestureListener(
|
||||||
playerImpl.popupLayoutParams.x = posX.toInt()
|
playerImpl.popupLayoutParams.x = posX.toInt()
|
||||||
playerImpl.popupLayoutParams.y = posY.toInt()
|
playerImpl.popupLayoutParams.y = posY.toInt()
|
||||||
|
|
||||||
onScroll(MainPlayer.PlayerType.POPUP, getDisplayHalfPortion(initialEvent),
|
onScroll(
|
||||||
initialEvent, movingEvent, distanceX, distanceY)
|
MainPlayer.PlayerType.POPUP,
|
||||||
|
getDisplayHalfPortion(initialEvent),
|
||||||
|
initialEvent,
|
||||||
|
movingEvent,
|
||||||
|
distanceX,
|
||||||
|
distanceY
|
||||||
|
)
|
||||||
|
|
||||||
playerImpl.windowManager
|
playerImpl.windowManager
|
||||||
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
|
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -16,4 +16,4 @@ class NotificationSettingsFragment : BasePreferenceFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package us.shandian.giga.get
|
package us.shandian.giga.get
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import java.io.Serializable
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import org.schabi.newpipe.extractor.MediaFormat
|
import org.schabi.newpipe.extractor.MediaFormat
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream
|
import org.schabi.newpipe.extractor.stream.AudioStream
|
||||||
import org.schabi.newpipe.extractor.stream.Stream
|
import org.schabi.newpipe.extractor.stream.Stream
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream
|
import org.schabi.newpipe.extractor.stream.VideoStream
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class MissionRecoveryInfo(
|
class MissionRecoveryInfo(
|
||||||
|
@ -62,10 +62,10 @@ class MissionRecoveryInfo(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
str.append(" format=")
|
str.append(" format=")
|
||||||
.append(format.getName())
|
.append(format.getName())
|
||||||
.append(' ')
|
.append(' ')
|
||||||
.append(info)
|
.append(info)
|
||||||
.append('}')
|
.append('}')
|
||||||
return str.toString()
|
return str.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue