SponsorBlock: Merge branch 'dev' into sponsorblock

# Conflicts:
#	app/src/main/res/values/strings.xml
This commit is contained in:
polymorphicshade 2020-11-22 18:23:20 -07:00
commit 86b324467d
246 changed files with 2587 additions and 1904 deletions

View file

@ -13,8 +13,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
versionCode 957
versionName "0.20.3"
versionCode 958
versionName "0.20.4"
multiDexEnabled true
@ -89,20 +89,20 @@ android {
ext {
icepickVersion = '3.2.0'
checkstyleVersion = '8.36.2'
checkstyleVersion = '8.37'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.2'
leakCanaryVersion = '2.5'
exoPlayerVersion = '2.11.8'
androidxLifecycleVersion = '2.2.0'
androidxRoomVersion = '2.2.5'
groupieVersion = '2.8.0'
markwonVersion = '4.3.1'
androidxRoomVersion = '2.3.0-alpha03'
groupieVersion = '2.8.1'
markwonVersion = '4.6.0'
googleAutoServiceVersion = '1.0-rc7'
}
configurations {
checkstyle
// ktlint
ktlint
}
checkstyle {
@ -130,81 +130,85 @@ task runCheckstyle(type: Checkstyle) {
}
}
//task runKtlint(type: JavaExec) {
// main = "com.pinterest.ktlint.Main"
// classpath = configurations.ktlint
// args "src/**/*.kt"
//}
//
//task formatKtlint(type: JavaExec) {
// main = "com.pinterest.ktlint.Main"
// classpath = configurations.ktlint
// args "-F", "src/**/*.kt"
//}
def outputDir = "${project.buildDir}/reports/ktlint/"
def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
task runKtlint(type: JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "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 {
preDebugBuild.dependsOn runCheckstyle //, runKtlint
preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
}
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}"
kapt "frankiesardo:icepick-processor:${icepickVersion}"
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-okhttp3:${stethoVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-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"
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
// 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 "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.google.android.exoplayer:exoplayer:${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}"
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.recyclerview:recyclerview:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation 'androidx.core:core-ktx:1.3.1'
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
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-viewmodel:${androidxLifecycleVersion}"
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "com.xwray:groupie:${groupieVersion}"
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
@ -216,13 +220,22 @@ dependencies {
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.rxjava2:rxandroid:2.1.1"
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
implementation "io.reactivex.rxjava3:rxjava:3.0.7"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
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() {

View file

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

View file

@ -8,7 +8,6 @@ import androidx.test.filters.LargeTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.schabi.newpipe.R;
import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
import static org.junit.Assert.assertEquals;
@ -29,10 +28,10 @@ public class ErrorInfoTest {
parcel.setDataPosition(0);
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
assertEquals("youtube", infoFromParcel.serviceName);
assertEquals("request", infoFromParcel.request);
assertEquals(R.string.general_error, infoFromParcel.message);
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
assertEquals("youtube", infoFromParcel.getServiceName());
assertEquals("request", infoFromParcel.getRequest());
assertEquals(R.string.general_error, infoFromParcel.getMessage());
parcel.recycle();
}

View file

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

View file

@ -305,8 +305,7 @@
<data android:host="peertube.cpy.re" />
<data android:host="peertube.mastodon.host" />
<data android:host="peertube.fr" />
<data android:host="peertube.live" />
<data android:host="peertube.video" />
<data android:host="tilvids.com" />
<data android:host="tube.privacytools.io" />
<data android:host="video.ploud.fr" />
<data android:host="video.lqdn.fr" />

View file

@ -86,8 +86,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
private final int mBehavior;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
/**

View file

@ -8,6 +8,7 @@ import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
@ -22,6 +23,7 @@ import org.acra.config.CoreConfigurationBuilder;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ExceptionUtils;
@ -36,13 +38,13 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException;
import io.reactivex.exceptions.OnErrorNotImplementedException;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.functions.Consumer;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.CompositeException;
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
/*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
@ -66,7 +68,7 @@ public class App extends MultiDexApplication {
protected static final String TAG = App.class.toString();
private static App app;
private Disposable disposable = null;
@Nullable private Disposable disposable = null;
@NonNull
public static App getApp() {
@ -226,7 +228,7 @@ public class App extends MultiDexApplication {
ace,
null,
null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not initialize ACRA crash report", R.string.app_ui_crash));
}
}

View file

@ -12,6 +12,7 @@ import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
@ -21,12 +22,11 @@ import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -35,11 +35,10 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class CheckForNewAppVersion {
private CheckForNewAppVersion() { }
@ -57,48 +56,43 @@ public final class CheckForNewAppVersion {
* @param application The application
* @return String with the apk's SHA1 fingeprint in hexadecimal
*/
@NonNull
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
final PackageManager pm = application.getPackageManager();
final String packageName = application.getPackageName();
final int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
final PackageInfo packageInfo;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
packageInfo = application.getPackageManager().getPackageInfo(
application.getPackageName(), PackageManager.GET_SIGNATURES);
} catch (final PackageManager.NameNotFoundException e) {
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
return "";
}
final Signature[] signatures = packageInfo.signatures;
final byte[] cert = signatures[0].toByteArray();
final InputStream input = new ByteArrayInputStream(cert);
X509Certificate c = null;
final X509Certificate c;
try {
final Signature[] signatures = packageInfo.signatures;
final byte[] cert = signatures[0].toByteArray();
final InputStream input = new ByteArrayInputStream(cert);
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (final CertificateException e) {
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
return "";
}
String hexString = null;
try {
final MessageDigest md = MessageDigest.getInstance("SHA1");
final byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
return byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
return "";
}
return hexString;
}
private static String byte2HexFormatted(final byte[] arr) {
@ -163,66 +157,66 @@ public final class CheckForNewAppVersion {
}
private static boolean isConnected(@NonNull final App app) {
final ConnectivityManager cm = ContextCompat.getSystemService(app,
ConnectivityManager.class);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
final ConnectivityManager connectivityManager =
ContextCompat.getSystemService(app, ConnectivityManager.class);
return connectivityManager != null && connectivityManager.getActiveNetworkInfo() != null
&& connectivityManager.getActiveNetworkInfo().isConnected();
}
public static boolean isGithubApk(@NonNull final App app) {
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
}
@NonNull
@Nullable
public static Disposable checkNewVersion(@NonNull final App app) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
// Check if user has enabled/disabled update checking
// and if the current apk is a github one or not.
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk(app)) {
return Disposables.empty();
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
return null;
}
return Observable.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}
return Maybe
.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}
// Make a network request to get latest NewPipe data.
try {
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
} catch (IOException | ReCaptchaException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
}
}
return null;
})
// Make a network request to get latest NewPipe data.
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
// Parse the json from the response.
if (response != null) {
try {
final JsonObject githubStableObject = JsonParser.object().from(response)
.getObject("flavors").getObject("github").getObject("stable");
.subscribe(
response -> {
// Parse the json from the response.
try {
final JsonObject githubStableObject = JsonParser.object()
.from(response).getObject("flavors").getObject("github")
.getObject("stable");
final String versionName = githubStableObject.getString("version");
final int versionCode = githubStableObject.getInt("version_code");
final String apkLocationUrl = githubStableObject.getString("apk");
final String versionName = githubStableObject
.getString("version");
final int versionCode = githubStableObject
.getInt("version_code");
final String apkLocationUrl = githubStableObject
.getString("apk");
compareAppVersionAndShowNotification(app, versionName, apkLocationUrl,
versionCode);
} catch (final JsonParserException e) {
compareAppVersionAndShowNotification(app, versionName,
apkLocationUrl, versionCode);
} catch (final JsonParserException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: invalid json", e);
}
}
},
e -> {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
Log.w(TAG, "Could not get NewPipe API: network problem", e);
}
}
}
});
});
}
}

View file

@ -50,8 +50,8 @@ public final class DownloaderImpl extends Downloader {
public static final String YOUTUBE_DOMAIN = "youtube.com";
private static DownloaderImpl instance;
private Map<String, String> mCookies;
private OkHttpClient client;
private final Map<String, String> mCookies;
private final OkHttpClient client;
private DownloaderImpl(final OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {

View file

@ -139,8 +139,7 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (getSupportFragmentManager() != null
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}

View file

@ -66,13 +66,13 @@ import java.util.List;
import icepick.Icepick;
import icepick.State;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
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.VIDEO;
@ -519,7 +519,7 @@ public class RouterActivity extends AppCompatActivity {
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
.subscribe(result -> {
final List<VideoStream> sortedVideoStreams = ListHelper
.getSortedStreamVideosList(this, result.getVideoStreams(),
result.getVideoOnlyStreams(), false);
@ -534,9 +534,8 @@ public class RouterActivity extends AppCompatActivity {
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
downloadDialog.requireDialog().setOnDismissListener(dialog -> finish());
}, (@NonNull Throwable throwable) -> {
showUnsupportedUrlDialog(currentUrl);
}));
}, throwable ->
showUnsupportedUrlDialog(currentUrl)));
}
@Override

View file

@ -31,7 +31,7 @@ public class AboutActivity extends AppCompatActivity {
/**
* List of all software components.
*/
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = {
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",

View file

@ -1,80 +0,0 @@
package org.schabi.newpipe.about;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
/**
* Class for storing information about a software license.
*/
public class License implements Parcelable, Serializable {
public static final Creator<License> CREATOR = new Creator<License>() {
@Override
public License createFromParcel(final Parcel source) {
return new License(source);
}
@Override
public License[] newArray(final int size) {
return new License[size];
}
};
private final String abbreviation;
private final String name;
private String filename;
public License(final String name, final String abbreviation, final String filename) {
if (name == null) {
throw new NullPointerException("name is null");
}
if (abbreviation == null) {
throw new NullPointerException("abbreviation is null");
}
if (filename == null) {
throw new NullPointerException("filename is null");
}
this.name = name;
this.filename = filename;
this.abbreviation = abbreviation;
}
protected License(final Parcel in) {
this.filename = in.readString();
this.abbreviation = in.readString();
this.name = in.readString();
}
public Uri getContentUri() {
return new Uri.Builder()
.scheme("file")
.path("/android_asset")
.appendPath(filename)
.build();
}
public String getAbbreviation() {
return abbreviation;
}
public String getFilename() {
return filename;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(this.filename);
dest.writeString(this.abbreviation);
dest.writeString(this.name);
}
public String getName() {
return name;
}
}

View file

@ -0,0 +1,19 @@
package org.schabi.newpipe.about
import android.net.Uri
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import java.io.Serializable
/**
* Class for storing information about a software license.
*/
@Parcelize
class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable {
val contentUri: Uri
get() = Uri.Builder()
.scheme("file")
.path("/android_asset")
.appendPath(filename)
.build()
}

View file

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

View file

@ -16,11 +16,10 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@ -37,14 +36,12 @@ public final class LicenseFragmentHelper {
@NonNull final License license) {
final StringBuilder licenseContent = new StringBuilder();
final String webViewData;
try {
final BufferedReader in = new BufferedReader(new InputStreamReader(
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
try (BufferedReader in = new BufferedReader(new InputStreamReader(
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8))) {
String str;
while ((str = in.readLine()) != null) {
licenseContent.append(str);
}
in.close();
// split the HTML file and insert the stylesheet into the HEAD of the file
webViewData = licenseContent.toString().replace("</head>",
@ -57,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
*/
private static String getLicenseStylesheet(@NonNull final Context context) {
@ -88,7 +85,7 @@ public final class LicenseFragmentHelper {
static Disposable showLicense(@Nullable final Context context, @NonNull final License license) {
if (context == null) {
return Disposables.empty();
return Disposable.empty();
}
return Observable.fromCallable(() -> getFormattedLicense(context, license))

View file

@ -1,83 +0,0 @@
package org.schabi.newpipe.about;
import android.os.Parcel;
import android.os.Parcelable;
public class SoftwareComponent implements Parcelable {
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
@Override
public SoftwareComponent createFromParcel(final Parcel source) {
return new SoftwareComponent(source);
}
@Override
public SoftwareComponent[] newArray(final int size) {
return new SoftwareComponent[size];
}
};
private final License license;
private final String name;
private final String years;
private final String copyrightOwner;
private final String link;
private final String version;
public SoftwareComponent(final String name, final String years, final String copyrightOwner,
final String link, final License license) {
this.name = name;
this.years = years;
this.copyrightOwner = copyrightOwner;
this.link = link;
this.license = license;
this.version = null;
}
protected SoftwareComponent(final Parcel in) {
this.name = in.readString();
this.license = in.readParcelable(License.class.getClassLoader());
this.copyrightOwner = in.readString();
this.link = in.readString();
this.years = in.readString();
this.version = in.readString();
}
public String getName() {
return name;
}
public String getYears() {
return years;
}
public String getCopyrightOwner() {
return copyrightOwner;
}
public String getLink() {
return link;
}
public String getVersion() {
return version;
}
public License getLicense() {
return license;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(name);
dest.writeParcelable(license, flags);
dest.writeString(copyrightOwner);
dest.writeString(link);
dest.writeString(years);
dest.writeString(version);
}
}

View file

@ -0,0 +1,16 @@
package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
class SoftwareComponent
@JvmOverloads
constructor(
val name: String,
val years: String,
val copyrightOwner: String,
val link: String,
val license: License,
val version: String? = null
) : Parcelable

View file

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

View file

@ -6,19 +6,20 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import java.time.OffsetDateTime
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Dao
abstract class FeedDAO {
@Query("DELETE FROM feed")
abstract fun deleteAll(): Int
@Query("""
@Query(
"""
SELECT s.* FROM streams s
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
LIMIT 500
""")
"""
)
abstract fun getAllStreams(): Flowable<List<StreamEntity>>
@Query("""
@Query(
"""
SELECT s.* FROM streams s
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
LIMIT 500
""")
"""
)
abstract fun getAllStreamsFromGroup(groupId: Long): Flowable<List<StreamEntity>>
@Query("""
@Query(
"""
DELETE FROM feed WHERE
feed.stream_id IN (
@ -60,10 +65,12 @@ abstract class FeedDAO {
WHERE s.upload_date < :offsetDateTime
)
""")
"""
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
@Query("""
@Query(
"""
DELETE FROM feed
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"
)
""")
"""
)
abstract fun unlinkOldLivestreams(subscriptionId: Long)
@Insert(onConflict = OnConflictStrategy.IGNORE)
@ -100,12 +108,14 @@ abstract class FeedDAO {
}
}
@Query("""
@Query(
"""
SELECT MIN(lu.last_updated) FROM feed_last_updated lu
INNER JOIN feed_group_subscription_join fgs
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
""")
"""
)
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
@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")
abstract fun notLoadedCount(): Flowable<Long>
@Query("""
@Query(
"""
SELECT COUNT(*) FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
@ -124,20 +135,24 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL
""")
"""
)
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
@Query("""
@Query(
"""
SELECT s.* FROM subscriptions s
LEFT JOIN feed_last_updated lu
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""")
"""
)
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
@Query("""
@Query(
"""
SELECT s.* FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
@ -147,6 +162,7 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""")
"""
)
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
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.ID;

View file

@ -10,7 +10,7 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
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.STREAM_ACCESS_DATE;
@ -20,6 +20,9 @@ import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LA
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
@ -73,6 +76,12 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_TIME
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS)
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
}

View file

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

View file

@ -5,12 +5,16 @@ import androidx.room.Embedded
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
class PlaylistStreamEntry(
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamStateEntity.STREAM_PROGRESS_TIME, defaultValue = "0")
val progressTime: Long,
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
val streamId: Long,

View file

@ -8,7 +8,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
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_TABLE;

View file

@ -9,7 +9,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
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_SERVICE_ID;

View file

@ -11,7 +11,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
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.model.PlaylistEntity.PLAYLIST_ID;
@ -24,6 +24,9 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JO
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
@ -58,6 +61,13 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
// then merge with the stream metadata
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_TIME
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS
+ " ORDER BY " + JOIN_INDEX + " ASC")
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);

View file

@ -2,23 +2,27 @@ package org.schabi.newpipe.database.stream
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.time.OffsetDateTime
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import java.time.OffsetDateTime
class StreamStatisticsEntry(
@Embedded
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
@ColumnInfo(name = STREAM_PROGRESS_TIME, defaultValue = "0")
val progressTime: Long,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = STREAM_LATEST_DATE)
@ColumnInfo(name = STREAM_LATEST_DATE)
val latestAccessDate: OffsetDateTime,
@ColumnInfo(name = STREAM_WATCH_COUNT)
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
) : LocalItem {
fun toStreamInfoItem(): StreamInfoItem {

View file

@ -6,14 +6,14 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import java.time.OffsetDateTime
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
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.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import java.time.OffsetDateTime
@Dao
abstract class StreamDAO : BasicDAO<StreamEntity> {
@ -35,10 +35,12 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
@Insert(onConflict = OnConflictStrategy.IGNORE)
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
FROM streams WHERE url = :url AND service_id = :serviceId
""")
"""
)
internal abstract fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed?
@Transaction
@ -79,7 +81,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
private fun compareAndUpdateStream(newerStream: StreamEntity) {
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
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
// (i.e. is an approximation). This is done to prevent unnecessary changes.
val hasBetterPrecision =
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) {
newerStream.uploadDate = existentMinimalStream.uploadDate
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
@ -101,7 +103,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
}
}
@Query("""
@Query(
"""
DELETE FROM streams WHERE
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
WHERE f.stream_id = streams.uid)
""")
"""
)
abstract fun deleteOrphans(): Int
/**

View file

@ -11,7 +11,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity;
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.STREAM_STATE_TABLE;

View file

@ -5,8 +5,6 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
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.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem
import java.io.Serializable
import java.time.OffsetDateTime
@Entity(tableName = STREAM_TABLE,
indices = [
Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
]
@Entity(
tableName = STREAM_TABLE,
indices = [
Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
]
)
data class StreamEntity(
@PrimaryKey(autoGenerate = true)
@ -61,27 +62,27 @@ data class StreamEntity(
) : Serializable {
@Ignore
constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
)
@Ignore
constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
)
@Ignore
constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader,
thumbnailUrl = item.thumbnailUrl
serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader,
thumbnailUrl = item.thumbnailUrl
)
fun toStreamInfoItem(): StreamInfoItem {

View file

@ -22,6 +22,9 @@ import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_
public class StreamStateEntity {
public static final String STREAM_STATE_TABLE = "stream_state";
public static final String JOIN_STREAM_ID = "stream_id";
// This additional field is required for the SQL query because 'stream_id' is used
// for some other joins already
public static final String JOIN_STREAM_ID_ALIAS = "stream_id_alias";
public static final String STREAM_PROGRESS_TIME = "progress_time";
/**

View file

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

View file

@ -10,7 +10,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@ -34,6 +33,7 @@ import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.nononsenseapps.filepicker.Utils;
@ -49,6 +49,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@ -69,7 +70,7 @@ import java.util.Locale;
import icepick.Icepick;
import icepick.State;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper;
@ -602,7 +603,7 @@ public class DownloadDialog extends DialogFragment
Collections.singletonList(e),
null,
null,
ErrorActivity.ErrorInfo
ErrorInfo
.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
);
}

View file

@ -13,7 +13,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
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.MainActivity;
@ -23,6 +23,7 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.InfoCache;
@ -33,7 +34,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@ -47,6 +49,8 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
@Nullable
private ProgressBar loadingProgressBar;
private Disposable errorDisposable;
protected View errorPanelRoot;
private Button errorButtonRetry;
private TextView errorTextView;
@ -63,6 +67,14 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
wasLoading.set(isLoading.get());
}
@Override
public void onDestroy() {
super.onDestroy();
if (errorDisposable != null) {
errorDisposable.dispose();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@ -82,7 +94,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
@Override
protected void initListeners() {
super.initListeners();
RxView.clicks(errorButtonRetry)
errorDisposable = RxView.clicks(errorButtonRetry)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(o -> onRetryButtonClicked());
@ -252,7 +264,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
}
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
ErrorActivity.ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
request == null ? "none" : request, errorId));
}
@ -265,7 +277,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
/**
* Show a SnackBar and only call
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorActivity.ErrorInfo)}
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorInfo)}
* IF we a find a valid view (otherwise the error screen appears).
*
* @param exception List of the exceptions to show
@ -291,6 +303,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
}
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
ErrorInfo.make(userAction, serviceName, request, errorId));
}
}

View file

@ -27,6 +27,7 @@ import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
@ -43,7 +44,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
private SelectedTabsPagerAdapter pagerAdapter;
private ScrollableTabLayout tabLayout;
private List<Tab> tabsList = new ArrayList<>();
private final List<Tab> tabsList = new ArrayList<>();
private TabsManager tabsManager;
private boolean hasTabsChanged = false;
@ -242,7 +243,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
if (throwable != null) {
ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo
ErrorActivity.reportError(context, throwable, null, null, ErrorInfo
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
return new BlankFragment();
}

View file

@ -14,12 +14,10 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
int pastVisibleItems = 0;
final int visibleItemCount;
final int totalItemCount;
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
final int visibleItemCount = layoutManager.getChildCount();
final int totalItemCount = layoutManager.getItemCount();
// Already covers the GridLayoutManager case
if (layoutManager instanceof LinearLayoutManager) {

View file

@ -15,11 +15,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.ViewTreeObserver;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
@ -28,6 +24,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
@ -46,7 +43,9 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.ExoPlaybackException;
@ -93,6 +92,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
@ -116,11 +116,11 @@ import java.util.concurrent.TimeUnit;
import icepick.State;
import io.noties.markwon.Markwon;
import io.noties.markwon.linkify.LinkifyPlugin;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
@ -731,7 +731,7 @@ public final class VideoDetailFragment
}
private View.OnTouchListener getOnControlsTouchListener() {
return (View view, MotionEvent motionEvent) -> {
return (view, motionEvent) -> {
if (!PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_hold_to_append_key), true)) {
return false;
@ -948,7 +948,7 @@ public final class VideoDetailFragment
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull final StreamInfo result) -> {
.subscribe(result -> {
isLoading.set(false);
hideMainPlayer();
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
@ -969,7 +969,7 @@ public final class VideoDetailFragment
openVideoPlayer();
}
}
}, (@NonNull final Throwable throwable) -> {
}, throwable -> {
isLoading.set(false);
onError(throwable);
});
@ -1140,7 +1140,7 @@ public final class VideoDetailFragment
PlayQueue queue = playQueue;
// Size can be 0 because queue removes bad stream automatically when error occurs
if (queue == null || queue.size() == 0) {
if (queue == null || queue.isEmpty()) {
queue = new SinglePlayQueue(currentInfo);
}
@ -1224,12 +1224,12 @@ public final class VideoDetailFragment
if (description.getType() == Description.HTML) {
disposables.add(Single.just(description.getContent())
.map((@NonNull final String descriptionText) ->
.map(descriptionText ->
HtmlCompat.fromHtml(descriptionText,
HtmlCompat.FROM_HTML_MODE_LEGACY))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull final Spanned spanned) -> {
.subscribe(spanned -> {
videoDescriptionView.setText(spanned);
videoDescriptionView.setVisibility(View.VISIBLE);
}));
@ -1346,19 +1346,24 @@ public final class VideoDetailFragment
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(ACTION_SHOW_MAIN_PLAYER)) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else if (intent.getAction().equals(ACTION_HIDE_MAIN_PLAYER)) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
} else if (intent.getAction().equals(ACTION_PLAYER_STARTED)) {
// If the state is not hidden we don't need to show the mini player
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
// Rebound to the service if it was closed via notification or mini player
if (!PlayerHolder.bound) {
PlayerHolder.startService(App.getApp(), false, VideoDetailFragment.this);
}
switch (intent.getAction()) {
case ACTION_SHOW_MAIN_PLAYER:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
break;
case ACTION_HIDE_MAIN_PLAYER:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
break;
case ACTION_PLAYER_STARTED:
// If the state is not hidden we don't need to show the mini player
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
// Rebound to the service if it was closed via notification or mini player
if (!PlayerHolder.bound) {
PlayerHolder.startService(
App.getApp(), false, VideoDetailFragment.this);
}
break;
}
}
};
@ -1626,7 +1631,7 @@ public final class VideoDetailFragment
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (final Exception e) {
final ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR,
ServiceList.all()
.get(currentInfo
.getServiceId())

View file

@ -48,7 +48,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N>, StateSaver.WriteRead,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected StateSaver.SavedState savedState;
protected org.schabi.newpipe.util.SavedState savedState;
private boolean useDefaultStateSaving = true;
private int updateFlags = 0;

View file

@ -16,10 +16,10 @@ import org.schabi.newpipe.views.NewPipeRecyclerView;
import java.util.Queue;
import icepick.State;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public abstract class BaseListInfoFragment<I extends ListInfo>
extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
@ -204,7 +204,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
name = result.getName();
setTitle(name);
if (infoListAdapter.getItemsList().size() == 0) {
if (infoListAdapter.getItemsList().isEmpty()) {
if (result.getRelatedItems().size() > 0) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
showListFooter(hasMoreItems());

View file

@ -24,7 +24,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
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.database.subscription.SubscriptionEntity;
@ -53,15 +53,15 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Action;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;

View file

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

View file

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

View file

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

View file

@ -49,6 +49,7 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
@ -67,13 +68,13 @@ import java.util.Queue;
import java.util.concurrent.TimeUnit;
import icepick.State;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
@ -248,7 +249,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} catch (final Exception e) {
ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(),
requireActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
ErrorInfo.make(UserAction.UI_ERROR,
"",
"", R.string.general_error));
}
@ -256,7 +257,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (!TextUtils.isEmpty(searchString)) {
if (wasLoading.getAndSet(false)) {
search(searchString, contentFilter, sortFilter);
} else if (infoListAdapter.getItemsList().size() == 0) {
} else if (infoListAdapter.getItemsList().isEmpty()) {
if (savedState == null) {
search(searchString, contentFilter, sortFilter);
} else if (!isLoading.get() && !wasSearchFocused) {
@ -708,7 +709,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
final Observable<String> observable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWith(searchString != null
.startWithItem(searchString != null
? searchString
: "")
.filter(ss -> isSuggestionsEnabled);
@ -977,7 +978,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
lastSearchedString = searchString;
nextPage = result.getNextPage();
if (infoListAdapter.getItemsList().size() == 0) {
if (infoListAdapter.getItemsList().isEmpty()) {
if (!result.getRelatedItems().isEmpty()) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
} else {

View file

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

View file

@ -371,7 +371,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
};
}
public class HFHolder extends RecyclerView.ViewHolder {
public static class HFHolder extends RecyclerView.ViewHolder {
public View view;
HFHolder(final View v) {

View file

@ -144,7 +144,8 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
}
if (item.getUploadDate() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate().date()));
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
.offsetDateTime()));
} else {
itemPublishedTime.setText(item.getTextualUploadDate());
}

View file

@ -95,7 +95,7 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
if (infoItem.getUploadDate() != null) {
String formattedRelativeTime = Localization
.relativeTime(infoItem.getUploadDate().date());
.relativeTime(infoItem.getUploadDate().offsetDateTime());
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
.getBoolean(itemBuilder.getContext()

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ import java.util.Queue;
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
private List<StreamEntity> streamEntities;
private StateSaver.SavedState savedState;
private org.schabi.newpipe.util.SavedState savedState;
protected void setInfo(final List<StreamEntity> entities) {
this.streamEntities = entities;

View file

@ -2,14 +2,11 @@ package org.schabi.newpipe.local.feed
import android.content.Context
import android.util.Log
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneOffset
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
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.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneOffset
class FeedDatabaseManager(context: 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.
*/
val FEED_OLDEST_ALLOWED_DATE: OffsetDateTime = LocalDate.now().minusWeeks(13)
.atStartOfDay().atOffset(ZoneOffset.UTC)
.atStartOfDay().atOffset(ZoneOffset.UTC)
}
fun groups() = feedGroupTable.getAll()
@ -44,9 +44,9 @@ class FeedDatabaseManager(context: Context) {
else -> feedTable.getAllStreamsFromGroup(groupId)
}
return streams.map<List<StreamInfoItem>> {
return streams.map {
val items = ArrayList<StreamInfoItem>(it.size)
it.mapTo(items) { it.toStreamInfoItem() }
it.mapTo(items) { stream -> stream.toStreamInfoItem() }
return@map items
}
}
@ -61,10 +61,10 @@ class FeedDatabaseManager(context: Context) {
}
fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: OffsetDateTime) =
feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
fun markAsOutdated(subscriptionId: Long) = feedTable
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
fun upsertAll(
subscriptionId: Long,
@ -92,8 +92,12 @@ class FeedDatabaseManager(context: Context) {
feedTable.insertAll(feedEntities)
}
feedTable.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId,
OffsetDateTime.now(ZoneOffset.UTC)))
feedTable.setLastUpdatedForSubscription(
FeedLastUpdatedEntity(
subscriptionId,
OffsetDateTime.now(ZoneOffset.UTC)
)
)
}
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
@ -113,38 +117,38 @@ class FeedDatabaseManager(context: Context) {
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
return feedGroupTable.getSubscriptionIdsFor(groupId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun updateSubscriptionsForGroup(groupId: Long, subscriptionIds: List<Long>): Completable {
return Completable.fromCallable { feedGroupTable.updateSubscriptionsForGroup(groupId, subscriptionIds) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun createGroup(name: String, icon: FeedGroupIcon): Maybe<Long> {
return Maybe.fromCallable { feedGroupTable.insert(FeedGroupEntity(0, name, icon)) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun getGroup(groupId: Long): Maybe<FeedGroupEntity> {
return feedGroupTable.getGroup(groupId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun updateGroup(feedGroupEntity: FeedGroupEntity): Completable {
return Completable.fromCallable { feedGroupTable.update(feedGroupEntity) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun deleteGroup(groupId: Long): Completable {
return Completable.fromCallable { feedGroupTable.delete(groupId) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun updateGroupsOrder(groupIdList: List<Long>): Completable {
@ -152,8 +156,8 @@ class FeedDatabaseManager(context: Context) {
val orderMap = groupIdList.associateBy({ it }, { index++ })
return Completable.fromCallable { feedGroupTable.updateOrder(orderMap) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> {

View file

@ -29,13 +29,14 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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_message_view
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
@ -53,9 +54,11 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.Localization
import java.util.Calendar
class FeedFragment : BaseListFragment<FeedState, Unit>() {
private lateinit var viewModel: FeedViewModel
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@State
@JvmField
var listState: Parcelable? = null
@ -73,7 +76,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
super.onCreate(savedInstanceState)
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
?: FeedGroupEntity.GROUP_ALL_ID
?: FeedGroupEntity.GROUP_ALL_ID
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
}
@ -83,7 +86,8 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
super.onViewCreated(rootView, savedInstanceState)
swipeRefreshLayout = requireView().findViewById(R.id.swiperefresh)
swipeRefreshLayout.setOnRefreshListener { reloadContent() }
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
}
@ -140,15 +144,15 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
AlertDialog.Builder(requireContext())
.setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
.setNeutralButton(enableDisableButtonText) { _, _ ->
sharedPreferences.edit()
.putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
.apply()
.setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
.setNeutralButton(enableDisableButtonText) { _, _ ->
sharedPreferences.edit {
putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
}
.setPositiveButton(resources.getString(R.string.finish), null)
.create()
.show()
}
.setPositiveButton(resources.getString(R.string.finish), null)
.create()
.show()
return true
}
@ -189,6 +193,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
empty_state_view?.let { animateView(it, false, 0) }
animateView(error_panel, false, 0)
swipeRefreshLayout.isRefreshing = false
}
override fun showEmptyState() {
@ -229,7 +234,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
showLoading()
val isIndeterminate = progressState.currentProgress == -1 &&
progressState.maxProgress == -1
progressState.maxProgress == -1
if (!isIndeterminate) {
loading_progress_text.text = "${progressState.currentProgress}/${progressState.maxProgress}"
@ -240,7 +245,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
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.max = progressState.maxProgress
@ -255,14 +260,17 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
oldestSubscriptionUpdate = loadedState.oldestUpdate
refresh_subtitle_text.isVisible = loadedState.notLoadedCount > 0
if (loadedState.notLoadedCount > 0) {
val loadedCount = loadedState.notLoadedCount > 0
refresh_subtitle_text.isVisible = loadedCount
if (loadedCount) {
refresh_subtitle_text.text = getString(R.string.feed_subscription_not_loaded_count, loadedState.notLoadedCount)
}
if (loadedState.itemsErrors.isNotEmpty()) {
showSnackBarError(loadedState.itemsErrors, UserAction.REQUESTED_FEED,
"none", "Loading feed", R.string.general_error)
showSnackBarError(
loadedState.itemsErrors, UserAction.REQUESTED_FEED,
"none", "Loading feed", R.string.general_error
)
}
if (loadedState.items.isEmpty()) {
@ -305,9 +313,11 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
override fun hasMoreItems() = false
private fun triggerUpdate() {
getActivity()?.startService(Intent(requireContext(), FeedLoadService::class.java).apply {
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
})
getActivity()?.startService(
Intent(requireContext(), FeedLoadService::class.java).apply {
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
}
)
listState = null
}

View file

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

View file

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

View file

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

View file

@ -31,21 +31,15 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager
import io.reactivex.Flowable
import io.reactivex.Notification
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.Consumer
import io.reactivex.functions.Function
import io.reactivex.processors.PublishProcessor
import io.reactivex.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 io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Notification
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.functions.Function
import io.reactivex.rxjava3.processors.PublishProcessor
import io.reactivex.rxjava3.schedulers.Schedulers
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
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.local.feed.FeedDatabaseManager
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.SuccessResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExceptionUtils
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() {
companion object {
@ -109,8 +108,11 @@ class FeedLoadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "]," +
" flags = [" + flags + "], startId = [" + startId + "]")
Log.d(
TAG,
"onStartCommand() called with: intent = [" + intent + "]," +
" flags = [" + flags + "], startId = [" + startId + "]"
)
}
if (intent == null || loadingSubscription != null) {
@ -123,10 +125,10 @@ class FeedLoadService : Service() {
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
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
.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()
startLoading(groupId, useFeedExtractor, thresholdOutdatedSeconds)
@ -181,63 +183,63 @@ class FeedLoadService : Service() {
}
subscriptions
.limit(1)
.take(1)
.doOnNext {
currentProgress.set(0)
maxProgress.set(it.size)
.doOnNext {
currentProgress.set(0)
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())
.doOnNext {
startForeground(NOTIFICATION_ID, notificationBuilder.build())
updateNotificationProgress(null)
broadcastProgress()
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(errorHandlingConsumer)
.observeOn(Schedulers.io())
.flatMap { Flowable.fromIterable(it) }
.takeWhile { !cancelSignal.get() }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(notificationsConsumer)
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
.filter { !cancelSignal.get() }
.observeOn(Schedulers.io())
.buffer(BUFFER_COUNT_BEFORE_INSERT)
.doOnNext(databaseConsumer)
.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)
}
}
.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)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(resultSubscriber)
}
private fun broadcastProgress() {
@ -262,7 +264,7 @@ class FeedLoadService : Service() {
override fun onComplete() {
if (maxProgress.get() == 0) {
postEvent(IdleEvent)
postEvent(FeedEventManager.Event.IdleEvent)
stopService()
return
@ -274,7 +276,8 @@ class FeedLoadService : Service() {
notificationUpdater.onNext(getString(R.string.feed_processing_message))
postEvent(ProgressEvent(R.string.feed_processing_message))
disposables.add(Single
disposables.add(
Single
.fromCallable {
feedResultsHolder.ready()
@ -293,7 +296,8 @@ class FeedLoadService : Service() {
return@subscribe
}
stopService()
})
}
)
}
}
@ -364,16 +368,18 @@ class FeedLoadService : Service() {
private var maxProgress = AtomicInteger(-1)
private fun createNotification(): NotificationCompat.Builder {
val cancelActionIntent = PendingIntent.getBroadcast(this,
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0)
val cancelActionIntent = PendingIntent.getBroadcast(
this,
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0
)
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setProgress(-1, -1, true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.addAction(0, getString(R.string.cancel), cancelActionIntent)
.setContentTitle(getString(R.string.feed_notification_loading))
.setOngoing(true)
.setProgress(-1, -1, true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.addAction(0, getString(R.string.cancel), cancelActionIntent)
.setContentTitle(getString(R.string.feed_notification_loading))
}
private fun setupNotification() {
@ -381,13 +387,15 @@ class FeedLoadService : Service() {
notificationBuilder = createNotification()
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)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNotificationProgress))
.subscribe(this::updateNotificationProgress)
)
}
private fun updateNotificationProgress(updateDescription: String?) {

View file

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

View file

@ -34,6 +34,7 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.NavigationHelper;
@ -48,9 +49,9 @@ import java.util.Comparator;
import java.util.List;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
@ -183,7 +184,7 @@ public class StatisticsPlaylistFragment
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete view history",
@ -197,7 +198,7 @@ public class StatisticsPlaylistFragment
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete search history",

View file

@ -11,7 +11,6 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -21,7 +20,6 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
@ -70,15 +68,11 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null) {
if (item.getProgressTime() > 0) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
@ -115,18 +109,14 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
}
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null && item.getStreamEntity().getDuration() > 0) {
if (item.getProgressTime() > 0 && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {

View file

@ -11,7 +11,6 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -21,7 +20,6 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/*
@ -98,15 +96,11 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null) {
if (item.getProgressTime() > 0) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
@ -146,18 +140,14 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null && item.getStreamEntity().getDuration() > 0) {
if (item.getProgressTime() > 0 && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {

View file

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

View file

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

View file

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

View file

@ -28,13 +28,7 @@ import com.xwray.groupie.Item
import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.State
import io.reactivex.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 io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
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.ShareUtils
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>() {
private lateinit var viewModel: SubscriptionViewModel
@ -208,14 +208,19 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
if (!exportFile.parentFile.canWrite() || !exportFile.parentFile.canRead()) {
Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show()
} else {
activity.startService(Intent(activity, SubscriptionsExportService::class.java)
.putExtra(KEY_FILE_PATH, exportFile.absolutePath))
activity.startService(
Intent(activity, SubscriptionsExportService::class.java)
.putExtra(KEY_FILE_PATH, exportFile.absolutePath)
)
}
} else if (requestCode == REQUEST_IMPORT_CODE) {
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_VALUE, path))
.putExtra(KEY_VALUE, path)
)
}
}
}
@ -247,9 +252,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
feedGroupsSortMenuItem = HeaderWithMenuItem(
getString(R.string.feed_groups_header_title),
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
menuItemOnClickListener = ::openReorderDialog
getString(R.string.feed_groups_header_title),
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
menuItemOnClickListener = ::openReorderDialog
)
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))
@ -260,10 +265,11 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.setHideWhenEmpty(true)
importExportItem = FeedImportExportItem(
{ onImportPreviousSelected() },
{ onImportFromServiceSelected(it) },
{ onExportSelected() },
importExportItemExpandedState ?: false)
{ onImportPreviousSelected() },
{ onImportFromServiceSelected(it) },
{ onExportSelected() },
importExportItemExpandedState ?: false
)
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
}
@ -284,8 +290,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
val commands = arrayOf(
getString(R.string.share),
getString(R.string.unsubscribe)
getString(R.string.share),
getString(R.string.unsubscribe)
)
val actions = DialogInterface.OnClickListener { _, i ->
@ -301,16 +307,18 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
bannerView.itemAdditionalDetails.visibility = View.GONE
AlertDialog.Builder(requireContext())
.setCustomTitle(bannerView)
.setItems(commands, actions)
.create()
.show()
.setCustomTitle(bannerView)
.setItems(commands, actions)
.create()
.show()
}
private fun deleteChannel(selectedItem: ChannelInfoItem) {
disposables.add(subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
})
disposables.add(
subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
}
)
}
override fun doInitialLoadLogic() = Unit
@ -332,8 +340,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
private val listenerChannelItem = object : OnClickGesture<ChannelInfoItem>() {
override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(fm,
selectedItem.serviceId, selectedItem.url, selectedItem.name)
override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(
fm,
selectedItem.serviceId, selectedItem.url, selectedItem.name
)
override fun held(selectedItem: ChannelInfoItem) = showLongTapDialog(selectedItem)
}
@ -420,14 +430,16 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private fun shouldUseGridLayout(): Boolean {
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) {
getString(R.string.list_view_mode_auto_key) -> {
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
else -> false

View file

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

View file

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

View file

@ -27,6 +27,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@ -84,7 +85,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
setupServiceVariables();
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorActivity.reportError(activity, Collections.emptyList(), null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE,
ErrorInfo.make(UserAction.SOMETHING_ELSE,
NewPipe.getNameOfService(currentServiceId),
"Service don't support importing", R.string.general_error));
activity.finish();

View file

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

View file

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

View file

@ -16,7 +16,6 @@ import com.xwray.groupie.TouchCallback
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
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.feed_groups_list
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.item.FeedGroupReorderItem
import org.schabi.newpipe.util.ThemeHelper
import java.util.Collections
class FeedGroupReorderDialog : DialogFragment() {
private lateinit var viewModel: FeedGroupReorderDialogViewModel
@ -51,12 +51,15 @@ class FeedGroupReorderDialog : DialogFragment() {
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
viewModel.dialogEventLiveData.observe(
viewLifecycleOwner,
Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
}
})
)
feed_groups_list.layoutManager = LinearLayoutManager(requireContext())
feed_groups_list.adapter = groupAdapter

View file

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

View file

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

View file

@ -20,7 +20,7 @@ data class FeedGroupReorderItem(
val dragCallback: ItemTouchHelper
) : Item() {
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 {
return when (groupId) {

View file

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

View file

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

View file

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

View file

@ -37,6 +37,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExceptionUtils;
@ -45,11 +46,11 @@ import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Function;
import io.reactivex.processors.PublishProcessor;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.processors.PublishProcessor;
public abstract class BaseImportExportService extends Service {
protected final String TAG = this.getClass().getSimpleName();
@ -119,7 +120,7 @@ public abstract class BaseImportExportService extends Service {
startForeground(getNotificationId(), notificationBuilder.build());
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));
disposables.add(notificationUpdater
@ -153,7 +154,7 @@ public abstract class BaseImportExportService extends Service {
protected void stopAndReportError(@Nullable final Throwable error, final String request) {
stopService();
final ErrorActivity.ErrorInfo errorInfo = ErrorActivity.ErrorInfo
final ErrorInfo errorInfo = ErrorInfo
.make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error);
ErrorActivity.reportError(this, error != null ? Collections.singletonList(error)
: Collections.emptyList(), null, null, errorInfo);

View file

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

View file

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

View file

@ -27,13 +27,13 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
@ -83,17 +83,16 @@ import org.schabi.newpipe.util.VideoSegment;
import java.io.IOException;
import java.util.Set;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
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_PERIOD_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
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;
/**
@ -846,8 +845,9 @@ public abstract class BasePlayer implements
}
private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS, mainThread())
.observeOn(mainThread())
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> triggerProgressUpdate(),
error -> Log.e(TAG, "Progress update failure: ", error));
}
@ -1512,7 +1512,7 @@ public abstract class BasePlayer implements
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
final Disposable stateSaver = recordManager.saveStreamState(info, progress)
.observeOn(mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
if (DEBUG) {
e.printStackTrace();
@ -1532,7 +1532,7 @@ public abstract class BasePlayer implements
if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
final Disposable stateSaver = queueItem.getStream()
.flatMapCompletable(info -> recordManager.saveStreamState(info, 0))
.observeOn(mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
if (DEBUG) {
e.printStackTrace();
@ -1738,8 +1738,7 @@ public abstract class BasePlayer implements
if (simpleExoPlayer == null) {
return PlaybackParameters.DEFAULT;
}
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
return simpleExoPlayer.getPlaybackParameters();
}
/**

View file

@ -48,7 +48,7 @@ public final class NotificationUtil {
@Nullable private static NotificationUtil instance = null;
@NotificationConstants.Action
private int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone();
private final int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone();
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder notificationBuilder;
@ -146,7 +146,11 @@ public final class NotificationUtil {
notificationBuilder.setContentText(player.getUploaderName());
notificationBuilder.setTicker(player.getVideoTitle());
updateActions(notificationBuilder, player);
setLargeIcon(notificationBuilder, player);
final boolean showThumbnail = player.sharedPreferences.getBoolean(
player.context.getString(R.string.show_thumbnail_key), true);
if (showThumbnail) {
setLargeIcon(notificationBuilder, player);
}
}

View file

@ -342,7 +342,7 @@ public class VideoPlayerImpl extends VideoPlayer
view.setFixedTextSize(TypedValue.COMPLEX_UNIT_PX,
(float) minimumLength / captionRatioInverse);
}
view.setApplyEmbeddedStyles(captionStyle.equals(CaptionStyleCompat.DEFAULT));
view.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT);
view.setStyle(captionStyle);
}
@ -715,9 +715,13 @@ public class VideoPlayerImpl extends VideoPlayer
super.onUpdateProgress(currentProgress, duration, bufferPercent);
updateProgress(currentProgress, duration, bufferPercent);
final boolean showThumbnail =
sharedPreferences.getBoolean(
context.getString(R.string.show_thumbnail_key),
true);
// setMetadata only updates the metadata when any of the metadata keys are null
mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(), getThumbnail(),
duration);
mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(),
showThumbnail ? getThumbnail() : null, duration);
}
@Override

View file

@ -15,6 +15,7 @@ import org.schabi.newpipe.util.AnimationUtils
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
import kotlin.math.min
/**
* Base gesture handling for [VideoPlayerImpl]
@ -117,22 +118,30 @@ abstract class BasePlayerGestureListener(
initSecPointerX = event.getX(1)
initSecPointerY = event.getY(1)
// record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble())
initPointerDistance = hypot(
initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble()
)
isResizing = true
}
if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
Log.d(
TAG,
"onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" +
"[${event.rawX}, ${event.rawY}]"
)
}
return handleMultiDrag(event)
}
if (event.action == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
Log.d(
TAG,
"onTouch() ACTION_UP > v = [$v], e1.getRaw =" +
" [${event.rawX}, ${event.rawY}]"
)
}
if (isMovingInPopup) {
isMovingInPopup = false
@ -162,18 +171,24 @@ abstract class BasePlayerGestureListener(
private fun handleMultiDrag(event: MotionEvent): Boolean {
if (initPointerDistance != -1.0 && event.pointerCount == 2) {
// get the movements of the fingers
val firstPointerMove = hypot(event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble())
val secPointerMove = hypot(event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble())
val firstPointerMove = hypot(
event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble()
)
val secPointerMove = hypot(
event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble()
)
// minimum threshold beyond which pinch gesture will work
val minimumMove = ViewConfiguration.get(service).scaledTouchSlop
if (max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
val currentPointerDistance = hypot(event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble())
val currentPointerDistance = hypot(
event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble()
)
val popupWidth = playerImpl.popupWidth.toDouble()
// change co-ordinates of popup so the center stays at the same position
@ -185,8 +200,9 @@ abstract class BasePlayerGestureListener(
playerImpl.updateScreenSize()
playerImpl.updatePopupSize(
Math.min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
-1)
min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
-1
)
return true
}
}
@ -315,22 +331,30 @@ abstract class BasePlayerGestureListener(
}
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
val isTouchingNavigationBar: Boolean = (initialEvent.y
> playerImpl.rootView.height - getNavigationBarHeight(service))
val isTouchingNavigationBar: Boolean =
initialEvent.y > (playerImpl.rootView.height - getNavigationBarHeight(service))
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false
}
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
if (!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
playerImpl.currentState == BasePlayer.STATE_COMPLETED) {
if (
!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
playerImpl.currentState == BasePlayer.STATE_COMPLETED
) {
return false
}
isMovingInMain = true
onScroll(MainPlayer.PlayerType.VIDEO, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
onScroll(
MainPlayer.PlayerType.VIDEO,
getDisplayHalfPortion(initialEvent),
initialEvent,
movingEvent,
distanceX,
distanceY
)
return true
}
@ -372,8 +396,14 @@ abstract class BasePlayerGestureListener(
playerImpl.popupLayoutParams.x = posX.toInt()
playerImpl.popupLayoutParams.y = posY.toInt()
onScroll(MainPlayer.PlayerType.POPUP, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
onScroll(
MainPlayer.PlayerType.POPUP,
getDisplayHalfPortion(initialEvent),
initialEvent,
movingEvent,
distanceX,
distanceY
)
playerImpl.windowManager
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)

View file

@ -36,15 +36,10 @@ public class PlayerGestureListener
private static final String TAG = ".PlayerGestureListener";
private static final boolean DEBUG = BasePlayer.DEBUG;
private final boolean isVolumeGestureEnabled;
private final boolean isBrightnessGestureEnabled;
private final int maxVolume;
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
super(playerImpl, service);
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
maxVolume = playerImpl.getAudioReactor().getMaxVolume();
}
@ -110,10 +105,20 @@ public class PlayerGestureListener
+ portion + "]");
}
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (portion == DisplayPortion.LEFT_HALF) {
onScrollMainBrightness(distanceX, distanceY);
final boolean isBrightnessGestureEnabled =
PlayerHelper.isBrightnessGestureEnabled(service);
final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
} else /* DisplayPortion.RIGHT_HALF */ {
if (isBrightnessGestureEnabled && isVolumeGestureEnabled) {
if (portion == DisplayPortion.LEFT_HALF) {
onScrollMainBrightness(distanceX, distanceY);
} else /* DisplayPortion.RIGHT_HALF */ {
onScrollMainVolume(distanceX, distanceY);
}
} else if (isBrightnessGestureEnabled) {
onScrollMainBrightness(distanceX, distanceY);
} else if (isVolumeGestureEnabled) {
onScrollMainVolume(distanceX, distanceY);
}
@ -132,75 +137,71 @@ public class PlayerGestureListener
}
private void onScrollMainVolume(final float distanceX, final float distanceY) {
if (isVolumeGestureEnabled) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp)
);
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp)
);
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
}
private void onScrollMainBrightness(final float distanceX, final float distanceY) {
if (isBrightnessGestureEnabled) {
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return;
}
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return;
}
final Window window = parent.getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
final ProgressBar bar = playerImpl.getBrightnessProgressBar();
final float oldBrightness = layoutParams.screenBrightness;
bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness))));
bar.incrementProgressBy((int) distanceY);
final Window window = parent.getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
final ProgressBar bar = playerImpl.getBrightnessProgressBar();
final float oldBrightness = layoutParams.screenBrightness;
bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness))));
bar.incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) bar.getProgress() / bar.getMax();
layoutParams.screenBrightness = currentProgressPercent;
window.setAttributes(layoutParams);
final float currentProgressPercent = (float) bar.getProgress() / bar.getMax();
layoutParams.screenBrightness = currentProgressPercent;
window.setAttributes(layoutParams);
// Save current brightness level
PlayerHelper.setScreenBrightness(parent, currentProgressPercent);
// Save current brightness level
PlayerHelper.setScreenBrightness(parent, currentProgressPercent);
if (DEBUG) {
Log.d(TAG, "onScroll().brightnessControl, "
+ "currentBrightness = " + currentProgressPercent);
}
if (DEBUG) {
Log.d(TAG, "onScroll().brightnessControl, "
+ "currentBrightness = " + currentProgressPercent);
}
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp)
);
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp)
);
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
}
}

View file

@ -30,14 +30,14 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.internal.subscriptions.EmptySubscription;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.internal.subscriptions.EmptySubscription;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
@ -123,7 +123,7 @@ public class MediaSourceManager {
@NonNull
private ManagedMediaSourcePlaylist playlist;
private Handler removeMediaSourceHandler = new Handler();
private final Handler removeMediaSourceHandler = new Handler();
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) {

View file

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

View file

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

View file

@ -21,10 +21,10 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
/**
* 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)
.observeOn(AndroidSchedulers.mainThread())
.startWith(new InitEvent());
.startWithItem(new InitEvent());
}
/**

View file

@ -20,8 +20,8 @@ import org.schabi.newpipe.util.FallbackViewHolder;
import java.util.List;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Created by Christian Schabesberger on 01.08.16.
@ -215,7 +215,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
}
public class HFHolder extends RecyclerView.ViewHolder {
public static class HFHolder extends RecyclerView.ViewHolder {
public View view;
public HFHolder(final View v) {

View file

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

View file

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

View file

@ -33,7 +33,7 @@ public class AcraReportSender implements ReportSender {
@Override
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
ErrorActivity.reportError(context, report,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none",
ErrorInfo.make(UserAction.UI_ERROR, "none",
"App crash, UI failure", R.string.app_ui_crash));
}
}

Some files were not shown because too many files have changed in this diff Show more