Improve code style to be more consistent

This commit is contained in:
wb9688 2020-03-31 19:20:15 +02:00
parent 819e52cab3
commit fda5405e48
244 changed files with 10116 additions and 7222 deletions

View File

@ -30,8 +30,9 @@ class AppDatabaseTest {
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc" private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
} }
@get:Rule val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), @get:Rule
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()); val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory())
@Test @Test
fun migrateDatabaseFrom2to3() { fun migrateDatabaseFrom2to3() {
@ -72,7 +73,7 @@ class AppDatabaseTest {
} }
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3, testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3); true, Migrations.MIGRATION_2_3)
val migratedDatabaseV3 = getMigratedDatabase() val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst() val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()

View File

@ -1,8 +1,9 @@
package org.schabi.newpipe.report; package org.schabi.newpipe.report;
import android.os.Parcel; import android.os.Parcel;
import androidx.test.filters.LargeTest;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -12,15 +13,16 @@ import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
* Instrumented tests for {@link ErrorInfo} * Instrumented tests for {@link ErrorInfo}.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@LargeTest @LargeTest
public class ErrorInfoTest { public class ErrorInfoTest {
@Test @Test
public void errorInfo_testParcelable() { public void errorInfoTestParcelable() {
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", R.string.general_error); ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
R.string.general_error);
// Obtain a Parcel object and write the parcelable object to it: // Obtain a Parcel object and write the parcelable object to it:
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
info.writeToParcel(parcel, 0); info.writeToParcel(parcel, 0);

View File

@ -3,6 +3,7 @@ package org.schabi.newpipe;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.multidex.MultiDex; import androidx.multidex.MultiDex;
@ -26,7 +27,7 @@ public class DebugApp extends App {
private static final String TAG = DebugApp.class.toString(); private static final String TAG = DebugApp.class.toString();
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(final Context base) {
super.attachBaseContext(base); super.attachBaseContext(base);
MultiDex.install(this); MultiDex.install(this);
} }

View File

@ -38,12 +38,15 @@ import java.util.ArrayList;
* This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}. * This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}.
* <p> * <p>
* It includes a workaround to fix the menu visibility when the adapter is restored. * It includes a workaround to fix the menu visibility when the adapter is restored.
* </p>
* <p> * <p>
* When restoring the state of this adapter, all the fragments' menu visibility were set to false, * When restoring the state of this adapter, all the fragments' menu visibility were set to false,
* effectively disabling the menu from the user until he switched pages or another event that triggered the * effectively disabling the menu from the user until he switched pages or another event
* menu to be visible again happened. * that triggered the menu to be visible again happened.
* </p>
* <p> * <p>
* <br><b>Check out the changes in:</b> * <b>Check out the changes in:</b>
* </p>
* <ul> * <ul>
* <li>{@link #saveState()}</li> * <li>{@link #saveState()}</li>
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li> * <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
@ -88,8 +91,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
private Fragment mCurrentPrimaryItem = null; private Fragment mCurrentPrimaryItem = null;
/** /**
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround} that sets the fragment manager for the * Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
* adapter. This is the equivalent of calling * that sets the fragment manager for the adapter. This is the equivalent of calling
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in * {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}. * {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
* *
@ -101,7 +104,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
*/ */
@Deprecated @Deprecated
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm) { public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT); this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
} }
@ -117,20 +120,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
* @param fm fragment manager that will interact with this adapter * @param fm fragment manager that will interact with this adapter
* @param behavior determines if only current fragments are in a resumed state * @param behavior determines if only current fragments are in a resumed state
*/ */
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm, public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm,
@Behavior int behavior) { @Behavior final int behavior) {
mFragmentManager = fm; mFragmentManager = fm;
mBehavior = behavior; mBehavior = behavior;
} }
/** /**
* Return the Fragment associated with a specified position. * @param position the position of the item you want
* @return the {@link Fragment} associated with a specified position
*/ */
@NonNull @NonNull
public abstract Fragment getItem(int position); public abstract Fragment getItem(int position);
@Override @Override
public void startUpdate(@NonNull ViewGroup container) { public void startUpdate(@NonNull final ViewGroup container) {
if (container.getId() == View.NO_ID) { if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id"); + " requires a view id");
@ -140,7 +144,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@NonNull @NonNull
@Override @Override
public Object instantiateItem(@NonNull ViewGroup container, int position) { public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
// If we already have this item instantiated, there is nothing // If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager // to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already // from its saved state, where the fragment manager has already
@ -157,7 +161,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
} }
Fragment fragment = getItem(position); Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); if (DEBUG) {
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
}
if (mSavedState.size() > position) { if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position); Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) { if (fss != null) {
@ -183,14 +189,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
} }
@Override @Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { public void destroyItem(@NonNull final ViewGroup container, final int position,
@NonNull final Object object) {
Fragment fragment = (Fragment) object; Fragment fragment = (Fragment) object;
if (mCurTransaction == null) { if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction(); mCurTransaction = mFragmentManager.beginTransaction();
} }
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object if (DEBUG) {
+ " v=" + ((Fragment)object).getView()); Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment) object).getView());
}
while (mSavedState.size() <= position) { while (mSavedState.size() <= position) {
mSavedState.add(null); mSavedState.add(null);
} }
@ -206,8 +215,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
@Override @Override
@SuppressWarnings({"ReferenceEquality", "deprecation"}) @SuppressWarnings({"ReferenceEquality", "deprecation"})
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
Fragment fragment = (Fragment)object; @NonNull final Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) { if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) { if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false); mCurrentPrimaryItem.setMenuVisibility(false);
@ -235,7 +245,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
} }
@Override @Override
public void finishUpdate(@NonNull ViewGroup container) { public void finishUpdate(@NonNull final ViewGroup container) {
if (mCurTransaction != null) { if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss(); mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null; mCurTransaction = null;
@ -243,12 +253,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
} }
@Override @Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
return ((Fragment)object).getView() == view; return ((Fragment) object).getView() == view;
} }
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
private final String SELECTED_FRAGMENT = "selected_fragment"; private final String selectedFragment = "selected_fragment";
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@Override @Override
@ -261,7 +271,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
mSavedState.toArray(fss); mSavedState.toArray(fss);
state.putParcelableArray("states", fss); state.putParcelableArray("states", fss);
} }
for (int i=0; i<mFragments.size(); i++) { for (int i = 0; i < mFragments.size(); i++) {
Fragment f = mFragments.get(i); Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) { if (f != null && f.isAdded()) {
if (state == null) { if (state == null) {
@ -273,7 +283,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Check if it's the same fragment instance // Check if it's the same fragment instance
if (f == mCurrentPrimaryItem) { if (f == mCurrentPrimaryItem) {
state.putString(SELECTED_FRAGMENT, key); state.putString(selectedFragment, key);
} }
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
} }
@ -282,16 +292,16 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
} }
@Override @Override
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) { public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
if (state != null) { if (state != null) {
Bundle bundle = (Bundle)state; Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader); bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states"); Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear(); mSavedState.clear();
mFragments.clear(); mFragments.clear();
if (fss != null) { if (fss != null) {
for (int i=0; i<fss.length; i++) { for (int i = 0; i < fss.length; i++) {
mSavedState.add((Fragment.SavedState)fss[i]); mSavedState.add((Fragment.SavedState) fss[i]);
} }
} }
Iterable<String> keys = bundle.keySet(); Iterable<String> keys = bundle.keySet();
@ -304,7 +314,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
mFragments.add(null); mFragments.add(null);
} }
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
final boolean wasSelected = bundle.getString(SELECTED_FRAGMENT, "").equals(key); final boolean wasSelected = bundle.getString(selectedFragment, "")
.equals(key);
f.setMenuVisibility(wasSelected); f.setMenuVisibility(wasSelected);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
mFragments.set(index, f); mFragments.set(index, f);

View File

@ -10,15 +10,15 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
import java.lang.reflect.Field; import java.lang.reflect.Field;
// check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489 // See https://stackoverflow.com/questions/56849221#57997489
public final class FlingBehavior extends AppBarLayout.Behavior { public final class FlingBehavior extends AppBarLayout.Behavior {
public FlingBehavior(final Context context, final AttributeSet attrs) {
public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
@Override @Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) { public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
final MotionEvent ev) {
switch (ev.getActionMasked()) { switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
// remove reference to old nested scrolling child // remove reference to old nested scrolling child
@ -35,7 +35,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
@Nullable @Nullable
private OverScroller getScrollerField() { private OverScroller getScrollerField() {
try { try {
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass(); Class<?> headerBehaviorType = this.getClass()
.getSuperclass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) { if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("scroller"); Field field = headerBehaviorType.getDeclaredField("scroller");
field.setAccessible(true); field.setAccessible(true);
@ -62,12 +63,14 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
return null; return null;
} }
private void resetNestedScrollingChild(){ private void resetNestedScrollingChild() {
Field field = getLastNestedScrollingChildRefField(); Field field = getLastNestedScrollingChildRefField();
if(field != null){ if (field != null) {
try { try {
Object value = field.get(this); Object value = field.get(this);
if(value != null) field.set(this, null); if (value != null) {
field.set(this, null);
}
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
// ? // ?
} }
@ -76,7 +79,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
private void stopAppBarLayoutFling() { private void stopAppBarLayoutFling() {
OverScroller scroller = getScrollerField(); OverScroller scroller = getScrollerField();
if (scroller != null) scroller.forceFinished(true); if (scroller != null) {
scroller.forceFinished(true);
}
} }
} }

View File

@ -23,17 +23,25 @@ package org.schabi.newpipe;
/** /**
* Singleton: * Singleton:
* Used to send data between certain Activity/Services within the same process. * Used to send data between certain Activity/Services within the same process.
* This can be considered as an ugly hack inside the Android universe. **/ * This can be considered as an ugly hack inside the Android universe.
**/
public class ActivityCommunicator { public class ActivityCommunicator {
private static ActivityCommunicator activityCommunicator; private static ActivityCommunicator activityCommunicator;
private volatile Class returnActivity;
public static ActivityCommunicator getCommunicator() { public static ActivityCommunicator getCommunicator() {
if(activityCommunicator == null) { if (activityCommunicator == null) {
activityCommunicator = new ActivityCommunicator(); activityCommunicator = new ActivityCommunicator();
} }
return activityCommunicator; return activityCommunicator;
} }
public volatile Class returnActivity; public Class getReturnActivity() {
return returnActivity;
}
public void setReturnActivity(final Class returnActivity) {
this.returnActivity = returnActivity;
}
} }

View File

@ -66,15 +66,24 @@ import io.reactivex.plugins.RxJavaPlugins;
public class App extends Application { public class App extends Application {
protected static final String TAG = App.class.toString(); protected static final String TAG = App.class.toString();
private RefWatcher refWatcher;
private static App app;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static final Class<? extends ReportSenderFactory>[] private static final Class<? extends ReportSenderFactory>[]
reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class}; REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
private static App app;
private RefWatcher refWatcher;
@Nullable
public static RefWatcher getRefWatcher(final Context context) {
final App application = (App) context.getApplicationContext();
return application.refWatcher;
}
public static App getApp() {
return app;
}
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(final Context base) {
super.attachBaseContext(base); super.attachBaseContext(base);
initACRA(); initACRA();
@ -123,24 +132,30 @@ public class App extends Application {
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling // https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
@Override @Override
public void accept(@NonNull Throwable throwable) { public void accept(@NonNull final Throwable throwable) {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " + Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
"throwable = [" + throwable.getClass().getName() + "]"); + "throwable = [" + throwable.getClass().getName() + "]");
final Throwable actualThrowable;
if (throwable instanceof UndeliverableException) { if (throwable instanceof UndeliverableException) {
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception // As UndeliverableException is a wrapper,
throwable = throwable.getCause(); // get the cause of it to get the "real" exception
actualThrowable = throwable.getCause();
} else {
actualThrowable = throwable;
} }
final List<Throwable> errors; final List<Throwable> errors;
if (throwable instanceof CompositeException) { if (actualThrowable instanceof CompositeException) {
errors = ((CompositeException) throwable).getExceptions(); errors = ((CompositeException) actualThrowable).getExceptions();
} else { } else {
errors = Collections.singletonList(throwable); errors = Collections.singletonList(actualThrowable);
} }
for (final Throwable error : errors) { for (final Throwable error : errors) {
if (isThrowableIgnored(error)) return; if (isThrowableIgnored(error)) {
return;
}
if (isThrowableCritical(error)) { if (isThrowableCritical(error)) {
reportException(error); reportException(error);
return; return;
@ -150,17 +165,19 @@ public class App extends Application {
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so, // Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
// When exception is not reported, log it // When exception is not reported, log it
if (isDisposedRxExceptionsReported()) { if (isDisposedRxExceptionsReported()) {
reportException(throwable); reportException(actualThrowable);
} else { } else {
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable); Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
} }
} }
private boolean isThrowableIgnored(@NonNull final Throwable throwable) { private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
// Don't crash the application over a simple network problem // Don't crash the application over a simple network problem
return ExtractorHelper.hasAssignableCauseThrowable(throwable, return ExtractorHelper.hasAssignableCauseThrowable(throwable,
IOException.class, SocketException.class, // network api cancellation // network api cancellation
InterruptedException.class, InterruptedIOException.class); // blocking code disposed IOException.class, SocketException.class,
// blocking code disposed
InterruptedException.class, InterruptedIOException.class);
} }
private boolean isThrowableCritical(@NonNull final Throwable throwable) { private boolean isThrowableCritical(@NonNull final Throwable throwable) {
@ -191,7 +208,7 @@ public class App extends Application {
private void initACRA() { private void initACRA() {
try { try {
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this) final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
.setReportSenderFactoryClasses(reportSenderFactoryClasses) .setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class) .setBuildConfigClass(BuildConfig.class)
.build(); .build();
ACRA.init(this, acraConfig); ACRA.init(this, acraConfig);
@ -202,7 +219,7 @@ public class App extends Application {
null, null,
null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not initialize ACRA crash report", R.string.app_ui_crash)); "Could not initialize ACRA crash report", R.string.app_ui_crash));
} }
} }
@ -230,11 +247,11 @@ public class App extends Application {
/** /**
* Set up notification channel for app update. * Set up notification channel for app update.
*
* @param importance * @param importance
*/ */
@TargetApi(Build.VERSION_CODES.O) @TargetApi(Build.VERSION_CODES.O)
private void setUpUpdateNotificationChannel(int importance) { private void setUpUpdateNotificationChannel(final int importance) {
final String appUpdateId final String appUpdateId
= getString(R.string.app_update_notification_channel_id); = getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName final CharSequence appUpdateName
@ -251,12 +268,6 @@ public class App extends Application {
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel); appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
} }
@Nullable
public static RefWatcher getRefWatcher(Context context) {
final App application = (App) context.getApplicationContext();
return application.refWatcher;
}
protected RefWatcher installLeakCanary() { protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED; return RefWatcher.DISABLED;
} }
@ -264,8 +275,4 @@ public class App extends Application {
protected boolean isDisposedRxExceptionsReported() { protected boolean isDisposedRxExceptionsReported() {
return false; return false;
} }
public static App getApp() {
return app;
}
} }

View File

@ -2,13 +2,14 @@ package org.schabi.newpipe;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.squareup.leakcanary.RefWatcher; import com.squareup.leakcanary.RefWatcher;
@ -16,18 +17,16 @@ import icepick.Icepick;
import icepick.State; import icepick.State;
public abstract class BaseFragment extends Fragment { public abstract class BaseFragment extends Fragment {
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
protected final boolean DEBUG = MainActivity.DEBUG; protected final boolean DEBUG = MainActivity.DEBUG;
protected AppCompatActivity activity; protected AppCompatActivity activity;
public static final ImageLoader imageLoader = ImageLoader.getInstance(); //These values are used for controlling fragments when they are part of the frontpage
//These values are used for controlling framgents when they are part of the frontpage
@State @State
protected boolean useAsFrontPage = false; protected boolean useAsFrontPage = false;
protected boolean mIsVisibleToUser = false; private boolean mIsVisibleToUser = false;
public void useAsFrontPage(boolean value) { public void useAsFrontPage(final boolean value) {
useAsFrontPage = value; useAsFrontPage = value;
} }
@ -36,7 +35,7 @@ public abstract class BaseFragment extends Fragment {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onAttach(Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
activity = (AppCompatActivity) context; activity = (AppCompatActivity) context;
} }
@ -48,43 +47,51 @@ public abstract class BaseFragment extends Fragment {
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); if (DEBUG) {
Log.d(TAG, "onCreate() called with: "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState); Icepick.restoreInstanceState(this, savedInstanceState);
if (savedInstanceState != null) onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null) {
onRestoreInstanceState(savedInstanceState);
}
} }
@Override @Override
public void onViewCreated(View rootView, Bundle savedInstanceState) { public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState); super.onViewCreated(rootView, savedInstanceState);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]"); Log.d(TAG, "onViewCreated() called with: "
+ "rootView = [" + rootView + "], "
+ "savedInstanceState = [" + savedInstanceState + "]");
} }
initViews(rootView, savedInstanceState); initViews(rootView, savedInstanceState);
initListeners(); initListeners();
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState); Icepick.saveInstanceState(this, outState);
} }
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity()); RefWatcher refWatcher = App.getRefWatcher(getActivity());
if (refWatcher != null) refWatcher.watch(this); if (refWatcher != null) {
refWatcher.watch(this);
}
} }
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser; mIsVisibleToUser = isVisibleToUser;
} }
@ -93,20 +100,20 @@ public abstract class BaseFragment extends Fragment {
// Init // Init
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) { }
}
protected void initListeners() { protected void initListeners() { }
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void setTitle(String title) { public void setTitle(final String title) {
if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]"); if (DEBUG) {
if((!useAsFrontPage || mIsVisibleToUser) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
&& (activity != null && activity.getSupportActionBar() != null)) { }
if ((!useAsFrontPage || mIsVisibleToUser)
&& (activity != null && activity.getSupportActionBar() != null)) {
activity.getSupportActionBar().setDisplayShowTitleEnabled(true); activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
activity.getSupportActionBar().setTitle(title); activity.getSupportActionBar().setTitle(title);
} }

View File

@ -12,9 +12,10 @@ import android.net.ConnectivityManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import android.util.Log;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -42,62 +43,137 @@ import okhttp3.Response;
* the notification, the user will be directed to the download link. * the notification, the user will be directed to the download link.
*/ */
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> { public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
private static final boolean DEBUG = MainActivity.DEBUG; private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName(); private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
private static final Application app = App.getApp(); private static final Application APP = App.getApp();
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15"; private static final String GITHUB_APK_SHA1
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json"; = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final int timeoutPeriod = 30; private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
private static final int TIMEOUT_PERIOD = 30;
private SharedPreferences mPrefs; private SharedPreferences mPrefs;
private OkHttpClient client; private OkHttpClient client;
/**
* Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
*
* @return String with the apk's SHA1 fingeprint in hexadecimal
*/
private static String getCertificateSHA1Fingerprint() {
PackageManager pm = APP.getPackageManager();
String packageName = APP.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException ex) {
ErrorActivity.reportError(APP, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
}
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
X509Certificate c = null;
try {
cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException ex) {
ErrorActivity.reportError(APP, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
}
String hexString = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException ex1) {
ErrorActivity.reportError(APP, ex1, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
} catch (CertificateEncodingException ex2) {
ErrorActivity.reportError(APP, ex2, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
}
return hexString;
}
private static String byte2HexFormatted(final byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) {
h = "0" + h;
}
if (l > 2) {
h = h.substring(l - 2, l);
}
str.append(h.toUpperCase());
if (i < (arr.length - 1)) {
str.append(':');
}
}
return str.toString();
}
public static boolean isGithubApk() {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(APP);
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
// Check if user has enabled/ disabled update checking // Check if user has enabled/ disabled update checking
// and if the current apk is a github one or not. // and if the current apk is a github one or not.
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true) if (!mPrefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
|| !isGithubApk()) {
this.cancel(true); this.cancel(true);
} }
} }
@Override @Override
protected String doInBackground(Void... voids) { protected String doInBackground(final Void... voids) {
if (isCancelled() || !isConnected()) {
if(isCancelled() || !isConnected()) return null; return null;
// Make a network request to get latest NewPipe data.
if (client == null) {
client = new OkHttpClient
.Builder()
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
.build();
} }
Request request = new Request.Builder() // Make a network request to get latest NewPipe data.
.url(newPipeApiUrl) // FIXME: Use DownloaderImp
.build(); if (client == null) {
client = new OkHttpClient.Builder()
.readTimeout(TIMEOUT_PERIOD, TimeUnit.SECONDS).build();
}
Request request = new Request.Builder().url(NEWPIPE_API_URL).build();
try { try {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
return response.body().string(); return response.body().string();
} catch (IOException ex) { } catch (IOException ex) {
// connectivity problems, do not alarm user and fail silently // connectivity problems, do not alarm user and fail silently
if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex)); if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(ex));
}
} }
return null; return null;
} }
@Override @Override
protected void onPostExecute(String response) { protected void onPostExecute(final String response) {
// Parse the json from the response. // Parse the json from the response.
if (response != null) { if (response != null) {
@ -115,7 +191,9 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
} catch (JSONException ex) { } catch (JSONException ex) {
// connectivity problems, do not alarm user and fail silently // connectivity problems, do not alarm user and fail silently
if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex)); if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(ex));
}
} }
} }
} }
@ -123,116 +201,42 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
/** /**
* Method to compare the current and latest available app version. * Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification. * If a newer version is available, we show the update notification.
* @param versionName *
* @param apkLocationUrl * @param versionName Name of new version
* @param apkLocationUrl Url with the new apk
* @param versionCode V
*/ */
private void compareAppVersionAndShowNotification(String versionName, private void compareAppVersionAndShowNotification(final String versionName,
String apkLocationUrl, final String apkLocationUrl,
String versionCode) { final String versionCode) {
int notificationId = 2000;
int NOTIFICATION_ID = 2000;
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) { if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
// A pending intent to open the apk location url in the browser. // A pending intent to open the apk location url in the browser.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl)); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
PendingIntent pendingIntent PendingIntent pendingIntent
= PendingIntent.getActivity(app, 0, intent, 0); = PendingIntent.getActivity(APP, 0, intent, 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat NotificationCompat.Builder notificationBuilder = new NotificationCompat
.Builder(app, app.getString(R.string.app_update_notification_channel_id)) .Builder(APP, APP.getString(R.string.app_update_notification_channel_id))
.setSmallIcon(R.drawable.ic_newpipe_update) .setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setAutoCancel(true) .setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title)) .setContentTitle(APP.getString(R.string.app_update_notification_content_title))
.setContentText(app.getString(R.string.app_update_notification_content_text) .setContentText(APP.getString(R.string.app_update_notification_content_text)
+ " " + versionName); + " " + versionName);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(APP);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); notificationManager.notify(notificationId, notificationBuilder.build());
} }
} }
/**
* Method to get the apk's SHA1 key.
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
*/
private static String getCertificateSHA1Fingerprint() {
PackageManager pm = app.getPackageManager();
String packageName = app.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
}
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
X509Certificate c = null;
try {
cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
}
String hexString = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException ex1) {
ErrorActivity.reportError(app, ex1, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
} catch (CertificateEncodingException ex2) {
ErrorActivity.reportError(app, ex2, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
}
return hexString;
}
private static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}
public static boolean isGithubApk() {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
private boolean isConnected() { private boolean isConnected() {
ConnectivityManager cm = ConnectivityManager cm =
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected(); && cm.getActiveNetworkInfo().isConnected();
} }
} }

View File

@ -3,6 +3,9 @@ package org.schabi.newpipe;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request; import org.schabi.newpipe.extractor.downloader.Request;
import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.downloader.Response;
@ -26,9 +29,6 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import okhttp3.CipherSuite; import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec; import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -37,20 +37,22 @@ import okhttp3.ResponseBody;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;
public class DownloaderImpl extends Downloader { public final class DownloaderImpl extends Downloader {
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0"; public static final String USER_AGENT
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
private static DownloaderImpl instance; private static DownloaderImpl instance;
private String mCookies; private String mCookies;
private OkHttpClient client; private OkHttpClient client;
private DownloaderImpl(OkHttpClient.Builder builder) { private DownloaderImpl(final OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
enableModernTLS(builder); enableModernTLS(builder);
} }
this.client = builder this.client = builder
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
//.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024)) // .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
// 16 * 1024 * 1024))
.build(); .build();
} }
@ -58,20 +60,72 @@ public class DownloaderImpl extends Downloader {
* It's recommended to call exactly once in the entire lifetime of the application. * It's recommended to call exactly once in the entire lifetime of the application.
* *
* @param builder if null, default builder will be used * @param builder if null, default builder will be used
* @return a new instance of {@link DownloaderImpl}
*/ */
public static DownloaderImpl init(@Nullable OkHttpClient.Builder builder) { public static DownloaderImpl init(@Nullable final OkHttpClient.Builder builder) {
return instance = new DownloaderImpl(builder != null ? builder : new OkHttpClient.Builder()); instance = new DownloaderImpl(
builder != null ? builder : new OkHttpClient.Builder());
return instance;
} }
public static DownloaderImpl getInstance() { public static DownloaderImpl getInstance() {
return instance; return instance;
} }
/**
* Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken
* from the documentation of OkHttpClient.Builder.sslSocketFactory(_,_).
* <p>
* If there is an error, the function will safely fall back to doing nothing
* and printing the error to the console.
* </p>
*
* @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place)
*/
private static void enableModernTLS(final OkHttpClient.Builder builder) {
try {
// get the default TrustManager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
// insert our own TLSSocketFactory
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
builder.sslSocketFactory(sslSocketFactory, trustManager);
// This will try to enable all modern CipherSuites(+2 more)
// that are supported on the device.
// Necessary because some servers (e.g. Framatube.org)
// don't support the old cipher suites.
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
List<CipherSuite> cipherSuites = new ArrayList<>();
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
.build();
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
if (DEBUG) {
e.printStackTrace();
}
}
}
public String getCookies() { public String getCookies() {
return mCookies; return mCookies;
} }
public void setCookies(String cookies) { public void setCookies(final String cookies) {
mCookies = cookies; mCookies = cookies;
} }
@ -81,7 +135,7 @@ public class DownloaderImpl extends Downloader {
* @param url an url pointing to the content * @param url an url pointing to the content
* @return the size of the content, in bytes * @return the size of the content, in bytes
*/ */
public long getContentLength(String url) throws IOException { public long getContentLength(final String url) throws IOException {
try { try {
final Response response = head(url); final Response response = head(url);
return Long.parseLong(response.getHeader("Content-Length")); return Long.parseLong(response.getHeader("Content-Length"));
@ -92,7 +146,7 @@ public class DownloaderImpl extends Downloader {
} }
} }
public InputStream stream(String siteUrl) throws IOException { public InputStream stream(final String siteUrl) throws IOException {
try { try {
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder() final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method("GET", null).url(siteUrl) .method("GET", null).url(siteUrl)
@ -122,7 +176,8 @@ public class DownloaderImpl extends Downloader {
} }
@Override @Override
public Response execute(@NonNull Request request) throws IOException, ReCaptchaException { public Response execute(@NonNull final Request request)
throws IOException, ReCaptchaException {
final String httpMethod = request.httpMethod(); final String httpMethod = request.httpMethod();
final String url = request.url(); final String url = request.url();
final Map<String, List<String>> headers = request.headers(); final Map<String, List<String>> headers = request.headers();
@ -172,49 +227,7 @@ public class DownloaderImpl extends Downloader {
} }
final String latestUrl = response.request().url().toString(); final String latestUrl = response.request().url().toString();
return new Response(response.code(), response.message(), response.headers().toMultimap(), responseBodyToReturn, latestUrl); return new Response(response.code(), response.message(), response.headers().toMultimap(),
} responseBodyToReturn, latestUrl);
/**
* Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken from the documentation of
* OkHttpClient.Builder.sslSocketFactory(_,_)
* <p>
* If there is an error, the function will safely fall back to doing nothing and printing the error to the console.
*
* @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place)
*/
private static void enableModernTLS(OkHttpClient.Builder builder) {
try {
// get the default TrustManager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
// insert our own TLSSocketFactory
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
builder.sslSocketFactory(sslSocketFactory, trustManager);
// This will try to enable all modern CipherSuites(+2 more) that are supported on the device.
// Necessary because some servers (e.g. Framatube.org) don't support the old cipher suites.
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
List<CipherSuite> cipherSuites = new ArrayList<>();
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
.build();
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
if (DEBUG) e.printStackTrace();
}
} }
} }

View File

@ -1,4 +1,3 @@
package org.schabi.newpipe; package org.schabi.newpipe;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -27,9 +26,20 @@ import android.os.Bundle;
public class ExitActivity extends Activity { public class ExitActivity extends Activity {
public static void exitAndRemoveFromRecentApps(final Activity activity) {
Intent intent = new Intent(activity, ExitActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.startActivity(intent);
}
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 21) { if (Build.VERSION.SDK_INT >= 21) {
@ -40,15 +50,4 @@ public class ExitActivity extends Activity {
System.exit(0); System.exit(0);
} }
public static void exitAndRemoveFromRecentApps(Activity activity) {
Intent intent = new Intent(activity, ExitActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.startActivity(intent);
}
} }

View File

@ -18,7 +18,7 @@ public class ImageDownloader extends BaseImageDownloader {
private final SharedPreferences preferences; private final SharedPreferences preferences;
private final String downloadThumbnailKey; private final String downloadThumbnailKey;
public ImageDownloader(Context context) { public ImageDownloader(final Context context) {
super(context); super(context);
this.resources = context.getResources(); this.resources = context.getResources();
this.preferences = PreferenceManager.getDefaultSharedPreferences(context); this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
@ -31,7 +31,7 @@ public class ImageDownloader extends BaseImageDownloader {
@SuppressLint("ResourceType") @SuppressLint("ResourceType")
@Override @Override
public InputStream getStream(String imageUri, Object extra) throws IOException { public InputStream getStream(final String imageUri, final Object extra) throws IOException {
if (isDownloadingThumbnail()) { if (isDownloadingThumbnail()) {
return super.getStream(imageUri, extra); return super.getStream(imageUri, extra);
} else { } else {
@ -39,7 +39,8 @@ public class ImageDownloader extends BaseImageDownloader {
} }
} }
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException { protected InputStream getStreamFromNetwork(final String imageUri, final Object extra)
throws IOException {
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader(); final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
return downloader.stream(imageUri); return downloader.stream(imageUri);
} }

View File

@ -93,11 +93,11 @@ public class MainActivity extends AppCompatActivity {
private boolean servicesShown = false; private boolean servicesShown = false;
private ImageView serviceArrow; private ImageView serviceArrow;
private static final int ITEM_ID_SUBSCRIPTIONS = - 1; private static final int ITEM_ID_SUBSCRIPTIONS = -1;
private static final int ITEM_ID_FEED = - 2; private static final int ITEM_ID_FEED = -2;
private static final int ITEM_ID_BOOKMARKS = - 3; private static final int ITEM_ID_BOOKMARKS = -3;
private static final int ITEM_ID_DOWNLOADS = - 4; private static final int ITEM_ID_DOWNLOADS = -4;
private static final int ITEM_ID_HISTORY = - 5; private static final int ITEM_ID_HISTORY = -5;
private static final int ITEM_ID_SETTINGS = 0; private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1; private static final int ITEM_ID_ABOUT = 1;
@ -108,8 +108,11 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); if (DEBUG) {
Log.d(TAG, "onCreate() called with: "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
// enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources // enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
@ -123,10 +126,12 @@ public class MainActivity extends AppCompatActivity {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = getWindow(); Window w = getWindow();
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
} }
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) { if (getSupportFragmentManager() != null
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments(); initFragments();
} }
@ -151,13 +156,15 @@ public class MainActivity extends AppCompatActivity {
for (final String ks : service.getKioskList().getAvailableKiosks()) { for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerItems.getMenu() drawerItems.getMenu()
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator.getTranslatedKioskName(ks, this)) .add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this)); .setIcon(KioskTranslator.getKioskIcons(ks, this));
kioskId ++; kioskId++;
} }
drawerItems.getMenu() drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions) .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel)); .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu() drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title) .add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
@ -180,20 +187,21 @@ public class MainActivity extends AppCompatActivity {
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about) .add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info)); .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close); toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
R.string.drawer_close);
toggle.syncState(); toggle.syncState();
drawer.addDrawerListener(toggle); drawer.addDrawerListener(toggle);
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
private int lastService; private int lastService;
@Override @Override
public void onDrawerOpened(View drawerView) { public void onDrawerOpened(final View drawerView) {
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this); lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
} }
@Override @Override
public void onDrawerClosed(View drawerView) { public void onDrawerClosed(final View drawerView) {
if(servicesShown) { if (servicesShown) {
toggleServices(); toggleServices();
} }
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) { if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
@ -206,7 +214,7 @@ public class MainActivity extends AppCompatActivity {
setupDrawerHeader(); setupDrawerHeader();
} }
private boolean drawerItemSelected(MenuItem item) { private boolean drawerItemSelected(final MenuItem item) {
switch (item.getGroupId()) { switch (item.getGroupId()) {
case R.id.menu_services_group: case R.id.menu_services_group:
changeService(item); changeService(item);
@ -229,14 +237,16 @@ public class MainActivity extends AppCompatActivity {
return true; return true;
} }
private void changeService(MenuItem item) { private void changeService(final MenuItem item) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false); drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getItemId()); ServiceHelper.setSelectedServiceId(this, item.getItemId());
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(true);
} }
private void tabSelected(MenuItem item) throws ExtractionException { private void tabSelected(final MenuItem item) throws ExtractionException {
switch(item.getItemId()) { switch (item.getItemId()) {
case ITEM_ID_SUBSCRIPTIONS: case ITEM_ID_SUBSCRIPTIONS:
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager()); NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
break; break;
@ -259,19 +269,20 @@ public class MainActivity extends AppCompatActivity {
int kioskId = 0; int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) { for (final String ks : service.getKioskList().getAvailableKiosks()) {
if(kioskId == item.getItemId()) { if (kioskId == item.getItemId()) {
serviceName = ks; serviceName = ks;
} }
kioskId ++; kioskId++;
} }
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName); NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId,
serviceName);
break; break;
} }
} }
private void optionsAboutSelected(MenuItem item) { private void optionsAboutSelected(final MenuItem item) {
switch(item.getItemId()) { switch (item.getItemId()) {
case ITEM_ID_SETTINGS: case ITEM_ID_SETTINGS:
NavigationHelper.openSettings(this); NavigationHelper.openSettings(this);
break; break;
@ -283,7 +294,7 @@ public class MainActivity extends AppCompatActivity {
private void setupDrawerHeader() { private void setupDrawerHeader() {
NavigationView navigationView = findViewById(R.id.navigation); NavigationView navigationView = findViewById(R.id.navigation);
View hView = navigationView.getHeaderView(0); View hView = navigationView.getHeaderView(0);
serviceArrow = hView.findViewById(R.id.drawer_arrow); serviceArrow = hView.findViewById(R.id.drawer_arrow);
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon); headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
@ -299,7 +310,7 @@ public class MainActivity extends AppCompatActivity {
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group); drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group); drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
if(servicesShown) { if (servicesShown) {
showServices(); showServices();
} else { } else {
try { try {
@ -313,55 +324,62 @@ public class MainActivity extends AppCompatActivity {
private void showServices() { private void showServices() {
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp); serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
for(StreamingService s : NewPipe.getServices()) { for (StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName() + final String title = s.getServiceInfo().getName()
(ServiceHelper.isBeta(s) ? " (beta)" : ""); + (ServiceHelper.isBeta(s) ? " (beta)" : "");
MenuItem menuItem = drawerItems.getMenu() MenuItem menuItem = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title) .add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId())); .setIcon(ServiceHelper.getIcon(s.getServiceId()));
// peertube specifics // peertube specifics
if(s.getServiceId() == 3){ if (s.getServiceId() == 3) {
enhancePeertubeMenu(s, menuItem); enhancePeertubeMenu(s, menuItem);
} }
} }
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(true);
} }
private void enhancePeertubeMenu(StreamingService s, MenuItem menuItem) { private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance(); PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : "")); menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
Spinner spinner = (Spinner) LayoutInflater.from(this).inflate(R.layout.instance_spinner_layout, null); Spinner spinner = (Spinner) LayoutInflater.from(this)
.inflate(R.layout.instance_spinner_layout, null);
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this); List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
List<String> items = new ArrayList<>(); List<String> items = new ArrayList<>();
int defaultSelect = 0; int defaultSelect = 0;
for(PeertubeInstance instance: instances){ for (PeertubeInstance instance : instances) {
items.add(instance.getName()); items.add(instance.getName());
if(instance.getUrl().equals(currentInstace.getUrl())){ if (instance.getUrl().equals(currentInstace.getUrl())) {
defaultSelect = items.size()-1; defaultSelect = items.size() - 1;
} }
} }
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.instance_spinner_item, items); ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
R.layout.instance_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter); spinner.setAdapter(adapter);
spinner.setSelection(defaultSelect, false); spinner.setSelection(defaultSelect, false);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(final AdapterView<?> parent, final View view,
final int position, final long id) {
PeertubeInstance newInstance = instances.get(position); PeertubeInstance newInstance = instances.get(position);
if(newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) return; if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
return;
}
PeertubeHelper.selectInstance(newInstance, getApplicationContext()); PeertubeHelper.selectInstance(newInstance, getApplicationContext());
changeService(menuItem); changeService(menuItem);
drawer.closeDrawers(); drawer.closeDrawers();
new Handler(Looper.getMainLooper()).postDelayed(() -> { new Handler(Looper.getMainLooper()).postDelayed(() -> {
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); getSupportFragmentManager().popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
recreate(); recreate();
}, 300); }, 300);
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(final AdapterView<?> parent) {
} }
}); });
@ -379,9 +397,10 @@ public class MainActivity extends AppCompatActivity {
for (final String ks : service.getKioskList().getAvailableKiosks()) { for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerItems.getMenu() drawerItems.getMenu()
.add(R.id.menu_tabs_group, kioskId, ORDER, KioskTranslator.getTranslatedKioskName(ks, this)) .add(R.id.menu_tabs_group, kioskId, ORDER,
KioskTranslator.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this)); .setIcon(KioskTranslator.getKioskIcons(ks, this));
kioskId ++; kioskId++;
} }
drawerItems.getMenu() drawerItems.getMenu()
@ -420,15 +439,17 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onResume() { protected void onResume() {
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
Localization.init(getApplicationContext()); //change the date format to match the selected language on resume // Change the date format to match the selected language on resume
Localization.init(getApplicationContext());
super.onResume(); super.onResume();
// close drawer on return, and don't show animation, so its looks like the drawer isn't open // Close drawer on return, and don't show animation,
// when the user returns to MainActivity // so it looks like the drawer isn't open when the user returns to MainActivity
drawer.closeDrawer(GravityCompat.START, false); drawer.closeDrawer(GravityCompat.START, false);
try { try {
final int selectedServiceId = ServiceHelper.getSelectedServiceId(this); final int selectedServiceId = ServiceHelper.getSelectedServiceId(this);
final String selectedServiceName = NewPipe.getService(selectedServiceId).getServiceInfo().getName(); final String selectedServiceName = NewPipe.getService(selectedServiceId)
.getServiceInfo().getName();
headerServiceView.setText(selectedServiceName); headerServiceView.setText(selectedServiceName);
headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId)); headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
@ -441,15 +462,20 @@ public class MainActivity extends AppCompatActivity {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) { if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
if (DEBUG) Log.d(TAG, "Theme has changed, recreating activity..."); if (DEBUG) {
Log.d(TAG, "Theme has changed, recreating activity...");
}
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply(); sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
// https://stackoverflow.com/questions/10844112/runtimeexception-performing-pause-of-activity-that-is-not-resumed // https://stackoverflow.com/questions/10844112/
// Briefly, let the activity resume properly posting the recreate call to end of the message queue // Briefly, let the activity resume
// properly posting the recreate call to end of the message queue
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate); new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
} }
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) { if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
if (DEBUG) Log.d(TAG, "main page has changed, recreating main fragment..."); if (DEBUG) {
Log.d(TAG, "main page has changed, recreating main fragment...");
}
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply(); sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
NavigationHelper.openMainActivity(this); NavigationHelper.openMainActivity(this);
} }
@ -460,13 +486,18 @@ public class MainActivity extends AppCompatActivity {
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(final Intent intent) {
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]"); if (DEBUG) {
Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
}
if (intent != null) { if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...) // Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack // to not destroy the already created backstack
String action = intent.getAction(); String action = intent.getAction();
if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return; if ((action != null && action.equals(Intent.ACTION_MAIN))
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
return;
}
} }
super.onNewIntent(intent); super.onNewIntent(intent);
@ -476,24 +507,32 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed() called"); if (DEBUG) {
Log.d(TAG, "onBackPressed() called");
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press) delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) return;
} }
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) {
return;
}
}
if (getSupportFragmentManager().getBackStackEntryCount() == 1) { if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
finish(); finish();
} else super.onBackPressed(); } else {
super.onBackPressed();
}
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(final int requestCode,
for (int i: grantResults){ @NonNull final String[] permissions,
if (i == PackageManager.PERMISSION_DENIED){ @NonNull final int[] grantResults) {
for (int i : grantResults) {
if (i == PackageManager.PERMISSION_DENIED) {
return; return;
} }
} }
@ -502,7 +541,8 @@ public class MainActivity extends AppCompatActivity {
NavigationHelper.openDownloads(this); NavigationHelper.openDownloads(this);
break; break;
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE: case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) { if (fragment instanceof VideoDetailFragment) {
((VideoDetailFragment) fragment).openDownloadDialog(); ((VideoDetailFragment) fragment).openDownloadDialog();
} }
@ -547,8 +587,10 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]"); if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
}
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
@ -557,8 +599,8 @@ public class MainActivity extends AppCompatActivity {
} }
if (!(fragment instanceof SearchFragment)) { if (!(fragment instanceof SearchFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE); findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
.setVisibility(View.GONE);
} }
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
@ -572,8 +614,10 @@ public class MainActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]"); if (DEBUG) {
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
}
int id = item.getItemId(); int id = item.getItemId();
switch (id) { switch (id) {
@ -590,11 +634,15 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void initFragments() { private void initFragments() {
if (DEBUG) Log.d(TAG, "initFragments() called"); if (DEBUG) {
Log.d(TAG, "initFragments() called");
}
StateSaver.clearStateFiles(); StateSaver.clearStateFiles();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) { if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
handleIntent(getIntent()); handleIntent(getIntent());
} else NavigationHelper.gotoMainFragment(getSupportFragmentManager()); } else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -602,12 +650,14 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void updateDrawerNavigation() { private void updateDrawerNavigation() {
if (getSupportActionBar() == null) return; if (getSupportActionBar() == null) {
return;
}
final Toolbar toolbar = findViewById(R.id.toolbar); final Toolbar toolbar = findViewById(R.id.toolbar);
final DrawerLayout drawer = findViewById(R.id.drawer_layout);
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
if (fragment instanceof MainFragment) { if (fragment instanceof MainFragment) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setDisplayHomeAsUpEnabled(false);
if (toggle != null) { if (toggle != null) {
@ -622,26 +672,23 @@ public class MainActivity extends AppCompatActivity {
} }
} }
private void updateDrawerHeaderString(String content) { private void handleIntent(final Intent intent) {
NavigationView navigationView = findViewById(R.id.navigation);
View hView = navigationView.getHeaderView(0);
Button action = hView.findViewById(R.id.drawer_header_action_button);
action.setContentDescription(content);
}
private void handleIntent(Intent intent) {
try { try {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); if (DEBUG) {
Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
}
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) { if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL); String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0); int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
String title = intent.getStringExtra(Constants.KEY_TITLE); String title = intent.getStringExtra(Constants.KEY_TITLE);
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) { switch (((StreamingService.LinkType) intent
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM: case STREAM:
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false); boolean autoPlay = intent
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay); .getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
serviceId, url, title, autoPlay);
break; break;
case CHANNEL: case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(), NavigationHelper.openChannelFragment(getSupportFragmentManager(),
@ -658,7 +705,9 @@ public class MainActivity extends AppCompatActivity {
} }
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) { } else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING); String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING);
if (searchString == null) searchString = ""; if (searchString == null) {
searchString = "";
}
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0); int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
NavigationHelper.openSearchFragment( NavigationHelper.openSearchFragment(
getSupportFragmentManager(), getSupportFragmentManager(),

View File

@ -13,14 +13,13 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3; import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
public final class NewPipeDatabase { public final class NewPipeDatabase {
private static volatile AppDatabase databaseInstance; private static volatile AppDatabase databaseInstance;
private NewPipeDatabase() { private NewPipeDatabase() {
//no instance //no instance
} }
private static AppDatabase getDatabase(Context context) { private static AppDatabase getDatabase(final Context context) {
return Room return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME) .databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
@ -28,13 +27,14 @@ public final class NewPipeDatabase {
} }
@NonNull @NonNull
public static AppDatabase getInstance(@NonNull Context context) { public static AppDatabase getInstance(@NonNull final Context context) {
AppDatabase result = databaseInstance; AppDatabase result = databaseInstance;
if (result == null) { if (result == null) {
synchronized (NewPipeDatabase.class) { synchronized (NewPipeDatabase.class) {
result = databaseInstance; result = databaseInstance;
if (result == null) { if (result == null) {
databaseInstance = (result = getDatabase(context)); databaseInstance = getDatabase(context);
result = databaseInstance;
} }
} }
} }

View File

@ -1,4 +1,3 @@
package org.schabi.newpipe; package org.schabi.newpipe;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -26,17 +25,18 @@ import android.os.Bundle;
*/ */
public class PanicResponderActivity extends Activity { public class PanicResponderActivity extends Activity {
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER"; public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Intent intent = getIntent(); Intent intent = getIntent();
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
// TODO explicitly clear the search results once they are restored when the app restarts // TODO: Explicitly clear the search results
// or if the app reloads the current video after being killed, that should be cleared also // once they are restored when the app restarts
// or if the app reloads the current video after being killed,
// that should be cleared also
ExitActivity.exitAndRemoveFromRecentApps(this); ExitActivity.exitAndRemoveFromRecentApps(this);
} }

View File

@ -3,11 +3,6 @@ package org.schabi.newpipe;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.app.NavUtils;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -16,9 +11,13 @@ import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import org.schabi.newpipe.util.ThemeHelper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import org.schabi.newpipe.util.ThemeHelper;
/* /*
* Created by beneth <bmauduit@beneth.fr> on 06.12.16. * Created by beneth <bmauduit@beneth.fr> on 06.12.16.
@ -49,7 +48,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
private String foundCookies = ""; private String foundCookies = "";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recaptcha); setContentView(R.layout.activity_recaptcha);
@ -73,7 +72,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
webView.setWebViewClient(new WebViewClient() { webView.setWebViewClient(new WebViewClient() {
@Override @Override
public void onPageFinished(WebView view, String url) { public void onPageFinished(final WebView view, final String url) {
super.onPageFinished(view, url); super.onPageFinished(view, url);
handleCookies(url); handleCookies(url);
} }
@ -84,7 +83,8 @@ public class ReCaptchaActivity extends AppCompatActivity {
webView.clearHistory(); webView.clearHistory();
android.webkit.CookieManager cookieManager = CookieManager.getInstance(); android.webkit.CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.removeAllCookies(aBoolean -> {}); cookieManager.removeAllCookies(aBoolean -> {
});
} else { } else {
cookieManager.removeAllCookie(); cookieManager.removeAllCookie();
} }
@ -93,7 +93,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_recaptcha, menu); getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
@ -112,7 +112,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId(); int id = item.getItemId();
switch (id) { switch (id) {
case R.id.menu_item_done: case R.id.menu_item_done:
@ -137,24 +137,29 @@ public class ReCaptchaActivity extends AppCompatActivity {
} }
private void handleCookies(final String url) {
private void handleCookies(String url) {
String cookies = CookieManager.getInstance().getCookie(url); String cookies = CookieManager.getInstance().getCookie(url);
if (MainActivity.DEBUG) Log.d(TAG, "handleCookies: url=" + url + "; cookies=" + (cookies == null ? "null" : cookies)); if (MainActivity.DEBUG) {
if (cookies == null) return; Log.d(TAG, "handleCookies: "
+ "url=" + url + "; cookies=" + (cookies == null ? "null" : cookies));
}
if (cookies == null) {
return;
}
addYoutubeCookies(cookies); addYoutubeCookies(cookies);
// add other methods to extract cookies here // add other methods to extract cookies here
} }
private void addYoutubeCookies(@NonNull String cookies) { private void addYoutubeCookies(@NonNull final String cookies) {
if (cookies.contains("s_gl=") || cookies.contains("goojf=") || cookies.contains("VISITOR_INFO1_LIVE=")) { if (cookies.contains("s_gl=") || cookies.contains("goojf=")
|| cookies.contains("VISITOR_INFO1_LIVE=")) {
// youtube seems to also need the other cookies: // youtube seems to also need the other cookies:
addCookie(cookies); addCookie(cookies);
} }
} }
private void addCookie(String cookie) { private void addCookie(final String cookie) {
if (foundCookies.contains(cookie)) { if (foundCookies.contains(cookie)) {
return; return;
} }

View File

@ -54,6 +54,8 @@ import org.schabi.newpipe.util.urlfinder.UrlFinder;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import icepick.Icepick; import icepick.Icepick;
@ -71,29 +73,31 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCap
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/** /**
* Get the url from the intent and open it in the chosen preferred player * Get the url from the intent and open it in the chosen preferred player.
*/ */
public class RouterActivity extends AppCompatActivity { public class RouterActivity extends AppCompatActivity {
public static final String INTERNAL_ROUTE_KEY = "internalRoute";
/**
* Removes invisible separators (\p{Z}) and punctuation characters including
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
* more details.
*/
private static final String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
protected final CompositeDisposable disposables = new CompositeDisposable();
@State @State
protected int currentServiceId = -1; protected int currentServiceId = -1;
private StreamingService currentService;
@State @State
protected LinkType currentLinkType; protected LinkType currentLinkType;
@State @State
protected int selectedRadioPosition = -1; protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1; protected int selectedPreviously = -1;
protected String currentUrl; protected String currentUrl;
protected boolean internalRoute = false; protected boolean internalRoute = false;
protected final CompositeDisposable disposables = new CompositeDisposable(); private StreamingService currentService;
private boolean selectionIsDownload = false; private boolean selectionIsDownload = false;
public static final String internalRouteKey = "internalRoute";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState); Icepick.restoreInstanceState(this, savedInstanceState);
@ -106,14 +110,14 @@ public class RouterActivity extends AppCompatActivity {
} }
} }
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false); internalRoute = getIntent().getBooleanExtra(INTERNAL_ROUTE_KEY, false);
setTheme(ThemeHelper.isLightThemeSelected(this) setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState); Icepick.saveInstanceState(this, outState);
} }
@ -132,7 +136,7 @@ public class RouterActivity extends AppCompatActivity {
disposables.clear(); disposables.clear();
} }
private void handleUrl(String url) { private void handleUrl(final String url) {
disposables.add(Observable disposables.add(Observable
.fromCallable(() -> { .fromCallable(() -> {
if (currentServiceId == -1) { if (currentServiceId == -1) {
@ -157,13 +161,14 @@ public class RouterActivity extends AppCompatActivity {
}, this::handleError)); }, this::handleError));
} }
private void handleError(Throwable error) { private void handleError(final Throwable error) {
error.printStackTrace(); error.printStackTrace();
if (error instanceof ExtractionException) { if (error instanceof ExtractionException) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
} else { } else {
ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null); ExtractorHelper.handleGeneralException(this, -1, null, error,
UserAction.SOMETHING_ELSE, null);
} }
finish(); finish();
@ -175,8 +180,11 @@ public class RouterActivity extends AppCompatActivity {
} }
protected void onSuccess() { protected void onSuccess() {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager
final String selectedChoiceKey = preferences.getString(getString(R.string.preferred_open_action_key), getString(R.string.preferred_open_action_default)); .getDefaultSharedPreferences(this);
final String selectedChoiceKey = preferences
.getString(getString(R.string.preferred_open_action_key),
getString(R.string.preferred_open_action_default));
final String showInfoKey = getString(R.string.show_info_key); final String showInfoKey = getString(R.string.show_info_key);
final String videoPlayerKey = getString(R.string.video_player_key); final String videoPlayerKey = getString(R.string.video_player_key);
@ -186,7 +194,8 @@ public class RouterActivity extends AppCompatActivity {
final String alwaysAskKey = getString(R.string.always_ask_open_action_key); final String alwaysAskKey = getString(R.string.always_ask_open_action_key);
if (selectedChoiceKey.equals(alwaysAskKey)) { if (selectedChoiceKey.equals(alwaysAskKey)) {
final List<AdapterChoiceItem> choices = getChoicesForService(currentService, currentLinkType); final List<AdapterChoiceItem> choices
= getChoicesForService(currentService, currentLinkType);
switch (choices.size()) { switch (choices.size()) {
case 1: case 1:
@ -204,20 +213,26 @@ public class RouterActivity extends AppCompatActivity {
} else if (selectedChoiceKey.equals(downloadKey)) { } else if (selectedChoiceKey.equals(downloadKey)) {
handleChoice(downloadKey); handleChoice(downloadKey);
} else { } else {
final boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); final boolean isExtVideoEnabled = preferences.getBoolean(
final boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); getString(R.string.use_external_video_player_key), false);
final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey) || selectedChoiceKey.equals(popupPlayerKey); final boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false);
final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey)
|| selectedChoiceKey.equals(popupPlayerKey);
final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey); final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey);
if (currentLinkType != LinkType.STREAM) { if (currentLinkType != LinkType.STREAM) {
if (isExtAudioEnabled && isAudioPlayerSelected || isExtVideoEnabled && isVideoPlayerSelected) { if (isExtAudioEnabled && isAudioPlayerSelected
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show(); || isExtVideoEnabled && isVideoPlayerSelected) {
Toast.makeText(this, R.string.external_player_unsupported_link_type,
Toast.LENGTH_LONG).show();
handleChoice(showInfoKey); handleChoice(showInfoKey);
return; return;
} }
} }
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = currentService.getServiceInfo().getMediaCapabilities(); final List<StreamingService.ServiceInfo.MediaCapability> capabilities
= currentService.getServiceInfo().getMediaCapabilities();
boolean serviceSupportsChoice = false; boolean serviceSupportsChoice = false;
if (isVideoPlayerSelected) { if (isVideoPlayerSelected) {
@ -239,7 +254,8 @@ public class RouterActivity extends AppCompatActivity {
final Context themeWrapperContext = getThemeWrapperContext(); final Context themeWrapperContext = getThemeWrapperContext();
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false); final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
R.layout.preferred_player_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list); final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> { final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
@ -250,7 +266,9 @@ public class RouterActivity extends AppCompatActivity {
handleChoice(choice.key); handleChoice(choice.key);
if (which == DialogInterface.BUTTON_POSITIVE) { if (which == DialogInterface.BUTTON_POSITIVE) {
preferences.edit().putString(getString(R.string.preferred_open_action_key), choice.key).apply(); preferences.edit()
.putString(getString(R.string.preferred_open_action_key), choice.key)
.apply();
} }
}; };
@ -261,7 +279,9 @@ public class RouterActivity extends AppCompatActivity {
.setNegativeButton(R.string.just_once, dialogButtonsClickListener) .setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener) .setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> { .setOnDismissListener((dialog) -> {
if (!selectionIsDownload) finish(); if (!selectionIsDownload) {
finish();
}
}) })
.create(); .create();
@ -270,10 +290,13 @@ public class RouterActivity extends AppCompatActivity {
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1); setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
}); });
radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true)); radioGroup.setOnCheckedChangeListener((group, checkedId) ->
setDialogButtonsState(alertDialog, true));
final View.OnClickListener radioButtonsClickListener = v -> { final View.OnClickListener radioButtonsClickListener = v -> {
final int indexOfChild = radioGroup.indexOfChild(v); final int indexOfChild = radioGroup.indexOfChild(v);
if (indexOfChild == -1) return; if (indexOfChild == -1) {
return;
}
selectedPreviously = selectedRadioPosition; selectedPreviously = selectedRadioPosition;
selectedRadioPosition = indexOfChild; selectedRadioPosition = indexOfChild;
@ -285,18 +308,21 @@ public class RouterActivity extends AppCompatActivity {
int id = 12345; int id = 12345;
for (AdapterChoiceItem item : choices) { for (AdapterChoiceItem item : choices) {
final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null); final RadioButton radioButton
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description); radioButton.setText(item.description);
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0); radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setChecked(false); radioButton.setChecked(false);
radioButton.setId(id++); radioButton.setId(id++);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); radioButton.setLayoutParams(new RadioGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener); radioButton.setOnClickListener(radioButtonsClickListener);
radioGroup.addView(radioButton); radioGroup.addView(radioButton);
} }
if (selectedRadioPosition == -1) { if (selectedRadioPosition == -1) {
final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_open_action_last_selected_key), null); final String lastSelectedPlayer = preferences.getString(
getString(R.string.preferred_open_action_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) { if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.size(); i++) { for (int i = 0; i < choices.size(); i++) {
AdapterChoiceItem c = choices.get(i); AdapterChoiceItem c = choices.get(i);
@ -317,46 +343,58 @@ public class RouterActivity extends AppCompatActivity {
alertDialog.show(); alertDialog.show();
} }
private List<AdapterChoiceItem> getChoicesForService(StreamingService service, LinkType linkType) { private List<AdapterChoiceItem> getChoicesForService(final StreamingService service,
final LinkType linkType) {
final Context context = getThemeWrapperContext(); final Context context = getThemeWrapperContext();
final List<AdapterChoiceItem> returnList = new ArrayList<>(); final List<AdapterChoiceItem> returnList = new ArrayList<>();
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = service.getServiceInfo().getMediaCapabilities(); final List<StreamingService.ServiceInfo.MediaCapability> capabilities
= service.getServiceInfo().getMediaCapabilities();
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); .getDefaultSharedPreferences(this);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); boolean isExtVideoEnabled = preferences.getBoolean(
getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false);
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key), getString(R.string.show_info), returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
getString(R.string.show_info),
resolveResourceIdFromAttr(context, R.attr.info))); resolveResourceIdFromAttr(context, R.attr.info)));
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) { if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player), returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
getString(R.string.video_player),
resolveResourceIdFromAttr(context, R.attr.play))); resolveResourceIdFromAttr(context, R.attr.play)));
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player), returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
getString(R.string.popup_player),
resolveResourceIdFromAttr(context, R.attr.popup))); resolveResourceIdFromAttr(context, R.attr.popup)));
} }
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) { if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player), returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
getString(R.string.background_player),
resolveResourceIdFromAttr(context, R.attr.audio))); resolveResourceIdFromAttr(context, R.attr.audio)));
} }
returnList.add(new AdapterChoiceItem(getString(R.string.download_key), getString(R.string.download), returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download),
resolveResourceIdFromAttr(context, R.attr.download))); resolveResourceIdFromAttr(context, R.attr.download)));
return returnList; return returnList;
} }
private Context getThemeWrapperContext() { private Context getThemeWrapperContext() {
return new ContextThemeWrapper(this, return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme); ? R.style.LightTheme : R.style.DarkTheme);
} }
private void setDialogButtonsState(AlertDialog dialog, boolean state) { private void setDialogButtonsState(final AlertDialog dialog, final boolean state) {
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (negativeButton == null || positiveButton == null) return; if (negativeButton == null || positiveButton == null) {
return;
}
negativeButton.setEnabled(state); negativeButton.setEnabled(state);
positiveButton.setEnabled(state); positiveButton.setEnabled(state);
@ -372,21 +410,25 @@ public class RouterActivity extends AppCompatActivity {
} }
private void handleChoice(final String selectedChoiceKey) { private void handleChoice(final String selectedChoiceKey) {
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list)); final List<String> validChoicesList = Arrays.asList(getResources()
.getStringArray(R.array.preferred_open_action_values_list));
if (validChoicesList.contains(selectedChoiceKey)) { if (validChoicesList.contains(selectedChoiceKey)) {
PreferenceManager.getDefaultSharedPreferences(this).edit() PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(getString(R.string.preferred_open_action_last_selected_key), selectedChoiceKey) .putString(getString(
R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
.apply(); .apply();
} }
if (selectedChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) { if (selectedChoiceKey.equals(getString(R.string.popup_player_key))
&& !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this); PermissionHelper.showPopupEnablementToast(this);
finish(); finish();
return; return;
} }
if (selectedChoiceKey.equals(getString(R.string.download_key))) { if (selectedChoiceKey.equals(getString(R.string.download_key))) {
if (PermissionHelper.checkStoragePermissions(this, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { if (PermissionHelper.checkStoragePermissions(this,
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
selectionIsDownload = true; selectionIsDownload = true;
openDownloadDialog(); openDownloadDialog();
} }
@ -414,7 +456,8 @@ public class RouterActivity extends AppCompatActivity {
} }
final Intent intent = new Intent(this, FetcherService.class); final Intent intent = new Intent(this, FetcherService.class);
final Choice choice = new Choice(currentService.getServiceId(), currentLinkType, currentUrl, selectedChoiceKey); final Choice choice = new Choice(currentService.getServiceId(), currentLinkType,
currentUrl, selectedChoiceKey);
intent.putExtra(FetcherService.KEY_CHOICE, choice); intent.putExtra(FetcherService.KEY_CHOICE, choice);
startService(intent); startService(intent);
@ -427,12 +470,11 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> { .subscribe((@NonNull StreamInfo result) -> {
List<VideoStream> sortedVideoStreams = ListHelper.getSortedStreamVideosList(this, List<VideoStream> sortedVideoStreams = ListHelper
result.getVideoStreams(), .getSortedStreamVideosList(this, result.getVideoStreams(),
result.getVideoOnlyStreams(), result.getVideoOnlyStreams(), false);
false); int selectedVideoStreamIndex = ListHelper
int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this, .getDefaultResolutionIndex(this, sortedVideoStreams);
sortedVideoStreams);
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
DownloadDialog downloadDialog = DownloadDialog.newInstance(result); DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
@ -450,7 +492,9 @@ public class RouterActivity extends AppCompatActivity {
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults) {
for (int i : grantResults) { for (int i : grantResults) {
if (i == PackageManager.PERMISSION_DENIED) { if (i == PackageManager.PERMISSION_DENIED) {
finish(); finish();
@ -462,12 +506,73 @@ public class RouterActivity extends AppCompatActivity {
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
private String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
if (!input.substring(i, i + 1).matches("\\p{L}")) {
start = i + 1;
break;
}
}
return input.substring(start);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private String trim(final String input) {
if (input == null || input.length() < 1) {
return input;
} else {
String output = input;
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
output = output.substring(1);
}
while (output.length() > 0
&& output.substring(output.length() - 1).matches(REGEX_REMOVE_FROM_URL)) {
output = output.substring(0, output.length() - 1);
}
return output;
}
}
/**
* Retrieves all Strings which look remotely like URLs from a text.
* Used if NewPipe was called through share menu.
*
* @param sharedText text to scan for URLs.
* @return potential URLs
*/
protected String[] getUris(final String sharedText) {
final Collection<String> result = new HashSet<>();
if (sharedText != null) {
final String[] array = sharedText.split("\\p{Space}");
for (String s : array) {
s = trim(s);
if (s.length() != 0) {
if (s.matches(".+://.+")) {
result.add(removeHeadingGibberish(s));
} else if (s.matches(".+\\..+")) {
result.add("http://" + s);
}
}
}
}
return result.toArray(new String[result.size()]);
}
private static class AdapterChoiceItem { private static class AdapterChoiceItem {
final String description, key; final String description;
final String key;
@DrawableRes @DrawableRes
final int icon; final int icon;
AdapterChoiceItem(String key, String description, int icon) { AdapterChoiceItem(final String key, final String description, final int icon) {
this.description = description; this.description = description;
this.key = key; this.key = key;
this.icon = icon; this.icon = icon;
@ -476,10 +581,12 @@ public class RouterActivity extends AppCompatActivity {
private static class Choice implements Serializable { private static class Choice implements Serializable {
final int serviceId; final int serviceId;
final String url, playerChoice; final String url;
final String playerChoice;
final LinkType linkType; final LinkType linkType;
Choice(int serviceId, LinkType linkType, String url, String playerChoice) { Choice(final int serviceId, final LinkType linkType,
final String url, final String playerChoice) {
this.serviceId = serviceId; this.serviceId = serviceId;
this.linkType = linkType; this.linkType = linkType;
this.url = url; this.url = url;
@ -492,14 +599,10 @@ public class RouterActivity extends AppCompatActivity {
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
public static class FetcherService extends IntentService { public static class FetcherService extends IntentService {
private static final int ID = 456;
public static final String KEY_CHOICE = "key_choice"; public static final String KEY_CHOICE = "key_choice";
private static final int ID = 456;
private Disposable fetcher; private Disposable fetcher;
public FetcherService() { public FetcherService() {
@ -513,16 +616,20 @@ public class RouterActivity extends AppCompatActivity {
} }
@Override @Override
protected void onHandleIntent(@Nullable Intent intent) { protected void onHandleIntent(@Nullable final Intent intent) {
if (intent == null) return; if (intent == null) {
return;
}
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE); final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
if (!(serializable instanceof Choice)) return; if (!(serializable instanceof Choice)) {
return;
}
Choice playerChoice = (Choice) serializable; Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice); handleChoice(playerChoice);
} }
public void handleChoice(Choice choice) { public void handleChoice(final Choice choice) {
Single<? extends Info> single = null; Single<? extends Info> single = null;
UserAction userAction = UserAction.SOMETHING_ELSE; UserAction userAction = UserAction.SOMETHING_ELSE;
@ -549,22 +656,27 @@ public class RouterActivity extends AppCompatActivity {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> { .subscribe(info -> {
resultHandler.accept(info); resultHandler.accept(info);
if (fetcher != null) fetcher.dispose(); if (fetcher != null) {
fetcher.dispose();
}
}, throwable -> ExtractorHelper.handleGeneralException(this, }, throwable -> ExtractorHelper.handleGeneralException(this,
choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice)); choice.serviceId, choice.url, throwable, finalUserAction,
", opened with " + choice.playerChoice));
} }
} }
public Consumer<Info> getResultHandler(Choice choice) { public Consumer<Info> getResultHandler(final Choice choice) {
return info -> { return info -> {
final String videoPlayerKey = getString(R.string.video_player_key); final String videoPlayerKey = getString(R.string.video_player_key);
final String backgroundPlayerKey = getString(R.string.background_player_key); final String backgroundPlayerKey = getString(R.string.background_player_key);
final String popupPlayerKey = getString(R.string.popup_player_key); final String popupPlayerKey = getString(R.string.popup_player_key);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); .getDefaultSharedPreferences(this);
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); boolean isExtVideoEnabled = preferences.getBoolean(
; getString(R.string.use_external_video_player_key), false);
boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false);
PlayQueue playQueue; PlayQueue playQueue;
String playerChoice = choice.playerChoice; String playerChoice = choice.playerChoice;
@ -590,7 +702,9 @@ public class RouterActivity extends AppCompatActivity {
} }
if (info instanceof ChannelInfo || info instanceof PlaylistInfo) { if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); playQueue = info instanceof ChannelInfo
? new ChannelPlayQueue((ChannelInfo) info)
: new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) { if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true); NavigationHelper.playOnMainPlayer(this, playQueue, true);
@ -607,7 +721,9 @@ public class RouterActivity extends AppCompatActivity {
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
stopForeground(true); stopForeground(true);
if (fetcher != null) fetcher.dispose(); if (fetcher != null) {
fetcher.dispose();
}
} }
private NotificationCompat.Builder createNotification() { private NotificationCompat.Builder createNotification() {
@ -615,8 +731,10 @@ public class RouterActivity extends AppCompatActivity {
.setOngoing(true) .setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(getString(R.string.preferred_player_fetcher_notification_title)) .setContentTitle(
.setContentText(getString(R.string.preferred_player_fetcher_notification_message)); getString(R.string.preferred_player_fetcher_notification_title))
.setContentText(
getString(R.string.preferred_player_fetcher_notification_message));
} }
} }
@ -625,7 +743,7 @@ public class RouterActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Nullable @Nullable
private String getUrl(Intent intent) { private String getUrl(final Intent intent) {
String foundUrl = null; String foundUrl = null;
if (intent.getData() != null) { if (intent.getData() != null) {
// Called from another app // Called from another app

View File

@ -4,21 +4,22 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import com.google.android.material.tabs.TabLayout; import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import com.google.android.material.tabs.TabLayout;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -27,26 +28,41 @@ import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class AboutActivity extends AppCompatActivity { public class AboutActivity extends AppCompatActivity {
/** /**
* List of all software components * List of all software components.
*/ */
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{ private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2), new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3), "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT), new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2), "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2), new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2), "https://github.com/jhy/jsoup", StandardLicenses.MIT),
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2), new SoftwareComponent("Rhino", "2015", "Mozilla",
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2), "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2), new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2), "http://www.acra.ch", StandardLicenses.APACHE2),
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2), new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2), "https://github.com/nostra13/Android-Universal-Image-Loader",
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2), StandardLicenses.APACHE2),
new SoftwareComponent("Markwon", "2017 - 2020", "Noties", "https://github.com/noties/Markwon", StandardLicenses.APACHE2), new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
new SoftwareComponent("Groupie", "2016", "Lisa Wray", "https://github.com/lisawray/groupie", StandardLicenses.MIT) "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
"https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc",
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors",
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton",
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
new SoftwareComponent("Markwon", "2017 - 2020", "Noties",
"https://github.com/noties/Markwon", StandardLicenses.APACHE2),
new SoftwareComponent("Groupie", "2016", "Lisa Wray",
"https://github.com/lisawray/groupie", StandardLicenses.MIT)
}; };
/** /**
@ -65,7 +81,7 @@ public class AboutActivity extends AppCompatActivity {
private ViewPager mViewPager; private ViewPager mViewPager;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
@ -88,10 +104,8 @@ public class AboutActivity extends AppCompatActivity {
tabLayout.setupWithViewPager(mViewPager); tabLayout.setupWithViewPager(mViewPager);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId(); int id = item.getItemId();
switch (id) { switch (id) {
@ -107,21 +121,20 @@ public class AboutActivity extends AppCompatActivity {
* A placeholder fragment containing a simple view. * A placeholder fragment containing a simple view.
*/ */
public static class AboutFragment extends Fragment { public static class AboutFragment extends Fragment {
public AboutFragment() { }
public AboutFragment() {
}
/** /**
* Returns a new instance of this fragment for the given section * Created a new instance of this fragment for the given section number.
* number. *
* @return New instance of {@link AboutFragment}
*/ */
public static AboutFragment newInstance() { public static AboutFragment newInstance() {
return new AboutFragment(); return new AboutFragment();
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) { final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_about, container, false); View rootView = inflater.inflate(R.layout.fragment_about, container, false);
Context context = this.getContext(); Context context = this.getContext();
@ -129,40 +142,42 @@ public class AboutActivity extends AppCompatActivity {
version.setText(BuildConfig.VERSION_NAME); version.setText(BuildConfig.VERSION_NAME);
View githubLink = rootView.findViewById(R.id.github_link); View githubLink = rootView.findViewById(R.id.github_link);
githubLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.github_url), context)); githubLink.setOnClickListener(nv ->
openWebsite(context.getString(R.string.github_url), context));
View donationLink = rootView.findViewById(R.id.donation_link); View donationLink = rootView.findViewById(R.id.donation_link);
donationLink.setOnClickListener(v -> openWebsite(context.getString(R.string.donation_url), context)); donationLink.setOnClickListener(v ->
openWebsite(context.getString(R.string.donation_url), context));
View websiteLink = rootView.findViewById(R.id.website_link); View websiteLink = rootView.findViewById(R.id.website_link);
websiteLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.website_url), context)); websiteLink.setOnClickListener(nv ->
openWebsite(context.getString(R.string.website_url), context));
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link); View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
privacyPolicyLink.setOnClickListener(v -> openWebsite(context.getString(R.string.privacy_policy_url), context)); privacyPolicyLink.setOnClickListener(v ->
openWebsite(context.getString(R.string.privacy_policy_url), context));
return rootView; return rootView;
} }
private void openWebsite(String url, Context context) { private void openWebsite(final String url, final Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent); context.startActivity(intent);
} }
} }
/** /**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages. * one of the sections/tabs/pages.
*/ */
public class SectionsPagerAdapter extends FragmentPagerAdapter { public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(final FragmentManager fm) {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm); super(fm);
} }
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(final int position) {
switch (position) { switch (position) {
case 0: case 0:
return AboutFragment.newInstance(); return AboutFragment.newInstance();
@ -179,7 +194,7 @@ public class AboutActivity extends AppCompatActivity {
} }
@Override @Override
public CharSequence getPageTitle(int position) { public CharSequence getPageTitle(final int position) {
switch (position) { switch (position) {
case 0: case 0:
return getString(R.string.tab_about); return getString(R.string.tab_about);

View File

@ -5,18 +5,17 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
/** /**
* A software license * Class for storing information about a software license.
*/ */
public class License implements Parcelable { public class License implements Parcelable {
public static final Creator<License> CREATOR = new Creator<License>() { public static final Creator<License> CREATOR = new Creator<License>() {
@Override @Override
public License createFromParcel(Parcel source) { public License createFromParcel(final Parcel source) {
return new License(source); return new License(source);
} }
@Override @Override
public License[] newArray(int size) { public License[] newArray(final int size) {
return new License[size]; return new License[size];
} }
}; };
@ -24,16 +23,22 @@ public class License implements Parcelable {
private final String name; private final String name;
private String filename; private String filename;
public License(String name, String abbreviation, String filename) { public License(final String name, final String abbreviation, final String filename) {
if(name == null) throw new NullPointerException("name is null"); if (name == null) {
if(abbreviation == null) throw new NullPointerException("abbreviation is null"); throw new NullPointerException("name is null");
if(filename == null) throw new NullPointerException("filename is null"); }
if (abbreviation == null) {
throw new NullPointerException("abbreviation is null");
}
if (filename == null) {
throw new NullPointerException("filename is null");
}
this.name = name; this.name = name;
this.filename = filename; this.filename = filename;
this.abbreviation = abbreviation; this.abbreviation = abbreviation;
} }
protected License(Parcel in) { protected License(final Parcel in) {
this.filename = in.readString(); this.filename = in.readString();
this.abbreviation = in.readString(); this.abbreviation = in.readString();
this.name = in.readString(); this.name = in.readString();
@ -61,7 +66,7 @@ public class License implements Parcelable {
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(this.filename); dest.writeString(this.filename);
dest.writeString(this.abbreviation); dest.writeString(this.abbreviation);
dest.writeString(this.name); dest.writeString(this.name);

View File

@ -5,26 +5,32 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.view.*;
import android.widget.TextView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
/** /**
* Fragment containing the software licenses * Fragment containing the software licenses.
*/ */
public class LicenseFragment extends Fragment { public class LicenseFragment extends Fragment {
private static final String ARG_COMPONENTS = "components"; private static final String ARG_COMPONENTS = "components";
private SoftwareComponent[] softwareComponents; private SoftwareComponent[] softwareComponents;
private SoftwareComponent mComponentForContextMenu; private SoftwareComponent mComponentForContextMenu;
public static LicenseFragment newInstance(SoftwareComponent[] softwareComponents) { public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
if(softwareComponents == null) { if (softwareComponents == null) {
throw new NullPointerException("softwareComponents is null"); throw new NullPointerException("softwareComponents is null");
} }
LicenseFragment fragment = new LicenseFragment(); LicenseFragment fragment = new LicenseFragment();
@ -35,23 +41,25 @@ public class LicenseFragment extends Fragment {
} }
/** /**
* Shows a popup containing the license * Shows a popup containing the license.
*
* @param context the context to use * @param context the context to use
* @param license the license to show * @param license the license to show
*/ */
public static void showLicense(Context context, License license) { public static void showLicense(final Context context, final License license) {
new LicenseFragmentHelper((Activity) context).execute(license); new LicenseFragmentHelper((Activity) context).execute(license);
} }
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
softwareComponents = (SoftwareComponent[]) getArguments().getParcelableArray(ARG_COMPONENTS); softwareComponents = (SoftwareComponent[]) getArguments()
.getParcelableArray(ARG_COMPONENTS);
// Sort components by name // Sort components by name
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() { Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
@Override @Override
public int compare(SoftwareComponent o1, SoftwareComponent o2) { public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
return o1.getName().compareTo(o2.getName()); return o1.getName().compareTo(o2.getName());
} }
}); });
@ -59,7 +67,8 @@ public class LicenseFragment extends Fragment {
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false); View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components); ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
@ -67,7 +76,8 @@ public class LicenseFragment extends Fragment {
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener()); licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
for (final SoftwareComponent component : softwareComponents) { for (final SoftwareComponent component : softwareComponents) {
View componentView = inflater.inflate(R.layout.item_software_component, container, false); View componentView = inflater
.inflate(R.layout.item_software_component, container, false);
TextView softwareName = componentView.findViewById(R.id.name); TextView softwareName = componentView.findViewById(R.id.name);
TextView copyright = componentView.findViewById(R.id.copyright); TextView copyright = componentView.findViewById(R.id.copyright);
softwareName.setText(component.getName()); softwareName.setText(component.getName());
@ -79,7 +89,7 @@ public class LicenseFragment extends Fragment {
componentView.setTag(component); componentView.setTag(component);
componentView.setOnClickListener(new View.OnClickListener() { componentView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(final View v) {
Context context = v.getContext(); Context context = v.getContext();
if (context != null) { if (context != null) {
showLicense(context, component.getLicense()); showLicense(context, component.getLicense());
@ -93,7 +103,8 @@ public class LicenseFragment extends Fragment {
} }
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { public void onCreateContextMenu(final ContextMenu menu, final View v,
final ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = getActivity().getMenuInflater(); MenuInflater inflater = getActivity().getMenuInflater();
SoftwareComponent component = (SoftwareComponent) v.getTag(); SoftwareComponent component = (SoftwareComponent) v.getTag();
menu.setHeaderTitle(component.getName()); menu.setHeaderTitle(component.getName());
@ -103,7 +114,7 @@ public class LicenseFragment extends Fragment {
} }
@Override @Override
public boolean onContextItemSelected(MenuItem item) { public boolean onContextItemSelected(final MenuItem item) {
// item.getMenuInfo() is null so we use the tag of the view // item.getMenuInfo() is null so we use the tag of the view
final SoftwareComponent component = mComponentForContextMenu; final SoftwareComponent component = mComponentForContextMenu;
if (component == null) { if (component == null) {
@ -119,14 +130,14 @@ public class LicenseFragment extends Fragment {
return false; return false;
} }
private void openWebsite(String componentLink) { private void openWebsite(final String componentLink) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink)); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
startActivity(browserIntent); startActivity(browserIntent);
} }
private static class OnReadFullLicenseClickListener implements View.OnClickListener { private static class OnReadFullLicenseClickListener implements View.OnClickListener {
@Override @Override
public void onClick(View v) { public void onClick(final View v) {
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3); LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
} }
} }

View File

@ -2,30 +2,103 @@ package org.schabi.newpipe.about;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.webkit.WebView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import android.webkit.WebView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> { public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
private final WeakReference<Activity> weakReference;
final WeakReference<Activity> weakReference;
private License license; private License license;
public LicenseFragmentHelper(@Nullable Activity activity) { public LicenseFragmentHelper(@Nullable final Activity activity) {
weakReference = new WeakReference<>(activity); weakReference = new WeakReference<>(activity);
} }
private static String getFinishString(final Activity activity) {
return activity.getApplicationContext().getResources().getString(R.string.finish);
}
/**
* @param context the context to use
* @param license the license
* @return String which contains a HTML formatted license page
* styled according to the context's theme
*/
public static String getFormattedLicense(final Context context, final License license) {
if (context == null) {
throw new NullPointerException("context is null");
}
if (license == null) {
throw new NullPointerException("license is null");
}
StringBuilder licenseContent = new StringBuilder();
String webViewData;
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
String[] insert = licenseContent.toString().split("</head>");
webViewData = insert[0] + "<style type=\"text/css\">"
+ getLicenseStylesheet(context) + "</style></head>"
+ insert[1];
} catch (Exception e) {
throw new NullPointerException("could not get license file:"
+ getLicenseStylesheet(context));
}
return webViewData;
}
/**
* @param context
* @return String which is a CSS stylesheet according to the context's theme
*/
public static String getLicenseStylesheet(final Context context) {
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
return "body{padding:12px 15px;margin:0;background:#"
+ getHexRGBColor(context, isLightTheme
? R.color.light_license_background_color
: R.color.dark_license_background_color)
+ ";color:#"
+ getHexRGBColor(context, isLightTheme
? R.color.light_license_text_color
: R.color.dark_license_text_color) + ";}"
+ "a[href]{color:#"
+ getHexRGBColor(context, isLightTheme
? R.color.light_youtube_primary_color
: R.color.dark_youtube_primary_color) + ";}"
+ "pre{white-space: pre-wrap;}";
}
/**
* Cast R.color to a hexadecimal color value.
*
* @param context the context to use
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
public static String getHexRGBColor(final Context context, final int color) {
return context.getResources().getString(color).substring(3);
}
@Nullable @Nullable
private Activity getActivity() { private Activity getActivity() {
Activity activity = weakReference.get(); Activity activity = weakReference.get();
@ -38,13 +111,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
} }
@Override @Override
protected Integer doInBackground(Object... objects) { protected Integer doInBackground(final Object... objects) {
license = (License) objects[0]; license = (License) objects[0];
return 1; return 1;
} }
@Override @Override
protected void onPostExecute(Integer result) { protected void onPostExecute(final Integer result) {
Activity activity = getActivity(); Activity activity = getActivity();
if (activity == null) { if (activity == null) {
return; return;
@ -63,74 +136,4 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
alert.show(); alert.show();
} }
private static String getFinishString(Activity activity) {
return activity.getApplicationContext().getResources().getString(R.string.finish);
}
/**
* @param context the context to use
* @param license the license
* @return String which contains a HTML formatted license page styled according to the context's theme
*/
public static String getFormattedLicense(Context context, License license) {
if(context == null) {
throw new NullPointerException("context is null");
}
if(license == null) {
throw new NullPointerException("license is null");
}
StringBuilder licenseContent = new StringBuilder();
String webViewData;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(context.getAssets().open(license.getFilename()), "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
String[] insert = licenseContent.toString().split("</head>");
webViewData = insert[0] + "<style type=\"text/css\">"
+ getLicenseStylesheet(context) + "</style></head>"
+ insert[1];
} catch (Exception e) {
throw new NullPointerException("could not get license file:" + getLicenseStylesheet(context));
}
return webViewData;
}
/**
*
* @param context
* @return String which is a CSS stylesheet according to the context's theme
*/
public static String getLicenseStylesheet(Context context) {
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
return "body{padding:12px 15px;margin:0;background:#"
+ getHexRGBColor(context, isLightTheme
? R.color.light_license_background_color
: R.color.dark_license_background_color)
+ ";color:#"
+ getHexRGBColor(context, isLightTheme
? R.color.light_license_text_color
: R.color.dark_license_text_color) + ";}"
+ "a[href]{color:#"
+ getHexRGBColor(context, isLightTheme
? R.color.light_youtube_primary_color
: R.color.dark_youtube_primary_color) + ";}"
+ "pre{white-space: pre-wrap;}";
}
/**
* Cast R.color to a hexadecimal color value
* @param context the context to use
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
public static String getHexRGBColor(Context context, int color) {
return context.getResources().getString(color).substring(3);
}
} }

View File

@ -4,19 +4,44 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
public class SoftwareComponent implements Parcelable { public class SoftwareComponent implements Parcelable {
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() { public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
@Override @Override
public SoftwareComponent createFromParcel(Parcel source) { public SoftwareComponent createFromParcel(final Parcel source) {
return new SoftwareComponent(source); return new SoftwareComponent(source);
} }
@Override @Override
public SoftwareComponent[] newArray(int size) { public SoftwareComponent[] newArray(final int size) {
return new SoftwareComponent[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() { public String getName() {
return name; return name;
} }
@ -37,31 +62,6 @@ public class SoftwareComponent implements Parcelable {
return version; return version;
} }
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(String name, String years, String copyrightOwner, String link, License license) {
this.name = name;
this.years = years;
this.copyrightOwner = copyrightOwner;
this.link = link;
this.license = license;
this.version = null;
}
protected SoftwareComponent(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 License getLicense() { public License getLicense() {
return license; return license;
} }
@ -72,7 +72,7 @@ public class SoftwareComponent implements Parcelable {
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(name); dest.writeString(name);
dest.writeParcelable(license, flags); dest.writeParcelable(license, flags);
dest.writeString(copyrightOwner); dest.writeString(copyrightOwner);

View File

@ -1,12 +1,19 @@
package org.schabi.newpipe.about; package org.schabi.newpipe.about;
/** /**
* Standard software licenses * Class containing information about standard software licenses.
*/ */
public final class StandardLicenses { public final class StandardLicenses {
public static final License GPL2 = new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html"); public static final License GPL2
public static final License GPL3 = new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html"); = new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html");
public static final License APACHE2 = new License("Apache License, Version 2.0", "ALv2", "apache2.html"); public static final License GPL3
public static final License MPL2 = new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html"); = new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
public static final License MIT = new License("MIT License", "MIT", "mit.html"); public static final License APACHE2
= new License("Apache License, Version 2.0", "ALv2", "apache2.html");
public static final License MPL2
= new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
public static final License MIT
= new License("MIT License", "MIT", "mit.html");
private StandardLicenses() { }
} }

View File

@ -46,14 +46,20 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract SearchHistoryDAO searchHistoryDAO(); public abstract SearchHistoryDAO searchHistoryDAO();
public abstract StreamDAO streamDAO(); public abstract StreamDAO streamDAO();
public abstract StreamHistoryDAO streamHistoryDAO(); public abstract StreamHistoryDAO streamHistoryDAO();
public abstract StreamStateDAO streamStateDAO(); public abstract StreamStateDAO streamStateDAO();
public abstract PlaylistDAO playlistDAO(); public abstract PlaylistDAO playlistDAO();
public abstract PlaylistStreamDAO playlistStreamDAO(); public abstract PlaylistStreamDAO playlistStreamDAO();
public abstract PlaylistRemoteDAO playlistRemoteDAO(); public abstract PlaylistRemoteDAO playlistRemoteDAO();
public abstract FeedDAO feedDAO(); public abstract FeedDAO feedDAO();
public abstract FeedGroupDAO feedGroupDAO(); public abstract FeedGroupDAO feedGroupDAO();
public abstract SubscriptionDAO subscriptionDAO(); public abstract SubscriptionDAO subscriptionDAO();
} }

View File

@ -15,13 +15,13 @@ import io.reactivex.Flowable;
public interface BasicDAO<Entity> { public interface BasicDAO<Entity> {
/* Inserts */ /* Inserts */
@Insert(onConflict = OnConflictStrategy.FAIL) @Insert(onConflict = OnConflictStrategy.FAIL)
long insert(final Entity entity); long insert(Entity entity);
@Insert(onConflict = OnConflictStrategy.FAIL) @Insert(onConflict = OnConflictStrategy.FAIL)
List<Long> insertAll(final Entity... entities); List<Long> insertAll(Entity... entities);
@Insert(onConflict = OnConflictStrategy.FAIL) @Insert(onConflict = OnConflictStrategy.FAIL)
List<Long> insertAll(final Collection<Entity> entities); List<Long> insertAll(Collection<Entity> entities);
/* Searches */ /* Searches */
Flowable<List<Entity>> getAll(); Flowable<List<Entity>> getAll();
@ -30,17 +30,17 @@ public interface BasicDAO<Entity> {
/* Deletes */ /* Deletes */
@Delete @Delete
void delete(final Entity entity); void delete(Entity entity);
@Delete @Delete
int delete(final Collection<Entity> entities); int delete(Collection<Entity> entities);
int deleteAll(); int deleteAll();
/* Updates */ /* Updates */
@Update @Update
int update(final Entity entity); int update(Entity entity);
@Update @Update
void update(final Collection<Entity> entities); void update(Collection<Entity> entities);
} }

View File

@ -7,47 +7,52 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon;
import java.util.Date; import java.util.Date;
public class Converters { public final class Converters {
private Converters() { }
/** /**
* Convert a long value to a date * Convert a long value to a date.
*
* @param value the long value * @param value the long value
* @return the date * @return the date
*/ */
@TypeConverter @TypeConverter
public static Date fromTimestamp(Long value) { public static Date fromTimestamp(final Long value) {
return value == null ? null : new Date(value); return value == null ? null : new Date(value);
} }
/** /**
* Convert a date to a long value * Convert a date to a long value.
*
* @param date the date * @param date the date
* @return the long value * @return the long value
*/ */
@TypeConverter @TypeConverter
public static Long dateToTimestamp(Date date) { public static Long dateToTimestamp(final Date date) {
return date == null ? null : date.getTime(); return date == null ? null : date.getTime();
} }
@TypeConverter @TypeConverter
public static StreamType streamTypeOf(String value) { public static StreamType streamTypeOf(final String value) {
return StreamType.valueOf(value); return StreamType.valueOf(value);
} }
@TypeConverter @TypeConverter
public static String stringOf(StreamType streamType) { public static String stringOf(final StreamType streamType) {
return streamType.name(); return streamType.name();
} }
@TypeConverter @TypeConverter
public static Integer integerOf(FeedGroupIcon feedGroupIcon) { public static Integer integerOf(final FeedGroupIcon feedGroupIcon) {
return feedGroupIcon.getId(); return feedGroupIcon.getId();
} }
@TypeConverter @TypeConverter
public static FeedGroupIcon feedGroupIconOf(Integer id) { public static FeedGroupIcon feedGroupIconOf(final Integer id) {
for (FeedGroupIcon icon : FeedGroupIcon.values()) { for (FeedGroupIcon icon : FeedGroupIcon.values()) {
if (icon.getId() == id) return icon; if (icon.getId() == id) {
return icon;
}
} }
throw new IllegalArgumentException("There's no feed group icon with the id \"" + id + "\""); throw new IllegalArgumentException("There's no feed group icon with the id \"" + id + "\"");

View File

@ -1,6 +1,8 @@
package org.schabi.newpipe.database; package org.schabi.newpipe.database;
public interface LocalItem { public interface LocalItem {
LocalItemType getLocalItemType();
enum LocalItemType { enum LocalItemType {
PLAYLIST_LOCAL_ITEM, PLAYLIST_LOCAL_ITEM,
PLAYLIST_REMOTE_ITEM, PLAYLIST_REMOTE_ITEM,
@ -8,6 +10,4 @@ public interface LocalItem {
PLAYLIST_STREAM_ITEM, PLAYLIST_STREAM_ITEM,
STATISTIC_STREAM_ITEM, STATISTIC_STREAM_ITEM,
} }
LocalItemType getLocalItemType();
} }

View File

@ -1,72 +1,103 @@
package org.schabi.newpipe.database; package org.schabi.newpipe.database;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.room.migration.Migration;
import androidx.annotation.NonNull;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
public class Migrations { public final class Migrations {
public static final int DB_VER_1 = 1; public static final int DB_VER_1 = 1;
public static final int DB_VER_2 = 2; public static final int DB_VER_2 = 2;
public static final int DB_VER_3 = 3; public static final int DB_VER_3 = 3;
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
private static final String TAG = Migrations.class.getName(); private static final String TAG = Migrations.class.getName();
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) { public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
@Override @Override
public void migrate(@NonNull SupportSQLiteDatabase database) { public void migrate(@NonNull final SupportSQLiteDatabase database) {
if(DEBUG) { if (DEBUG) {
Log.d(TAG, "Start migrating database"); Log.d(TAG, "Start migrating database");
} }
/* /*
* Unfortunately these queries must be hardcoded due to the possibility of * Unfortunately these queries must be hardcoded due to the possibility of
* schema and names changing at a later date, thus invalidating the older migration * schema and names changing at a later date, thus invalidating the older migration
* scripts if they are not hardcoded. * scripts if they are not hardcoded.
* */ * */
// Not much we can do about this, since room doesn't create tables before migration. // Not much we can do about this, since room doesn't create tables before migration.
// It's either this or blasting the entire database anew. // It's either this or blasting the entire database anew.
database.execSQL("CREATE INDEX `index_search_history_search` ON `search_history` (`search`)"); database.execSQL("CREATE INDEX `index_search_history_search` "
database.execSQL("CREATE TABLE IF NOT EXISTS `streams` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, `stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, `thumbnail_url` TEXT)"); + "ON `search_history` (`search`)");
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` ON `streams` (`service_id`, `url`)"); database.execSQL("CREATE TABLE IF NOT EXISTS `streams` "
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
database.execSQL("CREATE INDEX `index_stream_history_stream_id` ON `stream_history` (`stream_id`)"); + "`service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, "
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); + "`stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, "
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)"); + "`thumbnail_url` TEXT)");
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` "
+ "ON `streams` (`service_id`, `url`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` "
+ "(`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, "
+ "`repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), "
+ "FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) "
+ "ON UPDATE CASCADE ON DELETE CASCADE )");
database.execSQL("CREATE INDEX `index_stream_history_stream_id` "
+ "ON `stream_history` (`stream_id`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` "
+ "(`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, "
+ "PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) "
+ "REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`name` TEXT, `thumbnail_url` TEXT)");
database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)"); database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` "
database.execSQL("CREATE UNIQUE INDEX `index_playlist_stream_join_playlist_id_join_index` ON `playlist_stream_join` (`playlist_id`, `join_index`)"); + "(`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, "
database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` ON `playlist_stream_join` (`stream_id`)"); + "`join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), "
database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)"); + "FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) "
database.execSQL("CREATE INDEX `index_remote_playlists_name` ON `remote_playlists` (`name`)"); + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` ON `remote_playlists` (`service_id`, `url`)"); + "FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) "
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
database.execSQL("CREATE UNIQUE INDEX "
+ "`index_playlist_stream_join_playlist_id_join_index` "
+ "ON `playlist_stream_join` (`playlist_id`, `join_index`)");
database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` "
+ "ON `playlist_stream_join` (`stream_id`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
+ "`thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)");
database.execSQL("CREATE INDEX `index_remote_playlists_name` "
+ "ON `remote_playlists` (`name`)");
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
+ "ON `remote_playlists` (`service_id`, `url`)");
// Populate streams table with existing entries in watch history // Populate streams table with existing entries in watch history
// Latest data first, thus ignoring older entries with the same indices // Latest data first, thus ignoring older entries with the same indices
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, " + database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, "
"stream_type, duration, uploader, thumbnail_url) " + + "stream_type, duration, uploader, thumbnail_url) "
"SELECT service_id, url, title, 'VIDEO_STREAM', duration, " + + "SELECT service_id, url, title, 'VIDEO_STREAM', duration, "
"uploader, thumbnail_url " + + "uploader, thumbnail_url "
"FROM watch_history " + + "FROM watch_history "
"ORDER BY creation_date DESC"); + "ORDER BY creation_date DESC");
// Once the streams have PKs, join them with the normalized history table // Once the streams have PKs, join them with the normalized history table
// and populate it with the remaining data from watch history // and populate it with the remaining data from watch history
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" + database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)"
"SELECT uid, creation_date, 1 " + + "SELECT uid, creation_date, 1 "
"FROM watch_history INNER JOIN streams " + + "FROM watch_history INNER JOIN streams "
"ON watch_history.service_id == streams.service_id " + + "ON watch_history.service_id == streams.service_id "
"AND watch_history.url == streams.url " + + "AND watch_history.url == streams.url "
"ORDER BY creation_date DESC"); + "ORDER BY creation_date DESC");
database.execSQL("DROP TABLE IF EXISTS watch_history"); database.execSQL("DROP TABLE IF EXISTS watch_history");
if(DEBUG) { if (DEBUG) {
Log.d(TAG, "Stop migrating database"); Log.d(TAG, "Stop migrating database");
} }
} }
@ -74,37 +105,60 @@ public class Migrations {
public static final Migration MIGRATION_2_3 = new Migration(DB_VER_2, DB_VER_3) { public static final Migration MIGRATION_2_3 = new Migration(DB_VER_2, DB_VER_3) {
@Override @Override
public void migrate(@NonNull SupportSQLiteDatabase database) { public void migrate(@NonNull final SupportSQLiteDatabase database) {
// Add NOT NULLs and new fields // Add NOT NULLs and new fields
database.execSQL("CREATE TABLE IF NOT EXISTS streams_new " + database.execSQL("CREATE TABLE IF NOT EXISTS streams_new "
"(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, service_id INTEGER NOT NULL, url TEXT NOT NULL, title TEXT NOT NULL, stream_type TEXT NOT NULL," + + "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
" duration INTEGER NOT NULL, uploader TEXT NOT NULL, thumbnail_url TEXT, view_count INTEGER, textual_upload_date TEXT, upload_date INTEGER," + + "service_id INTEGER NOT NULL, url TEXT NOT NULL, title TEXT NOT NULL, "
" is_upload_date_approximation INTEGER)"); + "stream_type TEXT NOT NULL, duration INTEGER NOT NULL, "
+ "uploader TEXT NOT NULL, thumbnail_url TEXT, view_count INTEGER, "
+ "textual_upload_date TEXT, upload_date INTEGER, "
+ "is_upload_date_approximation INTEGER)");
database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type," + database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type, "
"duration, uploader, thumbnail_url, view_count," + + "duration, uploader, thumbnail_url, view_count, textual_upload_date, "
"textual_upload_date, upload_date, is_upload_date_approximation) " + + "upload_date, is_upload_date_approximation) "
"SELECT uid, service_id, url, ifnull(title, ''), ifnull(stream_type, 'VIDEO_STREAM')," + + "SELECT uid, service_id, url, ifnull(title, ''), "
"ifnull(duration, 0), ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL," + + "ifnull(stream_type, 'VIDEO_STREAM'), ifnull(duration, 0), "
"NULL, NULL, NULL " + + "ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL, NULL, NULL, NULL "
"FROM streams " + + "FROM streams WHERE url IS NOT NULL");
"WHERE url IS NOT NULL");
database.execSQL("DROP TABLE streams"); database.execSQL("DROP TABLE streams");
database.execSQL("ALTER TABLE streams_new RENAME TO streams"); database.execSQL("ALTER TABLE streams_new RENAME TO streams");
database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url ON streams (service_id, url)"); database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url "
+ "ON streams (service_id, url)");
// Tables for feed feature // Tables for feed feature
database.execSQL("CREATE TABLE IF NOT EXISTS feed (stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(stream_id, subscription_id), FOREIGN KEY(stream_id) REFERENCES streams(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); database.execSQL("CREATE TABLE IF NOT EXISTS feed "
+ "(stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, "
+ "PRIMARY KEY(stream_id, subscription_id), "
+ "FOREIGN KEY(stream_id) REFERENCES streams(uid) "
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
+ "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) "
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)"); database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)");
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)"); database.execSQL("CREATE TABLE IF NOT EXISTS feed_group "
+ "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, "
+ "icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)");
database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)"); database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)");
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join (group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(group_id, subscription_id), FOREIGN KEY(group_id) REFERENCES feed_group(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join "
database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id ON feed_group_subscription_join (subscription_id)"); + "(group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, "
database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated (subscription_id INTEGER NOT NULL, last_updated INTEGER, PRIMARY KEY(subscription_id), FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); + "PRIMARY KEY(group_id, subscription_id), "
+ "FOREIGN KEY(group_id) REFERENCES feed_group(uid) "
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
+ "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) "
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id "
+ "ON feed_group_subscription_join (subscription_id)");
database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated "
+ "(subscription_id INTEGER NOT NULL, last_updated INTEGER, "
+ "PRIMARY KEY(subscription_id), "
+ "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) "
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
} }
}; };
private Migrations() { }
} }

View File

@ -1,8 +1,8 @@
package org.schabi.newpipe.database.history.dao; package org.schabi.newpipe.database.history.dao;
import androidx.annotation.Nullable;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Query; import androidx.room.Query;
import androidx.annotation.Nullable;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
@ -18,11 +18,10 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE
@Dao @Dao
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> { public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC"; String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
@Query("SELECT * FROM " + TABLE_NAME + @Query("SELECT * FROM " + TABLE_NAME
" WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
@Nullable @Nullable
SearchHistoryEntry getLatestEntry(); SearchHistoryEntry getLatestEntry();
@ -37,13 +36,16 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
@Override @Override
Flowable<List<SearchHistoryEntry>> getAll(); Flowable<List<SearchHistoryEntry>> getAll();
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit") @Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE
+ " LIMIT :limit")
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit); Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) @Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
@Override @Override
Flowable<List<SearchHistoryEntry>> listByService(int serviceId); Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit") @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
+ " GROUP BY " + SEARCH + " LIMIT :limit")
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit); Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
} }

View File

@ -1,32 +1,31 @@
package org.schabi.newpipe.database.history.dao; package org.schabi.newpipe.database.history.dao;
import androidx.annotation.Nullable;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Query; import androidx.room.Query;
import androidx.annotation.Nullable;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import java.util.List; import java.util.List;
import io.reactivex.Flowable; import io.reactivex.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;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT;
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE; import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT; 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_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE; import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
@Dao @Dao
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> { public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + @Query("SELECT * FROM " + STREAM_HISTORY_TABLE
" WHERE " + STREAM_ACCESS_DATE + " = " + + " WHERE " + STREAM_ACCESS_DATE + " = "
"(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")") + "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
@Override @Override
@Nullable @Nullable
public abstract StreamHistoryEntity getLatestEntry(); public abstract StreamHistoryEntity getLatestEntry();
@ -40,33 +39,33 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
public abstract int deleteAll(); public abstract int deleteAll();
@Override @Override
public Flowable<List<StreamHistoryEntity>> listByService(int serviceId) { public Flowable<List<StreamHistoryEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Query("SELECT * FROM " + STREAM_TABLE + @Query("SELECT * FROM " + STREAM_TABLE
" INNER JOIN " + STREAM_HISTORY_TABLE + + " INNER JOIN " + STREAM_HISTORY_TABLE
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID + + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
" ORDER BY " + STREAM_ACCESS_DATE + " DESC") + " ORDER BY " + STREAM_ACCESS_DATE + " DESC")
public abstract Flowable<List<StreamHistoryEntry>> getHistory(); public abstract Flowable<List<StreamHistoryEntry>> getHistory();
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID
" = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") + " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
@Nullable @Nullable
public abstract StreamHistoryEntity getLatestEntry(final long streamId); public abstract StreamHistoryEntity getLatestEntry(long streamId);
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") @Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract int deleteStreamHistory(final long streamId); public abstract int deleteStreamHistory(long streamId);
@Query("SELECT * FROM " + STREAM_TABLE + @Query("SELECT * FROM " + STREAM_TABLE
// Select the latest entry and watch count for each stream id on history table // Select the latest entry and watch count for each stream id on history table
" INNER JOIN " + + " INNER JOIN "
"(SELECT " + JOIN_STREAM_ID + ", " + + "(SELECT " + JOIN_STREAM_ID + ", "
" MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " + + " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", "
" SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT + + " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
" FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" + + " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID) + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics(); public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
} }

View File

@ -13,7 +13,6 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARC
@Entity(tableName = SearchHistoryEntry.TABLE_NAME, @Entity(tableName = SearchHistoryEntry.TABLE_NAME,
indices = {@Index(value = SEARCH)}) indices = {@Index(value = SEARCH)})
public class SearchHistoryEntry { public class SearchHistoryEntry {
public static final String ID = "id"; public static final String ID = "id";
public static final String TABLE_NAME = "search_history"; public static final String TABLE_NAME = "search_history";
public static final String SERVICE_ID = "service_id"; public static final String SERVICE_ID = "service_id";
@ -33,7 +32,7 @@ public class SearchHistoryEntry {
@ColumnInfo(name = SEARCH) @ColumnInfo(name = SEARCH)
private String search; private String search;
public SearchHistoryEntry(Date creationDate, int serviceId, String search) { public SearchHistoryEntry(final Date creationDate, final int serviceId, final String search) {
this.serviceId = serviceId; this.serviceId = serviceId;
this.creationDate = creationDate; this.creationDate = creationDate;
this.search = search; this.search = search;
@ -43,7 +42,7 @@ public class SearchHistoryEntry {
return id; return id;
} }
public void setId(long id) { public void setId(final long id) {
this.id = id; this.id = id;
} }
@ -51,7 +50,7 @@ public class SearchHistoryEntry {
return creationDate; return creationDate;
} }
public void setCreationDate(Date creationDate) { public void setCreationDate(final Date creationDate) {
this.creationDate = creationDate; this.creationDate = creationDate;
} }
@ -59,7 +58,7 @@ public class SearchHistoryEntry {
return serviceId; return serviceId;
} }
public void setServiceId(int serviceId) { public void setServiceId(final int serviceId) {
this.serviceId = serviceId; this.serviceId = serviceId;
} }
@ -67,13 +66,13 @@ public class SearchHistoryEntry {
return search; return search;
} }
public void setSearch(String search) { public void setSearch(final String search) {
this.search = search; this.search = search;
} }
@Ignore @Ignore
public boolean hasEqualValues(SearchHistoryEntry otherEntry) { public boolean hasEqualValues(final SearchHistoryEntry otherEntry) {
return getServiceId() == otherEntry.getServiceId() && return getServiceId() == otherEntry.getServiceId()
getSearch().equals(otherEntry.getSearch()); && getSearch().equals(otherEntry.getSearch());
} }
} }

View File

@ -1,20 +1,20 @@
package org.schabi.newpipe.database.history.model; package org.schabi.newpipe.database.history.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Ignore; import androidx.room.Ignore;
import androidx.room.Index; import androidx.room.Index;
import androidx.annotation.NonNull;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
import java.util.Date; import java.util.Date;
import static androidx.room.ForeignKey.CASCADE; import static androidx.room.ForeignKey.CASCADE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
@Entity(tableName = STREAM_HISTORY_TABLE, @Entity(tableName = STREAM_HISTORY_TABLE,
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE}, primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
@ -27,10 +27,10 @@ import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STRE
onDelete = CASCADE, onUpdate = CASCADE) onDelete = CASCADE, onUpdate = CASCADE)
}) })
public class StreamHistoryEntity { public class StreamHistoryEntity {
final public static String STREAM_HISTORY_TABLE = "stream_history"; public static final String STREAM_HISTORY_TABLE = "stream_history";
final public static String JOIN_STREAM_ID = "stream_id"; public static final String JOIN_STREAM_ID = "stream_id";
final public static String STREAM_ACCESS_DATE = "access_date"; public static final String STREAM_ACCESS_DATE = "access_date";
final public static String STREAM_REPEAT_COUNT = "repeat_count"; public static final String STREAM_REPEAT_COUNT = "repeat_count";
@ColumnInfo(name = JOIN_STREAM_ID) @ColumnInfo(name = JOIN_STREAM_ID)
private long streamUid; private long streamUid;
@ -42,14 +42,15 @@ public class StreamHistoryEntity {
@ColumnInfo(name = STREAM_REPEAT_COUNT) @ColumnInfo(name = STREAM_REPEAT_COUNT)
private long repeatCount; private long repeatCount;
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate, long repeatCount) { public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate,
final long repeatCount) {
this.streamUid = streamUid; this.streamUid = streamUid;
this.accessDate = accessDate; this.accessDate = accessDate;
this.repeatCount = repeatCount; this.repeatCount = repeatCount;
} }
@Ignore @Ignore
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate) { public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate) {
this(streamUid, accessDate, 1); this(streamUid, accessDate, 1);
} }
@ -57,7 +58,7 @@ public class StreamHistoryEntity {
return streamUid; return streamUid;
} }
public void setStreamUid(long streamUid) { public void setStreamUid(final long streamUid) {
this.streamUid = streamUid; this.streamUid = streamUid;
} }
@ -65,7 +66,7 @@ public class StreamHistoryEntity {
return accessDate; return accessDate;
} }
public void setAccessDate(@NonNull Date accessDate) { public void setAccessDate(@NonNull final Date accessDate) {
this.accessDate = accessDate; this.accessDate = accessDate;
} }
@ -73,7 +74,7 @@ public class StreamHistoryEntity {
return repeatCount; return repeatCount;
} }
public void setRepeatCount(long repeatCount) { public void setRepeatCount(final long repeatCount) {
this.repeatCount = repeatCount; this.repeatCount = repeatCount;
} }
} }

View File

@ -7,18 +7,19 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
public class PlaylistMetadataEntry implements PlaylistLocalItem { public class PlaylistMetadataEntry implements PlaylistLocalItem {
final public static String PLAYLIST_STREAM_COUNT = "streamCount"; public static final String PLAYLIST_STREAM_COUNT = "streamCount";
@ColumnInfo(name = PLAYLIST_ID) @ColumnInfo(name = PLAYLIST_ID)
final public long uid; public final long uid;
@ColumnInfo(name = PLAYLIST_NAME) @ColumnInfo(name = PLAYLIST_NAME)
final public String name; public final String name;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL) @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
final public String thumbnailUrl; public final String thumbnailUrl;
@ColumnInfo(name = PLAYLIST_STREAM_COUNT) @ColumnInfo(name = PLAYLIST_STREAM_COUNT)
final public long streamCount; public final long streamCount;
public PlaylistMetadataEntry(long uid, String name, String thumbnailUrl, long streamCount) { public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl,
final long streamCount) {
this.uid = uid; this.uid = uid;
this.name = name; this.name = name;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;

View File

@ -24,13 +24,13 @@ public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> {
public abstract int deleteAll(); public abstract int deleteAll();
@Override @Override
public Flowable<List<PlaylistEntity>> listByService(int serviceId) { public Flowable<List<PlaylistEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") @Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
public abstract Flowable<List<PlaylistEntity>> getPlaylist(final long playlistId); public abstract Flowable<List<PlaylistEntity>> getPlaylist(long playlistId);
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") @Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
public abstract int deletePlaylist(final long playlistId); public abstract int deletePlaylist(long playlistId);
} }

View File

@ -27,22 +27,21 @@ public abstract class PlaylistRemoteDAO implements BasicDAO<PlaylistRemoteEntity
public abstract int deleteAll(); public abstract int deleteAll();
@Override @Override
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
" WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") + " WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
public abstract Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId); public abstract Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
REMOTE_PLAYLIST_URL + " = :url AND " + + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
public abstract Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url); public abstract Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
@Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE + @Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE
" WHERE " + + " WHERE " + REMOTE_PLAYLIST_URL + " = :url "
REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") + "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
abstract Long getPlaylistIdInternal(long serviceId, String url); abstract Long getPlaylistIdInternal(long serviceId, String url);
@Transaction @Transaction
public long upsert(PlaylistRemoteEntity playlist) { public long upsert(final PlaylistRemoteEntity playlist) {
final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl()); final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl());
if (playlistId == null) { if (playlistId == null) {
@ -54,7 +53,7 @@ public abstract class PlaylistRemoteDAO implements BasicDAO<PlaylistRemoteEntity
} }
} }
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE + @Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE
" WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId") + " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId")
public abstract int deletePlaylist(final long playlistId); public abstract int deletePlaylist(long playlistId);
} }

View File

@ -14,9 +14,16 @@ import java.util.List;
import io.reactivex.Flowable; import io.reactivex.Flowable;
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT; import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.*; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.*; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
import static org.schabi.newpipe.database.stream.model.StreamEntity.*; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID;
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;
@Dao @Dao
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> { public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
@ -29,40 +36,39 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
public abstract int deleteAll(); public abstract int deleteAll();
@Override @Override
public Flowable<List<PlaylistStreamEntity>> listByService(int serviceId) { public Flowable<List<PlaylistStreamEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE + @Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
public abstract void deleteBatch(final long playlistId); public abstract void deleteBatch(long playlistId);
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" + @Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)"
" FROM " + PLAYLIST_STREAM_JOIN_TABLE + + " FROM " + PLAYLIST_STREAM_JOIN_TABLE
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
public abstract Flowable<Integer> getMaximumIndexOf(final long playlistId); public abstract Flowable<Integer> getMaximumIndexOf(long playlistId);
@Transaction @Transaction
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " + @Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN "
// get ids of streams of the given playlist // get ids of streams of the given playlist
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX + + "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX
" FROM " + PLAYLIST_STREAM_JOIN_TABLE + + " FROM " + PLAYLIST_STREAM_JOIN_TABLE
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" + + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)"
// then merge with the stream metadata // then merge with the stream metadata
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID + + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
" ORDER BY " + JOIN_INDEX + " ASC") + " ORDER BY " + JOIN_INDEX + " ASC")
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId); public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
@Transaction @Transaction
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
PLAYLIST_THUMBNAIL_URL + ", " + + "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
"COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT +
" FROM " + PLAYLIST_TABLE + + " FROM " + PLAYLIST_TABLE
" LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE + + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
" ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID + + " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
" GROUP BY " + JOIN_PLAYLIST_ID + + " GROUP BY " + JOIN_PLAYLIST_ID
" ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC") + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata(); public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
} }

View File

@ -11,10 +11,10 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST
@Entity(tableName = PLAYLIST_TABLE, @Entity(tableName = PLAYLIST_TABLE,
indices = {@Index(value = {PLAYLIST_NAME})}) indices = {@Index(value = {PLAYLIST_NAME})})
public class PlaylistEntity { public class PlaylistEntity {
final public static String PLAYLIST_TABLE = "playlists"; public static final String PLAYLIST_TABLE = "playlists";
final public static String PLAYLIST_ID = "uid"; public static final String PLAYLIST_ID = "uid";
final public static String PLAYLIST_NAME = "name"; public static final String PLAYLIST_NAME = "name";
final public static String PLAYLIST_THUMBNAIL_URL = "thumbnail_url"; public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = PLAYLIST_ID) @ColumnInfo(name = PLAYLIST_ID)
@ -26,7 +26,7 @@ public class PlaylistEntity {
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL) @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
private String thumbnailUrl; private String thumbnailUrl;
public PlaylistEntity(String name, String thumbnailUrl) { public PlaylistEntity(final String name, final String thumbnailUrl) {
this.name = name; this.name = name;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
} }
@ -35,7 +35,7 @@ public class PlaylistEntity {
return uid; return uid;
} }
public void setUid(long uid) { public void setUid(final long uid) {
this.uid = uid; this.uid = uid;
} }
@ -43,7 +43,7 @@ public class PlaylistEntity {
return name; return name;
} }
public void setName(String name) { public void setName(final String name) {
this.name = name; this.name = name;
} }
@ -51,7 +51,7 @@ public class PlaylistEntity {
return thumbnailUrl; return thumbnailUrl;
} }
public void setThumbnailUrl(String thumbnailUrl) { public void setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
} }
} }

View File

@ -24,14 +24,14 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE
@Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true) @Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true)
}) })
public class PlaylistRemoteEntity implements PlaylistLocalItem { public class PlaylistRemoteEntity implements PlaylistLocalItem {
final public static String REMOTE_PLAYLIST_TABLE = "remote_playlists"; public static final String REMOTE_PLAYLIST_TABLE = "remote_playlists";
final public static String REMOTE_PLAYLIST_ID = "uid"; public static final String REMOTE_PLAYLIST_ID = "uid";
final public static String REMOTE_PLAYLIST_SERVICE_ID = "service_id"; public static final String REMOTE_PLAYLIST_SERVICE_ID = "service_id";
final public static String REMOTE_PLAYLIST_NAME = "name"; public static final String REMOTE_PLAYLIST_NAME = "name";
final public static String REMOTE_PLAYLIST_URL = "url"; public static final String REMOTE_PLAYLIST_URL = "url";
final public static String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url"; public static final String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
final public static String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader"; public static final String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
final public static String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count"; public static final String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = REMOTE_PLAYLIST_ID) @ColumnInfo(name = REMOTE_PLAYLIST_ID)
@ -55,8 +55,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT) @ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
private Long streamCount; private Long streamCount;
public PlaylistRemoteEntity(int serviceId, String name, String url, String thumbnailUrl, public PlaylistRemoteEntity(final int serviceId, final String name, final String url,
String uploader, Long streamCount) { final String thumbnailUrl, final String uploader,
final Long streamCount) {
this.serviceId = serviceId; this.serviceId = serviceId;
this.name = name; this.name = name;
this.url = url; this.url = url;
@ -68,7 +69,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@Ignore @Ignore
public PlaylistRemoteEntity(final PlaylistInfo info) { public PlaylistRemoteEntity(final PlaylistInfo info) {
this(info.getServiceId(), info.getName(), info.getUrl(), this(info.getServiceId(), info.getName(), info.getUrl(),
info.getThumbnailUrl() == null ? info.getUploaderAvatarUrl() : info.getThumbnailUrl(), info.getThumbnailUrl() == null
? info.getUploaderAvatarUrl() : info.getThumbnailUrl(),
info.getUploaderName(), info.getStreamCount()); info.getUploaderName(), info.getStreamCount());
} }
@ -90,7 +92,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return uid; return uid;
} }
public void setUid(long uid) { public void setUid(final long uid) {
this.uid = uid; this.uid = uid;
} }
@ -98,7 +100,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return serviceId; return serviceId;
} }
public void setServiceId(int serviceId) { public void setServiceId(final int serviceId) {
this.serviceId = serviceId; this.serviceId = serviceId;
} }
@ -106,7 +108,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return name; return name;
} }
public void setName(String name) { public void setName(final String name) {
this.name = name; this.name = name;
} }
@ -114,7 +116,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return thumbnailUrl; return thumbnailUrl;
} }
public void setThumbnailUrl(String thumbnailUrl) { public void setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
} }
@ -122,7 +124,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return url; return url;
} }
public void setUrl(String url) { public void setUrl(final String url) {
this.url = url; this.url = url;
} }
@ -130,7 +132,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return uploader; return uploader;
} }
public void setUploader(String uploader) { public void setUploader(final String uploader) {
this.uploader = uploader; this.uploader = uploader;
} }
@ -138,7 +140,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return streamCount; return streamCount;
} }
public void setStreamCount(Long streamCount) { public void setStreamCount(final Long streamCount) {
this.streamCount = streamCount; this.streamCount = streamCount;
} }

View File

@ -30,11 +30,10 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PL
onDelete = CASCADE, onUpdate = CASCADE, deferred = true) onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
}) })
public class PlaylistStreamEntity { public class PlaylistStreamEntity {
public static final String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
final public static String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join"; public static final String JOIN_PLAYLIST_ID = "playlist_id";
final public static String JOIN_PLAYLIST_ID = "playlist_id"; public static final String JOIN_STREAM_ID = "stream_id";
final public static String JOIN_STREAM_ID = "stream_id"; public static final String JOIN_INDEX = "join_index";
final public static String JOIN_INDEX = "join_index";
@ColumnInfo(name = JOIN_PLAYLIST_ID) @ColumnInfo(name = JOIN_PLAYLIST_ID)
private long playlistUid; private long playlistUid;
@ -55,23 +54,23 @@ public class PlaylistStreamEntity {
return playlistUid; return playlistUid;
} }
public void setPlaylistUid(final long playlistUid) {
this.playlistUid = playlistUid;
}
public long getStreamUid() { public long getStreamUid() {
return streamUid; return streamUid;
} }
public void setStreamUid(final long streamUid) {
this.streamUid = streamUid;
}
public int getIndex() { public int getIndex() {
return index; return index;
} }
public void setPlaylistUid(long playlistUid) { public void setIndex(final int index) {
this.playlistUid = playlistUid;
}
public void setStreamUid(long streamUid) {
this.streamUid = streamUid;
}
public void setIndex(int index) {
this.index = index; this.index = index;
} }
} }

View File

@ -27,21 +27,21 @@ public abstract class StreamStateDAO implements BasicDAO<StreamStateEntity> {
public abstract int deleteAll(); public abstract int deleteAll();
@Override @Override
public Flowable<List<StreamStateEntity>> listByService(int serviceId) { public Flowable<List<StreamStateEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") @Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract Flowable<List<StreamStateEntity>> getState(final long streamId); public abstract Flowable<List<StreamStateEntity>> getState(long streamId);
@Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") @Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract int deleteState(final long streamId); public abstract int deleteState(long streamId);
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract void silentInsertInternal(final StreamStateEntity streamState); abstract void silentInsertInternal(StreamStateEntity streamState);
@Transaction @Transaction
public long upsert(StreamStateEntity stream) { public long upsert(final StreamStateEntity stream) {
silentInsertInternal(stream); silentInsertInternal(stream);
return update(stream); return update(stream);
} }

View File

@ -90,7 +90,8 @@ data class StreamEntity(
if (viewCount != null) item.viewCount = viewCount as Long if (viewCount != null) item.viewCount = viewCount as Long
item.textualUploadDate = textualUploadDate item.textualUploadDate = textualUploadDate
item.uploadDate = uploadDate?.let { item.uploadDate = uploadDate?.let {
DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation ?: false) DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation
?: false)
} }
return item return item

View File

@ -1,10 +1,9 @@
package org.schabi.newpipe.database.stream.model; package org.schabi.newpipe.database.stream.model;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.annotation.Nullable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -21,14 +20,17 @@ import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_
onDelete = CASCADE, onUpdate = CASCADE) onDelete = CASCADE, onUpdate = CASCADE)
}) })
public class StreamStateEntity { public class StreamStateEntity {
final public static String STREAM_STATE_TABLE = "stream_state"; public static final String STREAM_STATE_TABLE = "stream_state";
final public static String JOIN_STREAM_ID = "stream_id"; public static final String JOIN_STREAM_ID = "stream_id";
final public static String STREAM_PROGRESS_TIME = "progress_time"; public static final String STREAM_PROGRESS_TIME = "progress_time";
/**
/** Playback state will not be saved, if playback time less than this threshold */ * Playback state will not be saved, if playback time is less than this threshold.
*/
private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5; private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5;
/** Playback state will not be saved, if time left less than this threshold */ /**
* Playback state will not be saved, if time left is less than this threshold.
*/
private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10; private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10;
@ColumnInfo(name = JOIN_STREAM_ID) @ColumnInfo(name = JOIN_STREAM_ID)
@ -37,7 +39,7 @@ public class StreamStateEntity {
@ColumnInfo(name = STREAM_PROGRESS_TIME) @ColumnInfo(name = STREAM_PROGRESS_TIME)
private long progressTime; private long progressTime;
public StreamStateEntity(long streamUid, long progressTime) { public StreamStateEntity(final long streamUid, final long progressTime) {
this.streamUid = streamUid; this.streamUid = streamUid;
this.progressTime = progressTime; this.progressTime = progressTime;
} }
@ -46,7 +48,7 @@ public class StreamStateEntity {
return streamUid; return streamUid;
} }
public void setStreamUid(long streamUid) { public void setStreamUid(final long streamUid) {
this.streamUid = streamUid; this.streamUid = streamUid;
} }
@ -54,21 +56,23 @@ public class StreamStateEntity {
return progressTime; return progressTime;
} }
public void setProgressTime(long progressTime) { public void setProgressTime(final long progressTime) {
this.progressTime = progressTime; this.progressTime = progressTime;
} }
public boolean isValid(int durationInSeconds) { public boolean isValid(final int durationInSeconds) {
final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime); final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime);
return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS
&& seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS; && seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS;
} }
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable final Object obj) {
if (obj instanceof StreamStateEntity) { if (obj instanceof StreamStateEntity) {
return ((StreamStateEntity) obj).streamUid == streamUid return ((StreamStateEntity) obj).streamUid == streamUid
&& ((StreamStateEntity) obj).progressTime == progressTime; && ((StreamStateEntity) obj).progressTime == progressTime;
} else return false; } else {
return false;
}
} }
} }

View File

@ -48,7 +48,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
entity.uid = uidFromInsert entity.uid = uidFromInsert
} else { } else {
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url) val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
?: throw IllegalStateException("Subscription cannot be null just after insertion.") ?: throw IllegalStateException("Subscription cannot be null just after insertion.")
entity.uid = subscriptionIdFromDb entity.uid = subscriptionIdFromDb
update(entity) update(entity)

View File

@ -1,11 +1,11 @@
package org.schabi.newpipe.database.subscription; package org.schabi.newpipe.database.subscription;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Ignore; import androidx.room.Ignore;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
@ -18,15 +18,14 @@ import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCR
@Entity(tableName = SUBSCRIPTION_TABLE, @Entity(tableName = SUBSCRIPTION_TABLE,
indices = {@Index(value = {SUBSCRIPTION_SERVICE_ID, SUBSCRIPTION_URL}, unique = true)}) indices = {@Index(value = {SUBSCRIPTION_SERVICE_ID, SUBSCRIPTION_URL}, unique = true)})
public class SubscriptionEntity { public class SubscriptionEntity {
public static final String SUBSCRIPTION_UID = "uid";
public static final String SUBSCRIPTION_UID = "uid"; public static final String SUBSCRIPTION_TABLE = "subscriptions";
public static final String SUBSCRIPTION_TABLE = "subscriptions"; public static final String SUBSCRIPTION_SERVICE_ID = "service_id";
public static final String SUBSCRIPTION_SERVICE_ID = "service_id"; public static final String SUBSCRIPTION_URL = "url";
public static final String SUBSCRIPTION_URL = "url"; public static final String SUBSCRIPTION_NAME = "name";
public static final String SUBSCRIPTION_NAME = "name"; public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url";
public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url"; public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count";
public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count"; public static final String SUBSCRIPTION_DESCRIPTION = "description";
public static final String SUBSCRIPTION_DESCRIPTION = "description";
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
private long uid = 0; private long uid = 0;
@ -49,11 +48,21 @@ public class SubscriptionEntity {
@ColumnInfo(name = SUBSCRIPTION_DESCRIPTION) @ColumnInfo(name = SUBSCRIPTION_DESCRIPTION)
private String description; private String description;
@Ignore
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
SubscriptionEntity result = new SubscriptionEntity();
result.setServiceId(info.getServiceId());
result.setUrl(info.getUrl());
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
info.getSubscriberCount());
return result;
}
public long getUid() { public long getUid() {
return uid; return uid;
} }
public void setUid(long uid) { public void setUid(final long uid) {
this.uid = uid; this.uid = uid;
} }
@ -61,7 +70,7 @@ public class SubscriptionEntity {
return serviceId; return serviceId;
} }
public void setServiceId(int serviceId) { public void setServiceId(final int serviceId) {
this.serviceId = serviceId; this.serviceId = serviceId;
} }
@ -69,7 +78,7 @@ public class SubscriptionEntity {
return url; return url;
} }
public void setUrl(String url) { public void setUrl(final String url) {
this.url = url; this.url = url;
} }
@ -77,7 +86,7 @@ public class SubscriptionEntity {
return name; return name;
} }
public void setName(String name) { public void setName(final String name) {
this.name = name; this.name = name;
} }
@ -85,7 +94,7 @@ public class SubscriptionEntity {
return avatarUrl; return avatarUrl;
} }
public void setAvatarUrl(String avatarUrl) { public void setAvatarUrl(final String avatarUrl) {
this.avatarUrl = avatarUrl; this.avatarUrl = avatarUrl;
} }
@ -93,7 +102,7 @@ public class SubscriptionEntity {
return subscriberCount; return subscriberCount;
} }
public void setSubscriberCount(Long subscriberCount) { public void setSubscriberCount(final Long subscriberCount) {
this.subscriberCount = subscriberCount; this.subscriberCount = subscriberCount;
} }
@ -101,19 +110,16 @@ public class SubscriptionEntity {
return description; return description;
} }
public void setDescription(String description) { public void setDescription(final String description) {
this.description = description; this.description = description;
} }
@Ignore @Ignore
public void setData(final String name, public void setData(final String n, final String au, final String d, final Long sc) {
final String avatarUrl, this.setName(n);
final String description, this.setAvatarUrl(au);
final Long subscriberCount) { this.setDescription(d);
this.setName(name); this.setSubscriberCount(sc);
this.setAvatarUrl(avatarUrl);
this.setDescription(description);
this.setSubscriberCount(subscriberCount);
} }
@Ignore @Ignore
@ -124,13 +130,4 @@ public class SubscriptionEntity {
item.setDescription(getDescription()); item.setDescription(getDescription());
return item; return item;
} }
@Ignore
public static SubscriptionEntity from(@NonNull ChannelInfo info) {
SubscriptionEntity result = new SubscriptionEntity();
result.setServiceId(info.getServiceId());
result.setUrl(info.getUrl());
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
return result;
}
} }

View File

@ -3,16 +3,16 @@ package org.schabi.newpipe.download;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;
@ -25,7 +25,7 @@ public class DownloadActivity extends AppCompatActivity {
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag"; private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
// Service // Service
Intent i = new Intent(); Intent i = new Intent();
i.setClass(this, DownloadManagerService.class); i.setClass(this, DownloadManagerService.class);
@ -46,7 +46,8 @@ public class DownloadActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true); actionBar.setDisplayShowTitleEnabled(true);
} }
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { getWindow().getDecorView().getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override @Override
public void onGlobalLayout() { public void onGlobalLayout() {
updateFragments(); updateFragments();
@ -65,7 +66,7 @@ public class DownloadActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
@ -75,7 +76,7 @@ public class DownloadActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
onBackPressed(); onBackPressed();

View File

@ -81,11 +81,12 @@ import us.shandian.giga.service.MissionState;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { public class DownloadDialog extends DialogFragment
implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment"; private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG; private static final boolean DEBUG = MainActivity.DEBUG;
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230; private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
private final CompositeDisposable disposables = new CompositeDisposable();
@State @State
protected StreamInfo currentInfo; protected StreamInfo currentInfo;
@State @State
@ -100,30 +101,32 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
protected int selectedAudioIndex = 0; protected int selectedAudioIndex = 0;
@State @State
protected int selectedSubtitleIndex = 0; protected int selectedSubtitleIndex = 0;
private StoredDirectoryHelper mainStorageAudio = null;
private StoredDirectoryHelper mainStorageVideo = null;
private DownloadManager downloadManager = null;
private ActionMenuItemView okButton = null;
private Context context;
private boolean askForSavePath;
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter; private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter; private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter; private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
private final CompositeDisposable disposables = new CompositeDisposable();
private EditText nameEditText; private EditText nameEditText;
private Spinner streamsSpinner; private Spinner streamsSpinner;
private RadioGroup radioStreamsGroup; private RadioGroup radioStreamsGroup;
private TextView threadsCountTextView; private TextView threadsCountTextView;
private SeekBar threadsSeekBar; private SeekBar threadsSeekBar;
private SharedPreferences prefs; private SharedPreferences prefs;
public static DownloadDialog newInstance(StreamInfo info) { public static DownloadDialog newInstance(final StreamInfo info) {
DownloadDialog dialog = new DownloadDialog(); DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info); dialog.setInfo(info);
return dialog; return dialog;
} }
public static DownloadDialog newInstance(Context context, StreamInfo info) { public static DownloadDialog newInstance(final Context context, final StreamInfo info) {
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper
info.getVideoStreams(), info.getVideoOnlyStreams(), false)); .getSortedStreamVideosList(context, info.getVideoStreams(),
info.getVideoOnlyStreams(), false));
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList); final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
final DownloadDialog instance = newInstance(info); final DownloadDialog instance = newInstance(info);
@ -135,57 +138,61 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
return instance; return instance;
} }
private void setInfo(StreamInfo info) { private void setInfo(final StreamInfo info) {
this.currentInfo = info; this.currentInfo = info;
} }
public void setAudioStreams(List<AudioStream> audioStreams) { public void setAudioStreams(final List<AudioStream> audioStreams) {
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext())); setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext()));
} }
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) { public void setAudioStreams(final StreamSizeWrapper<AudioStream> was) {
this.wrappedAudioStreams = wrappedAudioStreams; this.wrappedAudioStreams = was;
} }
public void setVideoStreams(List<VideoStream> videoStreams) { public void setVideoStreams(final List<VideoStream> videoStreams) {
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext())); setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
} }
public void setVideoStreams(StreamSizeWrapper<VideoStream> wrappedVideoStreams) {
this.wrappedVideoStreams = wrappedVideoStreams;
}
public void setSubtitleStreams(List<SubtitlesStream> subtitleStreams) {
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
}
public void setSubtitleStreams(StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams) {
this.wrappedSubtitleStreams = wrappedSubtitleStreams;
}
public void setSelectedVideoStream(int selectedVideoIndex) {
this.selectedVideoIndex = selectedVideoIndex;
}
public void setSelectedAudioStream(int selectedAudioIndex) {
this.selectedAudioIndex = selectedAudioIndex;
}
public void setSelectedSubtitleStream(int selectedSubtitleIndex) {
this.selectedSubtitleIndex = selectedSubtitleIndex;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// LifeCycle // LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override public void setVideoStreams(final StreamSizeWrapper<VideoStream> wvs) {
public void onCreate(@Nullable Bundle savedInstanceState) { this.wrappedVideoStreams = wvs;
super.onCreate(savedInstanceState); }
if (DEBUG)
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { public void setSubtitleStreams(final List<SubtitlesStream> subtitleStreams) {
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
}
public void setSubtitleStreams(
final StreamSizeWrapper<SubtitlesStream> wss) {
this.wrappedSubtitleStreams = wss;
}
public void setSelectedVideoStream(final int svi) {
this.selectedVideoIndex = svi;
}
public void setSelectedAudioStream(final int sai) {
this.selectedAudioIndex = sai;
}
public void setSelectedSubtitleStream(final int ssi) {
this.selectedSubtitleIndex = ssi;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) {
Log.d(TAG, "onCreate() called with: "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
if (!PermissionHelper.checkStoragePermissions(getActivity(),
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
getDialog().dismiss(); getDialog().dismiss();
return; return;
} }
@ -199,17 +206,23 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList(); List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) { for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) continue; if (!videoStreams.get(i).isVideoOnly()) {
AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i)); continue;
}
AudioStream audioStream = SecondaryStreamHelper
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) { if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream)); secondaryStreams
.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
} else if (DEBUG) { } else if (DEBUG) {
Log.w(TAG, "No audio stream candidates for video format " + videoStreams.get(i).getFormat().name()); Log.w(TAG, "No audio stream candidates for video format "
+ videoStreams.get(i).getFormat().name());
} }
} }
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams, secondaryStreams); this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams,
secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams); this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams); this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
@ -218,7 +231,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
context.bindService(intent, new ServiceConnection() { context.bindService(intent, new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName cname, IBinder service) { public void onServiceConnected(final ComponentName cname, final IBinder service) {
DownloadManagerBinder mgr = (DownloadManagerBinder) service; DownloadManagerBinder mgr = (DownloadManagerBinder) service;
mainStorageAudio = mgr.getMainStorageAudio(); mainStorageAudio = mgr.getMainStorageAudio();
@ -232,25 +245,34 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(final ComponentName name) {
// nothing to do // nothing to do
} }
}, Context.BIND_AUTO_CREATE); }, Context.BIND_AUTO_CREATE);
} }
/*//////////////////////////////////////////////////////////////////////////
// Inits
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
if (DEBUG) final Bundle savedInstanceState) {
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]"); if (DEBUG) {
Log.d(TAG, "onCreateView() called with: "
+ "inflater = [" + inflater + "], container = [" + container + "], "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
return inflater.inflate(R.layout.download_dialog, container); return inflater.inflate(R.layout.download_dialog, container);
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
nameEditText = view.findViewById(R.id.file_name); nameEditText = view.findViewById(R.id.file_name);
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName())); nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams()); selectedAudioIndex = ListHelper
.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll()); selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
@ -272,21 +294,20 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
threadsCountTextView.setText(String.valueOf(threads)); threadsCountTextView.setText(String.valueOf(threads));
threadsSeekBar.setProgress(threads - 1); threadsSeekBar.setProgress(threads - 1);
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override @Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { public void onProgressChanged(final SeekBar seekbar, final int progress,
progress++; final boolean fromUser) {
prefs.edit().putInt(getString(R.string.default_download_threads), progress).apply(); final int newProgress = progress + 1;
threadsCountTextView.setText(String.valueOf(progress)); prefs.edit().putInt(getString(R.string.default_download_threads), newProgress)
.apply();
threadsCountTextView.setText(String.valueOf(newProgress));
} }
@Override @Override
public void onStartTrackingTouch(SeekBar p1) { public void onStartTrackingTouch(final SeekBar p1) { }
}
@Override @Override
public void onStopTrackingTouch(SeekBar p1) { public void onStopTrackingTouch(final SeekBar p1) { }
}
}); });
fetchStreamsSize(); fetchStreamsSize();
@ -295,17 +316,20 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private void fetchStreamsSize() { private void fetchStreamsSize() {
disposables.clear(); disposables.clear();
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> { disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
.subscribe(result -> {
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) { if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) {
setupVideoSpinner(); setupVideoSpinner();
} }
})); }));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> { disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
.subscribe(result -> {
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) { if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
setupAudioSpinner(); setupAudioSpinner();
} }
})); }));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> { disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
.subscribe(result -> {
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) { if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
setupSubtitleSpinner(); setupSubtitleSpinner();
} }
@ -318,14 +342,22 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
disposables.clear(); disposables.clear();
} }
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onSaveInstanceState(@NonNull Bundle outState) { public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState); Icepick.saveInstanceState(this, outState);
} }
/*//////////////////////////////////////////////////////////////////////////
// Streams Spinner Listener
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) {
@ -336,7 +368,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) { if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
File file = Utils.getFileForUri(data.getData()); File file = Utils.getFileForUri(data.getData());
checkSelectedDownload(null, Uri.fromFile(file), file.getName(), StoredFileHelper.DEFAULT_MIME); checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
StoredFileHelper.DEFAULT_MIME);
return; return;
} }
@ -347,27 +380,27 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
// check if the selected file was previously used // check if the selected file was previously used
checkSelectedDownload(null, data.getData(), docFile.getName(), docFile.getType()); checkSelectedDownload(null, data.getData(), docFile.getName(),
docFile.getType());
} }
} }
/*////////////////////////////////////////////////////////////////////////// private void initToolbar(final Toolbar toolbar) {
// Inits if (DEBUG) {
//////////////////////////////////////////////////////////////////////////*/ Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
}
private void initToolbar(Toolbar toolbar) {
if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity()); boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
toolbar.setTitle(R.string.download_dialog_title); toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp); toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp
: R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url); toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss()); toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
toolbar.setNavigationContentDescription(R.string.cancel); toolbar.setNavigationContentDescription(R.string.cancel);
okButton = toolbar.findViewById(R.id.okay); okButton = toolbar.findViewById(R.id.okay);
okButton.setEnabled(false);// disable until the download service connection is done okButton.setEnabled(false); // disable until the download service connection is done
toolbar.setOnMenuItemClickListener(item -> { toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.okay) { if (item.getItemId() == R.id.okay) {
@ -381,8 +414,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}); });
} }
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void setupAudioSpinner() { private void setupAudioSpinner() {
if (getContext() == null) return; if (getContext() == null) {
return;
}
streamsSpinner.setAdapter(audioStreamsAdapter); streamsSpinner.setAdapter(audioStreamsAdapter);
streamsSpinner.setSelection(selectedAudioIndex); streamsSpinner.setSelection(selectedAudioIndex);
@ -390,7 +429,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
private void setupVideoSpinner() { private void setupVideoSpinner() {
if (getContext() == null) return; if (getContext() == null) {
return;
}
streamsSpinner.setAdapter(videoStreamsAdapter); streamsSpinner.setAdapter(videoStreamsAdapter);
streamsSpinner.setSelection(selectedVideoIndex); streamsSpinner.setSelection(selectedVideoIndex);
@ -398,21 +439,21 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
private void setupSubtitleSpinner() { private void setupSubtitleSpinner() {
if (getContext() == null) return; if (getContext() == null) {
return;
}
streamsSpinner.setAdapter(subtitleStreamsAdapter); streamsSpinner.setAdapter(subtitleStreamsAdapter);
streamsSpinner.setSelection(selectedSubtitleIndex); streamsSpinner.setSelection(selectedSubtitleIndex);
setRadioButtonsState(true); setRadioButtonsState(true);
} }
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) {
if (DEBUG) if (DEBUG) {
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]"); Log.d(TAG, "onCheckedChanged() called with: "
+ "group = [" + group + "], checkedId = [" + checkedId + "]");
}
boolean flag = true; boolean flag = true;
switch (checkedId) { switch (checkedId) {
@ -431,14 +472,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
threadsSeekBar.setEnabled(flag); threadsSeekBar.setEnabled(flag);
} }
/*//////////////////////////////////////////////////////////////////////////
// Streams Spinner Listener
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(final AdapterView<?> parent, final View view,
if (DEBUG) final int position, final long id) {
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); if (DEBUG) {
Log.d(TAG, "onItemSelected() called with: "
+ "parent = [" + parent + "], view = [" + view + "], "
+ "position = [" + position + "], id = [" + id + "]");
}
switch (radioStreamsGroup.getCheckedRadioButtonId()) { switch (radioStreamsGroup.getCheckedRadioButtonId()) {
case R.id.audio_button: case R.id.audio_button:
selectedAudioIndex = position; selectedAudioIndex = position;
@ -453,13 +494,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(final AdapterView<?> parent) {
} }
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void setupDownloadOptions() { protected void setupDownloadOptions() {
setRadioButtonsState(false); setRadioButtonsState(false);
@ -484,30 +521,36 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
subtitleButton.setChecked(true); subtitleButton.setChecked(true);
setupSubtitleSpinner(); setupSubtitleSpinner();
} else { } else {
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.no_streams_available_download,
Toast.LENGTH_SHORT).show();
getDialog().dismiss(); getDialog().dismiss();
} }
} }
private void setRadioButtonsState(boolean enabled) { private void setRadioButtonsState(final boolean enabled) {
radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled); radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled);
radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled); radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled);
radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled); radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
} }
private int getSubtitleIndexBy(List<SubtitlesStream> streams) { private int getSubtitleIndexBy(final List<SubtitlesStream> streams) {
final Localization preferredLocalization = NewPipe.getPreferredLocalization(); final Localization preferredLocalization = NewPipe.getPreferredLocalization();
int candidate = 0; int candidate = 0;
for (int i = 0; i < streams.size(); i++) { for (int i = 0; i < streams.size(); i++) {
final Locale streamLocale = streams.get(i).getLocale(); final Locale streamLocale = streams.get(i).getLocale();
final boolean languageEquals = streamLocale.getLanguage() != null && preferredLocalization.getLanguageCode() != null && final boolean languageEquals = streamLocale.getLanguage() != null
streamLocale.getLanguage().equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage()); && preferredLocalization.getLanguageCode() != null
final boolean countryEquals = streamLocale.getCountry() != null && streamLocale.getCountry().equals(preferredLocalization.getCountryCode()); && streamLocale.getLanguage()
.equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage());
final boolean countryEquals = streamLocale.getCountry() != null
&& streamLocale.getCountry().equals(preferredLocalization.getCountryCode());
if (languageEquals) { if (languageEquals) {
if (countryEquals) return i; if (countryEquals) {
return i;
}
candidate = i; candidate = i;
} }
@ -516,20 +559,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
return candidate; return candidate;
} }
StoredDirectoryHelper mainStorageAudio = null;
StoredDirectoryHelper mainStorageVideo = null;
DownloadManager downloadManager = null;
ActionMenuItemView okButton = null;
Context context;
boolean askForSavePath;
private String getNameEditText() { private String getNameEditText() {
String str = nameEditText.getText().toString().trim(); String str = nameEditText.getText().toString().trim();
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str); return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
} }
private void showFailedDialog(@StringRes int msg) { private void showFailedDialog(@StringRes final int msg) {
assureCorrectAppLanguage(getContext()); assureCorrectAppLanguage(getContext());
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(R.string.general_error) .setTitle(R.string.general_error)
@ -539,13 +575,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
.show(); .show();
} }
private void showErrorActivity(Exception e) { private void showErrorActivity(final Exception e) {
ErrorActivity.reportError( ErrorActivity.reportError(
context, context,
Collections.singletonList(e), Collections.singletonList(e),
null, null,
null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error) ErrorActivity.ErrorInfo
.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
); );
} }
@ -563,7 +600,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
case R.id.audio_button: case R.id.audio_button:
mainStorage = mainStorageAudio; mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat(); format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
switch(format) { switch (format) {
case WEBMA_OPUS: case WEBMA_OPUS:
mime = "audio/ogg"; mime = "audio/ogg";
filename += "opus"; filename += "opus";
@ -581,7 +618,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
filename += format.suffix; filename += format.suffix;
break; break;
case R.id.subtitle_button: case R.id.subtitle_button:
mainStorage = mainStorageVideo;// subtitle & video files go together mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat(); format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
mime = format.mimeType; mime = format.mimeType;
filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix; filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix;
@ -596,23 +633,25 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
// * save path not defined (via download settings) // * save path not defined (via download settings)
// * the user checked the "ask where to download" option // * the user checked the "ask where to download" option
if (!askForSavePath) if (!askForSavePath) {
Toast.makeText(context, getString(R.string.no_available_dir), Toast.LENGTH_LONG).show(); Toast.makeText(context, getString(R.string.no_available_dir),
Toast.LENGTH_LONG).show();
}
if (NewPipeSettings.useStorageAccessFramework(context)) { if (NewPipeSettings.useStorageAccessFramework(context)) {
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, filename, mime); StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS,
filename, mime);
} else { } else {
File initialSavePath; File initialSavePath;
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC); initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
else } else {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES); initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
}
initialSavePath = new File(initialSavePath, filename); initialSavePath = new File(initialSavePath, filename);
startActivityForResult( startActivityForResult(FilePickerActivityHelper.chooseFileToSave(context,
FilePickerActivityHelper.chooseFileToSave(context, initialSavePath.getAbsolutePath()), initialSavePath.getAbsolutePath()), REQUEST_DOWNLOAD_SAVE_AS);
REQUEST_DOWNLOAD_SAVE_AS
);
} }
return; return;
@ -622,7 +661,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime); checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
} }
private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri targetFile, String filename, String mime) { private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
final Uri targetFile, final String filename,
final String mime) {
StoredFileHelper storage; StoredFileHelper storage;
try { try {
@ -631,10 +672,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
storage = new StoredFileHelper(context, null, targetFile, ""); storage = new StoredFileHelper(context, null, targetFile, "");
} else if (targetFile == null) { } else if (targetFile == null) {
// the file does not exist, but it is probably used in a pending download // the file does not exist, but it is probably used in a pending download
storage = new StoredFileHelper(mainStorage.getUri(), filename, mime, mainStorage.getTag()); storage = new StoredFileHelper(mainStorage.getUri(), filename, mime,
mainStorage.getTag());
} else { } else {
// the target filename is already use, attempt to use it // the target filename is already use, attempt to use it
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag()); storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
mainStorage.getTag());
} }
} catch (Exception e) { } catch (Exception e) {
showErrorActivity(e); showErrorActivity(e);
@ -738,24 +781,28 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} else { } else {
try { try {
// try take (or steal) the file // try take (or steal) the file
storageNew = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag()); storageNew = new StoredFileHelper(context, mainStorage.getUri(),
targetFile, mainStorage.getTag());
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Failed to take (or steal) the file in " + targetFile.toString()); Log.e(TAG, "Failed to take (or steal) the file in "
+ targetFile.toString());
storageNew = null; storageNew = null;
} }
} }
if (storageNew != null && storageNew.canWrite()) if (storageNew != null && storageNew.canWrite()) {
continueSelectedDownload(storageNew); continueSelectedDownload(storageNew);
else } else {
showFailedDialog(R.string.error_file_creation); showFailedDialog(R.string.error_file_creation);
}
break; break;
case PendingRunning: case PendingRunning:
storageNew = mainStorage.createUniqueFile(filename, mime); storageNew = mainStorage.createUniqueFile(filename, mime);
if (storageNew == null) if (storageNew == null) {
showFailedDialog(R.string.error_file_creation); showFailedDialog(R.string.error_file_creation);
else } else {
continueSelectedDownload(storageNew); continueSelectedDownload(storageNew);
}
break; break;
} }
}); });
@ -763,7 +810,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
askDialog.create().show(); askDialog.create().show();
} }
private void continueSelectedDownload(@NonNull StoredFileHelper storage) { private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
if (!storage.canWrite()) { if (!storage.canWrite()) {
showFailedDialog(R.string.permission_denied); showFailedDialog(R.string.permission_denied);
return; return;
@ -771,7 +818,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
// check if the selected file has to be overwritten, by simply checking its length // check if the selected file has to be overwritten, by simply checking its length
try { try {
if (storage.length() > 0) storage.truncate(); if (storage.length() > 0) {
storage.truncate();
}
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e); Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
showFailedDialog(R.string.overwrite_failed); showFailedDialog(R.string.overwrite_failed);
@ -811,13 +860,15 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
if (secondary != null) { if (secondary != null) {
secondaryStream = secondary.getStream(); secondaryStream = secondary.getStream();
if (selectedStream.getFormat() == MediaFormat.MPEG_4) if (selectedStream.getFormat() == MediaFormat.MPEG_4) {
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER; psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
else } else {
psName = Postprocessing.ALGORITHM_WEBM_MUXER; psName = Postprocessing.ALGORITHM_WEBM_MUXER;
}
psArgs = null; psArgs = null;
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream); long videoSize = wrappedVideoStreams
.getSizeInBytes((VideoStream) selectedStream);
// set nearLength, only, if both sizes are fetched or known. This probably // set nearLength, only, if both sizes are fetched or known. This probably
// does not work on slow networks but is later updated in the downloader // does not work on slow networks but is later updated in the downloader
@ -827,7 +878,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} }
break; break;
case R.id.subtitle_button: case R.id.subtitle_button:
threads = 1;// use unique thread for subtitles due small file size threads = 1; // use unique thread for subtitles due small file size
kind = 's'; kind = 's';
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex); selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
@ -835,7 +886,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
psName = Postprocessing.ALGORITHM_TTML_CONVERTER; psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
psArgs = new String[]{ psArgs = new String[]{
selectedStream.getFormat().getSuffix(), selectedStream.getFormat().getSuffix(),
"false",// ignore empty frames "false" // ignore empty frames
}; };
} }
break; break;
@ -854,14 +905,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
urls = new String[]{ urls = new String[]{
selectedStream.getUrl(), secondaryStream.getUrl() selectedStream.getUrl(), secondaryStream.getUrl()
}; };
recoveryInfo = new MissionRecoveryInfo[]{ recoveryInfo = new MissionRecoveryInfo[]{new MissionRecoveryInfo(selectedStream),
new MissionRecoveryInfo(selectedStream), new MissionRecoveryInfo(secondaryStream) new MissionRecoveryInfo(secondaryStream)};
};
} }
DownloadManagerService.startMission( DownloadManagerService.startMission(context, urls, storage, kind, threads,
context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo);
);
dismiss(); dismiss();
} }

View File

@ -1,11 +1,11 @@
package org.schabi.newpipe.fragments; package org.schabi.newpipe.fragments;
/** /**
* Indicates that the current fragment can handle back presses * Indicates that the current fragment can handle back presses.
*/ */
public interface BackPressable { public interface BackPressable {
/** /**
* A back press was delegated to this fragment * A back press was delegated to this fragment.
* *
* @return if the back press was handled * @return if the back press was handled
*/ */

View File

@ -1,9 +1,8 @@
package org.schabi.newpipe.fragments; package org.schabi.newpipe.fragments;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -11,6 +10,9 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding2.view.RxView;
import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.BaseFragment;
@ -36,22 +38,21 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> { public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {
@State @State
protected AtomicBoolean wasLoading = new AtomicBoolean(); protected AtomicBoolean wasLoading = new AtomicBoolean();
protected AtomicBoolean isLoading = new AtomicBoolean(); protected AtomicBoolean isLoading = new AtomicBoolean();
@Nullable @Nullable
protected View emptyStateView; private View emptyStateView;
@Nullable @Nullable
protected ProgressBar loadingProgressBar; private ProgressBar loadingProgressBar;
protected View errorPanelRoot; protected View errorPanelRoot;
protected Button errorButtonRetry; private Button errorButtonRetry;
protected TextView errorTextView; private TextView errorTextView;
@Override @Override
public void onViewCreated(View rootView, Bundle savedInstanceState) { public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState); super.onViewCreated(rootView, savedInstanceState);
doInitialLoadLogic(); doInitialLoadLogic();
} }
@ -62,14 +63,12 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
wasLoading.set(isLoading.get()); wasLoading.set(isLoading.get());
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Init // Init
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
emptyStateView = rootView.findViewById(R.id.empty_state_view); emptyStateView = rootView.findViewById(R.id.empty_state_view);
@ -105,8 +104,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
startLoading(true); startLoading(true);
} }
protected void startLoading(boolean forceLoad) { protected void startLoading(final boolean forceLoad) {
if (DEBUG) Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]"); if (DEBUG) {
Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]");
}
showLoading(); showLoading();
isLoading.set(true); isLoading.set(true);
} }
@ -117,42 +118,62 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
@Override @Override
public void showLoading() { public void showLoading() {
if (emptyStateView != null) animateView(emptyStateView, false, 150); if (emptyStateView != null) {
if (loadingProgressBar != null) animateView(loadingProgressBar, true, 400); animateView(emptyStateView, false, 150);
}
if (loadingProgressBar != null) {
animateView(loadingProgressBar, true, 400);
}
animateView(errorPanelRoot, false, 150); animateView(errorPanelRoot, false, 150);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
if (emptyStateView != null) animateView(emptyStateView, false, 150); if (emptyStateView != null) {
if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0); animateView(emptyStateView, false, 150);
}
if (loadingProgressBar != null) {
animateView(loadingProgressBar, false, 0);
}
animateView(errorPanelRoot, false, 150); animateView(errorPanelRoot, false, 150);
} }
@Override @Override
public void showEmptyState() { public void showEmptyState() {
isLoading.set(false); isLoading.set(false);
if (emptyStateView != null) animateView(emptyStateView, true, 200); if (emptyStateView != null) {
if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0); animateView(emptyStateView, true, 200);
}
if (loadingProgressBar != null) {
animateView(loadingProgressBar, false, 0);
}
animateView(errorPanelRoot, false, 150); animateView(errorPanelRoot, false, 150);
} }
@Override @Override
public void showError(String message, boolean showRetryButton) { public void showError(final String message, final boolean showRetryButton) {
if (DEBUG) Log.d(TAG, "showError() called with: message = [" + message + "], showRetryButton = [" + showRetryButton + "]"); if (DEBUG) {
Log.d(TAG, "showError() called with: "
+ "message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
}
isLoading.set(false); isLoading.set(false);
InfoCache.getInstance().clearCache(); InfoCache.getInstance().clearCache();
hideLoading(); hideLoading();
errorTextView.setText(message); errorTextView.setText(message);
if (showRetryButton) animateView(errorButtonRetry, true, 600); if (showRetryButton) {
else animateView(errorButtonRetry, false, 0); animateView(errorButtonRetry, true, 600);
} else {
animateView(errorButtonRetry, false, 0);
}
animateView(errorPanelRoot, true, 300); animateView(errorPanelRoot, true, 300);
} }
@Override @Override
public void handleResult(I result) { public void handleResult(final I result) {
if (DEBUG) Log.d(TAG, "handleResult() called with: result = [" + result + "]"); if (DEBUG) {
Log.d(TAG, "handleResult() called with: result = [" + result + "]");
}
hideLoading(); hideLoading();
} }
@ -161,21 +182,28 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
/** /**
* Default implementation handles some general exceptions * Default implementation handles some general exceptions.
* *
* @return if the exception was handled * @param exception The exception that should be handled
* @return If the exception was handled
*/ */
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]"); if (DEBUG) {
Log.d(TAG, "onError() called with: exception = [" + exception + "]");
}
isLoading.set(false); isLoading.set(false);
if (isDetached() || isRemoving()) { if (isDetached() || isRemoving()) {
if (DEBUG) Log.w(TAG, "onError() is detached or removing = [" + exception + "]"); if (DEBUG) {
Log.w(TAG, "onError() is detached or removing = [" + exception + "]");
}
return true; return true;
} }
if (ExtractorHelper.isInterruptedCaused(exception)) { if (ExtractorHelper.isInterruptedCaused(exception)) {
if (DEBUG) Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); if (DEBUG) {
Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
}
return true; return true;
} }
@ -193,8 +221,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
return false; return false;
} }
public void onReCaptchaException(ReCaptchaException exception) { public void onReCaptchaException(final ReCaptchaException exception) {
if (DEBUG) Log.d(TAG, "onReCaptchaException() called"); if (DEBUG) {
Log.d(TAG, "onReCaptchaException() called");
}
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity // Starting ReCaptcha Challenge Activity
Intent intent = new Intent(activity, ReCaptchaActivity.class); Intent intent = new Intent(activity, ReCaptchaActivity.class);
@ -204,33 +234,58 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
showError(getString(R.string.recaptcha_request_toast), false); showError(getString(R.string.recaptcha_request_toast), false);
} }
public void onUnrecoverableError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) { public void onUnrecoverableError(final Throwable exception, final UserAction userAction,
onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName, request, errorId); final String serviceName, final String request,
@StringRes final int errorId) {
onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName,
request, errorId);
} }
public void onUnrecoverableError(List<Throwable> exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) { public void onUnrecoverableError(final List<Throwable> exception, final UserAction userAction,
if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]"); final String serviceName, final String request,
@StringRes final int errorId) {
if (DEBUG) {
Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
}
if (serviceName == null) serviceName = "none"; ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
if (request == null) request = "none"; ErrorActivity.ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
request == null ? "none" : request, errorId));
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
} }
public void showSnackBarError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) { public void showSnackBarError(final Throwable exception, final UserAction userAction,
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request, errorId); final String serviceName, final String request,
@StringRes final int errorId) {
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request,
errorId);
} }
/** /**
* Show a SnackBar and only call ErrorActivity#reportError IF we a find a valid view (otherwise the error screen appears) * Show a SnackBar and only call
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorActivity.ErrorInfo)}
* IF we a find a valid view (otherwise the error screen appears).
*
* @param exception List of the exceptions to show
* @param userAction The user action that caused the exception
* @param serviceName The service where the exception happened
* @param request The page that was requested
* @param errorId The ID of the error
*/ */
public void showSnackBarError(List<Throwable> exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) { public void showSnackBarError(final List<Throwable> exception, final UserAction userAction,
final String serviceName, final String request,
@StringRes final int errorId) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "showSnackBarError() called with: exception = [" + exception + "], userAction = [" + userAction + "], request = [" + request + "], errorId = [" + errorId + "]"); Log.d(TAG, "showSnackBarError() called with: "
+ "exception = [" + exception + "], userAction = [" + userAction + "], "
+ "request = [" + request + "], errorId = [" + errorId + "]");
} }
View rootView = activity != null ? activity.findViewById(android.R.id.content) : null; View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
if (rootView == null && getView() != null) rootView = getView(); if (rootView == null && getView() != null) {
if (rootView == null) return; rootView = getView();
}
if (rootView == null) {
return;
}
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId)); ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));

View File

@ -1,24 +1,26 @@
package org.schabi.newpipe.fragments; package org.schabi.newpipe.fragments;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.Nullable;
import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
public class BlankFragment extends BaseFragment { public class BlankFragment extends BaseFragment {
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
final Bundle savedInstanceState) {
setTitle("NewPipe"); setTitle("NewPipe");
return inflater.inflate(R.layout.fragment_blank, container, false); return inflater.inflate(R.layout.fragment_blank, container, false);
} }
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
setTitle("NewPipe"); setTitle("NewPipe");
// leave this inline. Will make it harder for copy cats. // leave this inline. Will make it harder for copy cats.

View File

@ -1,17 +1,19 @@
package org.schabi.newpipe.fragments; package org.schabi.newpipe.fragments;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.Nullable;
import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
public class EmptyFragment extends BaseFragment { public class EmptyFragment extends BaseFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_empty, container, false); return inflater.inflate(R.layout.fragment_empty, container, false);
} }
} }

View File

@ -50,14 +50,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
tabsManager = TabsManager.getManager(activity); tabsManager = TabsManager.getManager(activity);
tabsManager.setSavedTabsListener(() -> { tabsManager.setSavedTabsListener(() -> {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed()); Log.d(TAG, "TabsManager.SavedTabsChangeListener: "
+ "onTabsChanged called, isResumed = " + isResumed());
} }
if (isResumed()) { if (isResumed()) {
setupTabs(); setupTabs();
@ -68,12 +69,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false); return inflater.inflate(R.layout.fragment_main, container, false);
} }
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
tabLayout = rootView.findViewById(R.id.main_tab_layout); tabLayout = rootView.findViewById(R.id.main_tab_layout);
@ -89,14 +92,18 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (hasTabsChanged) setupTabs(); if (hasTabsChanged) {
setupTabs();
}
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
tabsManager.unsetSavedTabsListener(); tabsManager.unsetSavedTabsListener();
if (viewPager != null) viewPager.setAdapter(null); if (viewPager != null) {
viewPager.setAdapter(null);
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -104,9 +111,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
inflater.inflate(R.menu.main_fragment_menu, menu); inflater.inflate(R.menu.main_fragment_menu, menu);
ActionBar supportActionBar = activity.getSupportActionBar(); ActionBar supportActionBar = activity.getSupportActionBar();
@ -116,7 +126,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_search: case R.id.action_search:
try { try {
@ -141,7 +151,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
tabsList.addAll(tabsManager.getTabs()); tabsList.addAll(tabsManager.getTabs());
if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) { if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) {
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), getChildFragmentManager(), tabsList); pagerAdapter = new SelectedTabsPagerAdapter(requireContext(),
getChildFragmentManager(), tabsList);
} }
viewPager.setAdapter(null); viewPager.setAdapter(null);
@ -165,31 +176,37 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
} }
} }
private void updateTitleForTab(int tabPosition) { private void updateTitleForTab(final int tabPosition) {
setTitle(tabsList.get(tabPosition).getTabName(requireContext())); setTitle(tabsList.get(tabPosition).getTabName(requireContext()));
} }
@Override @Override
public void onTabSelected(TabLayout.Tab selectedTab) { public void onTabSelected(final TabLayout.Tab selectedTab) {
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]"); if (DEBUG) {
Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
}
updateTitleForTab(selectedTab.getPosition()); updateTitleForTab(selectedTab.getPosition());
} }
@Override @Override
public void onTabUnselected(TabLayout.Tab tab) { public void onTabUnselected(final TabLayout.Tab tab) { }
}
@Override @Override
public void onTabReselected(TabLayout.Tab tab) { public void onTabReselected(final TabLayout.Tab tab) {
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]"); if (DEBUG) {
Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
}
updateTitleForTab(tab.getPosition()); updateTitleForTab(tab.getPosition());
} }
private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapterMenuWorkaround { private static final class SelectedTabsPagerAdapter
extends FragmentStatePagerAdapterMenuWorkaround {
private final Context context; private final Context context;
private final List<Tab> internalTabsList; private final List<Tab> internalTabsList;
private SelectedTabsPagerAdapter(Context context, FragmentManager fragmentManager, List<Tab> tabsList) { private SelectedTabsPagerAdapter(final Context context,
final FragmentManager fragmentManager,
final List<Tab> tabsList) {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.context = context; this.context = context;
this.internalTabsList = new ArrayList<>(tabsList); this.internalTabsList = new ArrayList<>(tabsList);
@ -197,7 +214,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
@NonNull @NonNull
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(final int position) {
final Tab tab = internalTabsList.get(position); final Tab tab = internalTabsList.get(position);
Throwable throwable = null; Throwable throwable = null;
@ -209,8 +226,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
} }
if (throwable != null) { if (throwable != null) {
ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
return new BlankFragment(); return new BlankFragment();
} }
@ -222,7 +239,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
} }
@Override @Override
public int getItemPosition(Object object) { public int getItemPosition(final Object object) {
// Causes adapter to reload all Fragments when // Causes adapter to reload all Fragments when
// notifyDataSetChanged is called // notifyDataSetChanged is called
return POSITION_NONE; return POSITION_NONE;
@ -233,7 +250,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
return internalTabsList.size(); return internalTabsList.size();
} }
public boolean sameTabs(List<Tab> tabsToCompare) { public boolean sameTabs(final List<Tab> tabsToCompare) {
return internalTabsList.equals(tabsToCompare); return internalTabsList.equals(tabsToCompare);
} }
} }

View File

@ -9,12 +9,13 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager;
* if the view is scrolled below the last item. * if the view is scrolled below the last item.
*/ */
public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener { public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener {
@Override @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy); super.onScrolled(recyclerView, dx, dy);
if (dy > 0) { if (dy > 0) {
int pastVisibleItems = 0, visibleItemCount, totalItemCount; int pastVisibleItems = 0;
int visibleItemCount;
int totalItemCount;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
visibleItemCount = layoutManager.getChildCount(); visibleItemCount = layoutManager.getChildCount();
@ -22,10 +23,14 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
// Already covers the GridLayoutManager case // Already covers the GridLayoutManager case
if (layoutManager instanceof LinearLayoutManager) { if (layoutManager instanceof LinearLayoutManager) {
pastVisibleItems = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); pastVisibleItems = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) { } else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null); int[] positions = ((StaggeredGridLayoutManager) layoutManager)
if (positions != null && positions.length > 0) pastVisibleItems = positions[0]; .findFirstVisibleItemPositions(null);
if (positions != null && positions.length > 0) {
pastVisibleItems = positions[0];
}
} }
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) { if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {

View File

@ -2,8 +2,11 @@ package org.schabi.newpipe.fragments;
public interface ViewContract<I> { public interface ViewContract<I> {
void showLoading(); void showLoading();
void hideLoading(); void hideLoading();
void showEmptyState(); void showEmptyState();
void showError(String message, boolean showRetryButton); void showError(String message, boolean showRetryButton);
void handleResult(I result); void handleResult(I result);

View File

@ -4,19 +4,15 @@ import java.io.Serializable;
class StackItem implements Serializable { class StackItem implements Serializable {
private final int serviceId; private final int serviceId;
private String title;
private final String url; private final String url;
private String title;
StackItem(int serviceId, String url, String title) { StackItem(final int serviceId, final String url, final String title) {
this.serviceId = serviceId; this.serviceId = serviceId;
this.url = url; this.url = url;
this.title = title; this.title = title;
} }
public void setTitle(String title) {
this.title = title;
}
public int getServiceId() { public int getServiceId() {
return serviceId; return serviceId;
} }
@ -25,6 +21,10 @@ class StackItem implements Serializable {
return title; return title;
} }
public void setTitle(final String title) {
this.title = title;
}
public String getUrl() { public String getUrl() {
return url; return url;
} }

View File

@ -1,27 +1,27 @@
package org.schabi.newpipe.fragments.detail; package org.schabi.newpipe.fragments.detail;
import android.view.ViewGroup;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
import android.view.ViewGroup;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class TabAdaptor extends FragmentPagerAdapter { public class TabAdaptor extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>(); private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>(); private final List<String> mFragmentTitleList = new ArrayList<>();
private final FragmentManager fragmentManager; private final FragmentManager fragmentManager;
public TabAdaptor(FragmentManager fm) { public TabAdaptor(final FragmentManager fm) {
super(fm); super(fm);
this.fragmentManager = fm; this.fragmentManager = fm;
} }
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(final int position) {
return mFragmentList.get(position); return mFragmentList.get(position);
} }
@ -30,7 +30,7 @@ public class TabAdaptor extends FragmentPagerAdapter {
return mFragmentList.size(); return mFragmentList.size();
} }
public void addFragment(Fragment fragment, String title) { public void addFragment(final Fragment fragment, final String title) {
mFragmentList.add(fragment); mFragmentList.add(fragment);
mFragmentTitleList.add(title); mFragmentTitleList.add(title);
} }
@ -40,46 +40,49 @@ public class TabAdaptor extends FragmentPagerAdapter {
mFragmentTitleList.clear(); mFragmentTitleList.clear();
} }
public void removeItem(int position){ public void removeItem(final int position) {
mFragmentList.remove(position == 0 ? 0 : position - 1); mFragmentList.remove(position == 0 ? 0 : position - 1);
mFragmentTitleList.remove(position == 0 ? 0 : position - 1); mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
} }
public void updateItem(int position, Fragment fragment){ public void updateItem(final int position, final Fragment fragment) {
mFragmentList.set(position, fragment); mFragmentList.set(position, fragment);
} }
public void updateItem(String title, Fragment fragment){ public void updateItem(final String title, final Fragment fragment) {
int index = mFragmentTitleList.indexOf(title); int index = mFragmentTitleList.indexOf(title);
if(index != -1){ if (index != -1) {
updateItem(index, fragment); updateItem(index, fragment);
} }
} }
@Override @Override
public int getItemPosition(Object object) { public int getItemPosition(final Object object) {
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object); if (mFragmentList.contains(object)) {
else return POSITION_NONE; return mFragmentList.indexOf(object);
} else {
return POSITION_NONE;
}
} }
public int getItemPositionByTitle(String title) { public int getItemPositionByTitle(final String title) {
return mFragmentTitleList.indexOf(title); return mFragmentTitleList.indexOf(title);
} }
@Nullable @Nullable
public String getItemTitle(int position) { public String getItemTitle(final int position) {
if (position < 0 || position >= mFragmentTitleList.size()) { if (position < 0 || position >= mFragmentTitleList.size()) {
return null; return null;
} }
return mFragmentTitleList.get(position); return mFragmentTitleList.get(position);
} }
public void notifyDataSetUpdate(){ public void notifyDataSetUpdate() {
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override @Override
public void destroyItem(ViewGroup container, int position, Object object) { public void destroyItem(final ViewGroup container, final int position, final Object object) {
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss(); fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
} }

View File

@ -7,16 +7,17 @@ import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
@ -40,24 +41,26 @@ import java.util.Queue;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener { public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N>, StateSaver.WriteRead,
SharedPreferences.OnSharedPreferenceChangeListener {
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected InfoListAdapter infoListAdapter; protected InfoListAdapter infoListAdapter;
protected RecyclerView itemsList; protected RecyclerView itemsList;
private int updateFlags = 0; protected StateSaver.SavedState savedState;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// LifeCycle // LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private boolean useDefaultStateSaving = true;
private int updateFlags = 0;
@Override @Override
public void onAttach(Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
if (infoListAdapter == null) { if (infoListAdapter == null) {
@ -71,17 +74,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity) PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this); .registerOnSharedPreferenceChangeListener(this);
} }
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (useDefaultStateSaving) StateSaver.onDestroy(savedState); if (useDefaultStateSaving) {
StateSaver.onDestroy(savedState);
}
PreferenceManager.getDefaultSharedPreferences(activity) PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this); .unregisterOnSharedPreferenceChangeListener(this);
} }
@ -93,28 +102,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
if (updateFlags != 0) { if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout(); final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); itemsList.setLayoutManager(useGrid
infoListAdapter.setGridItemVariants(useGrid); ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.notifyDataSetChanged(); infoListAdapter.notifyDataSetChanged();
} }
updateFlags = 0; updateFlags = 0;
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
protected StateSaver.SavedState savedState;
protected boolean useDefaultStateSaving = true;
/** /**
* If the default implementation of {@link StateSaver.WriteRead} should be used. * If the default implementation of {@link StateSaver.WriteRead} should be used.
* *
* @see StateSaver * @see StateSaver
* @param useDefaultStateSaving Whether the default implementation should be used
*/ */
public void useDefaultStateSaving(boolean useDefault) { public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
this.useDefaultStateSaving = useDefault; this.useDefaultStateSaving = useDefaultStateSaving;
} }
@Override @Override
@ -124,13 +128,15 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
} }
@Override @Override
public void writeTo(Queue<Object> objectsToSave) { public void writeTo(final Queue<Object> objectsToSave) {
if (useDefaultStateSaving) objectsToSave.add(infoListAdapter.getItemsList()); if (useDefaultStateSaving) {
objectsToSave.add(infoListAdapter.getItemsList());
}
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception { public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
if (useDefaultStateSaving) { if (useDefaultStateSaving) {
infoListAdapter.getItemsList().clear(); infoListAdapter.getItemsList().clear();
infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll()); infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll());
@ -138,15 +144,20 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
} }
@Override @Override
public void onSaveInstanceState(Bundle bundle) { public void onSaveInstanceState(final Bundle bundle) {
super.onSaveInstanceState(bundle); super.onSaveInstanceState(bundle);
if (useDefaultStateSaving) savedState = StateSaver.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this); if (useDefaultStateSaving) {
savedState = StateSaver
.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this);
}
} }
@Override @Override
protected void onRestoreInstanceState(@NonNull Bundle bundle) { protected void onRestoreInstanceState(@NonNull final Bundle bundle) {
super.onRestoreInstanceState(bundle); super.onRestoreInstanceState(bundle);
if (useDefaultStateSaving) savedState = StateSaver.tryToRestore(bundle, this); if (useDefaultStateSaving) {
savedState = StateSaver.tryToRestore(bundle, this);
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -169,29 +180,32 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
final Resources resources = activity.getResources(); final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width); int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density); width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width); final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
/ (double) width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
return lm; return lm;
} }
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
final boolean useGrid = isGridLayout(); final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list); itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid); infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.setFooter(getListFooter()); infoListAdapter.setFooter(getListFooter());
infoListAdapter.setHeader(getListHeader()); infoListAdapter.setHeader(getListHeader());
itemsList.setAdapter(infoListAdapter); itemsList.setAdapter(infoListAdapter);
} }
protected void onItemSelected(InfoItem selectedItem) { protected void onItemSelected(final InfoItem selectedItem) {
if (DEBUG) Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]"); if (DEBUG) {
Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
}
} }
@Override @Override
@ -199,19 +213,19 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
super.initListeners(); super.initListeners();
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() { infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
@Override @Override
public void selected(StreamInfoItem selectedItem) { public void selected(final StreamInfoItem selectedItem) {
onStreamSelected(selectedItem); onStreamSelected(selectedItem);
} }
@Override @Override
public void held(StreamInfoItem selectedItem) { public void held(final StreamInfoItem selectedItem) {
showStreamDialog(selectedItem); showStreamDialog(selectedItem);
} }
}); });
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() { infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
@Override @Override
public void selected(ChannelInfoItem selectedItem) { public void selected(final ChannelInfoItem selectedItem) {
try { try {
onItemSelected(selectedItem); onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(getFM(), NavigationHelper.openChannelFragment(getFM(),
@ -226,7 +240,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() { infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
@Override @Override
public void selected(PlaylistInfoItem selectedItem) { public void selected(final PlaylistInfoItem selectedItem) {
try { try {
onItemSelected(selectedItem); onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(getFM(), NavigationHelper.openPlaylistFragment(getFM(),
@ -241,7 +255,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() { infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
@Override @Override
public void selected(CommentsInfoItem selectedItem) { public void selected(final CommentsInfoItem selectedItem) {
onItemSelected(selectedItem); onItemSelected(selectedItem);
} }
}); });
@ -249,13 +263,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
itemsList.clearOnScrollListeners(); itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() { itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
@Override @Override
public void onScrolledDown(RecyclerView recyclerView) { public void onScrolledDown(final RecyclerView recyclerView) {
onScrollToBottom(); onScrollToBottom();
} }
}); });
} }
private void onStreamSelected(StreamInfoItem selectedItem) { private void onStreamSelected(final StreamInfoItem selectedItem) {
onItemSelected(selectedItem); onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(getFM(), NavigationHelper.openVideoDetailFragment(getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName()); selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
@ -268,12 +282,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
} }
protected void showStreamDialog(final StreamInfoItem item) { protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext(); final Context context = getContext();
final Activity activity = getActivity(); final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) return; if (context == null || context.getResources() == null || activity == null) {
return;
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) { if (item.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries( StreamDialogEntry.setEnabledEntries(
@ -291,8 +305,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
StreamDialogEntry.share); StreamDialogEntry.share);
} }
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) -> new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
StreamDialogEntry.clickOn(which, this, item)).show(); (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -300,8 +314,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar(); ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) { if (supportActionBar != null) {
@ -339,7 +356,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
} }
@Override @Override
public void showError(String message, boolean showRetryButton) { public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton); super.showError(message, showRetryButton);
showListFooter(false); showListFooter(false);
animateView(itemsList, false, 200); animateView(itemsList, false, 200);
@ -361,25 +378,28 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
} }
@Override @Override
public void handleNextItems(N result) { public void handleNextItems(final N result) {
isLoading.set(false); isLoading.set(false);
} }
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.list_view_mode_key))) { if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG; updateFlags |= LIST_MODE_UPDATE_FLAG;
} }
} }
protected boolean isGridLayout() { protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)); final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
if ("auto".equals(list_mode)) { .getString(getString(R.string.list_view_mode_key),
getString(R.string.list_view_mode_value));
if ("auto".equals(listMode)) {
final Configuration configuration = getResources().getConfiguration(); final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else { } else {
return "grid".equals(list_mode); return "grid".equals(listMode);
} }
} }
} }

View File

@ -21,7 +21,6 @@ import io.reactivex.schedulers.Schedulers;
public abstract class BaseListInfoFragment<I extends ListInfo> public abstract class BaseListInfoFragment<I extends ListInfo>
extends BaseListFragment<I, ListExtractor.InfoItemsPage> { extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
@State @State
protected int serviceId = Constants.NO_SERVICE_ID; protected int serviceId = Constants.NO_SERVICE_ID;
@State @State
@ -34,7 +33,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
protected Disposable currentWorker; protected Disposable currentWorker;
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
setTitle(name); setTitle(name);
showListFooter(hasMoreItems()); showListFooter(hasMoreItems());
@ -43,7 +42,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (currentWorker != null) currentWorker.dispose(); if (currentWorker != null) {
currentWorker.dispose();
}
} }
@Override @Override
@ -73,7 +74,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void writeTo(Queue<Object> objectsToSave) { public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave); super.writeTo(objectsToSave);
objectsToSave.add(currentInfo); objectsToSave.add(currentInfo);
objectsToSave.add(currentNextPageUrl); objectsToSave.add(currentNextPageUrl);
@ -81,7 +82,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception { public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects); super.readFrom(savedObjects);
currentInfo = (I) savedObjects.poll(); currentInfo = (I) savedObjects.poll();
currentNextPageUrl = (String) savedObjects.poll(); currentNextPageUrl = (String) savedObjects.poll();
@ -92,10 +93,14 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected void doInitialLoadLogic() { protected void doInitialLoadLogic() {
if (DEBUG) Log.d(TAG, "doInitialLoadLogic() called"); if (DEBUG) {
Log.d(TAG, "doInitialLoadLogic() called");
}
if (currentInfo == null) { if (currentInfo == null) {
startLoading(false); startLoading(false);
} else handleResult(currentInfo); } else {
handleResult(currentInfo);
}
} }
/** /**
@ -103,18 +108,21 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}. * You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}.
* *
* @param forceLoad allow or disallow the result to come from the cache * @param forceLoad allow or disallow the result to come from the cache
* @return Rx {@link Single} containing the {@link ListInfo}
*/ */
protected abstract Single<I> loadResult(boolean forceLoad); protected abstract Single<I> loadResult(boolean forceLoad);
@Override @Override
public void startLoading(boolean forceLoad) { public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad); super.startLoading(forceLoad);
showListFooter(false); showListFooter(false);
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
currentInfo = null; currentInfo = null;
if (currentWorker != null) currentWorker.dispose(); if (currentWorker != null) {
currentWorker.dispose();
}
currentWorker = loadResult(forceLoad) currentWorker = loadResult(forceLoad)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -127,19 +135,25 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
} }
/** /**
* Implement the logic to load more items<br/> * Implement the logic to load more items.
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper} * <p>You can use the default implementations
* from {@link org.schabi.newpipe.util.ExtractorHelper}.</p>
*
* @return Rx {@link Single} containing the {@link ListExtractor.InfoItemsPage}
*/ */
protected abstract Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic(); protected abstract Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic();
protected void loadMoreItems() { protected void loadMoreItems() {
isLoading.set(true); isLoading.set(true);
if (currentWorker != null) currentWorker.dispose(); if (currentWorker != null) {
currentWorker.dispose();
}
currentWorker = loadMoreItemsLogic() currentWorker = loadMoreItemsLogic()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> { .subscribe((@io.reactivex.annotations.NonNull
ListExtractor.InfoItemsPage InfoItemsPage) -> {
isLoading.set(false); isLoading.set(false);
handleNextItems(InfoItemsPage); handleNextItems(InfoItemsPage);
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> { }, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
@ -149,7 +163,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
} }
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result); super.handleNextItems(result);
currentNextPageUrl = result.getNextPageUrl(); currentNextPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems()); infoListAdapter.addInfoItemList(result.getItems());
@ -167,7 +181,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void handleResult(@NonNull I result) { public void handleResult(@NonNull final I result) {
super.handleResult(result); super.handleResult(result);
name = result.getName(); name = result.getName();
@ -188,9 +202,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected void setInitialData(int serviceId, String url, String name) { protected void setInitialData(final int sid, final String u, final String title) {
this.serviceId = serviceId; this.serviceId = sid;
this.url = url; this.url = u;
this.name = !TextUtils.isEmpty(name) ? name : ""; this.name = !TextUtils.isEmpty(title) ? title : "";
} }
} }

View File

@ -4,10 +4,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.ActionBar;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -21,6 +17,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding2.view.RxView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -29,7 +30,6 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
@ -63,15 +63,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private final CompositeDisposable disposables = new CompositeDisposable(); private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor; private Disposable subscribeButtonMonitor;
private SubscriptionManager subscriptionManager;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private SubscriptionManager subscriptionManager;
private View headerRootLayout; private View headerRootLayout;
private ImageView headerChannelBanner; private ImageView headerChannelBanner;
private ImageView headerAvatarView; private ImageView headerAvatarView;
@ -79,25 +78,24 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private TextView headerSubscribersTextView; private TextView headerSubscribersTextView;
private Button headerSubscribeButton; private Button headerSubscribeButton;
private View playlistCtrl; private View playlistCtrl;
private LinearLayout headerPlayAllButton; private LinearLayout headerPlayAllButton;
private LinearLayout headerPopupButton; private LinearLayout headerPopupButton;
private LinearLayout headerBackgroundButton; private LinearLayout headerBackgroundButton;
private MenuItem menuRssButton; private MenuItem menuRssButton;
public static ChannelFragment getInstance(int serviceId, String url, String name) {
ChannelFragment instance = new ChannelFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// LifeCycle // LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) {
ChannelFragment instance = new ChannelFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
if (activity != null if (activity != null
&& useAsFrontPage && useAsFrontPage
@ -107,29 +105,40 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
subscriptionManager = new SubscriptionManager(activity); subscriptionManager = new SubscriptionManager(activity);
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_channel, container, false); return inflater.inflate(R.layout.fragment_channel, container, false);
} }
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Init // Init
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) {
disposables.clear();
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
protected View getListHeader() { protected View getListHeader() {
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, itemsList, false); headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.channel_header, itemsList, false);
headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image); headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image);
headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view); headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view);
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view); headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
@ -145,12 +154,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return headerRootLayout; return headerRootLayout;
} }
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar(); ActionBar supportActionBar = activity.getSupportActionBar();
if (useAsFrontPage && supportActionBar != null) { if (useAsFrontPage && supportActionBar != null) {
@ -158,8 +163,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} else { } else {
inflater.inflate(R.menu.menu_channel, menu); inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + if (DEBUG) {
"], inflater = [" + inflater + "]"); Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
menuRssButton = menu.findItem(R.id.menu_item_rss); menuRssButton = menu.findItem(R.id.menu_item_rss);
} }
} }
@ -172,8 +179,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_settings: case R.id.action_settings:
NavigationHelper.openSettings(requireContext()); NavigationHelper.openSettings(requireContext());
@ -197,22 +208,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return true; return true;
} }
/*//////////////////////////////////////////////////////////////////////////
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private void monitorSubscription(final ChannelInfo info) { private void monitorSubscription(final ChannelInfo info) {
final Consumer<Throwable> onError = (Throwable throwable) -> { final Consumer<Throwable> onError = (Throwable throwable) -> {
animateView(headerSubscribeButton, false, 100); animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION, showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()), NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status", "Get subscription status", 0);
0);
}; };
final Observable<List<SubscriptionEntity>> observable = subscriptionManager.subscriptionTable() final Observable<List<SubscriptionEntity>> observable = subscriptionManager
.subscriptionTable()
.getSubscriptionFlowable(info.getServiceId(), info.getUrl()) .getSubscriptionFlowable(info.getServiceId(), info.getUrl())
.toObservable(); .toObservable();
@ -221,17 +226,19 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.subscribe(getSubscribeUpdateMonitor(info), onError)); .subscribe(getSubscribeUpdateMonitor(info), onError));
disposables.add(observable disposables.add(observable
// Some updates are very rapid (when calling the updateSubscription(info), for example) // Some updates are very rapid
// so only update the UI for the latest emission ("sync" the subscribe button's state) // (for example when calling the updateSubscription(info))
// so only update the UI for the latest emission
// ("sync" the subscribe button's state)
.debounce(100, TimeUnit.MILLISECONDS) .debounce(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((List<SubscriptionEntity> subscriptionEntities) -> .subscribe((List<SubscriptionEntity> subscriptionEntities) ->
updateSubscribeButton(!subscriptionEntities.isEmpty()) updateSubscribeButton(!subscriptionEntities.isEmpty()), onError));
, onError));
} }
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription, ChannelInfo info) { private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription,
final ChannelInfo info) {
return (@NonNull Object o) -> { return (@NonNull Object o) -> {
subscriptionManager.insertSubscription(subscription, info); subscriptionManager.insertSubscription(subscription, info);
return o; return o;
@ -246,9 +253,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} }
private void updateSubscription(final ChannelInfo info) { private void updateSubscription(final ChannelInfo info) {
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]"); if (DEBUG) {
Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
}
final Action onComplete = () -> { final Action onComplete = () -> {
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl()); if (DEBUG) {
Log.d(TAG, "Updated subscription: " + info.getUrl());
}
}; };
final Consumer<Throwable> onError = (@NonNull Throwable throwable) -> final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
@ -264,9 +275,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.subscribe(onComplete, onError)); .subscribe(onComplete, onError));
} }
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) { private Disposable monitorSubscribeButton(final Button subscribeButton,
final Function<Object, Object> action) {
final Consumer<Object> onNext = (@NonNull Object o) -> { final Consumer<Object> onNext = (@NonNull Object o) -> {
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!"); if (DEBUG) {
Log.d(TAG, "Changed subscription status to this channel!");
}
}; };
final Consumer<Throwable> onError = (@NonNull Throwable throwable) -> final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
@ -287,12 +301,18 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) { private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
return (List<SubscriptionEntity> subscriptionEntities) -> { return (List<SubscriptionEntity> subscriptionEntities) -> {
if (DEBUG) if (DEBUG) {
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]"); Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: "
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); + "subscriptionEntities = [" + subscriptionEntities + "]");
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
if (subscriptionEntities.isEmpty()) { if (subscriptionEntities.isEmpty()) {
if (DEBUG) Log.d(TAG, "No subscription to this channel!"); if (DEBUG) {
Log.d(TAG, "No subscription to this channel!");
}
SubscriptionEntity channel = new SubscriptionEntity(); SubscriptionEntity channel = new SubscriptionEntity();
channel.setServiceId(info.getServiceId()); channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl()); channel.setUrl(info.getUrl());
@ -300,34 +320,45 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
info.getAvatarUrl(), info.getAvatarUrl(),
info.getDescription(), info.getDescription(),
info.getSubscriberCount()); info.getSubscriberCount());
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel, info)); subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
mapOnSubscribe(channel, info));
} else { } else {
if (DEBUG) Log.d(TAG, "Found subscription to this channel!"); if (DEBUG) {
Log.d(TAG, "Found subscription to this channel!");
}
final SubscriptionEntity subscription = subscriptionEntities.get(0); final SubscriptionEntity subscription = subscriptionEntities.get(0);
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription)); subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
mapOnUnsubscribe(subscription));
} }
}; };
} }
private void updateSubscribeButton(boolean isSubscribed) { private void updateSubscribeButton(final boolean isSubscribed) {
if (DEBUG) Log.d(TAG, "updateSubscribeButton() called with: isSubscribed = [" + isSubscribed + "]"); if (DEBUG) {
Log.d(TAG, "updateSubscribeButton() called with: "
+ "isSubscribed = [" + isSubscribed + "]");
}
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE; boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
int backgroundDuration = isButtonVisible ? 300 : 0; int backgroundDuration = isButtonVisible ? 300 : 0;
int textDuration = isButtonVisible ? 200 : 0; int textDuration = isButtonVisible ? 200 : 0;
int subscribeBackground = ContextCompat.getColor(activity, R.color.subscribe_background_color); int subscribeBackground = ContextCompat
.getColor(activity, R.color.subscribe_background_color);
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color); int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
int subscribedBackground = ContextCompat.getColor(activity, R.color.subscribed_background_color); int subscribedBackground = ContextCompat
.getColor(activity, R.color.subscribed_background_color);
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color); int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
if (!isSubscribed) { if (!isSubscribed) {
headerSubscribeButton.setText(R.string.subscribe_button_title); headerSubscribeButton.setText(R.string.subscribe_button_title);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground, subscribeBackground); animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground,
subscribeBackground);
animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText); animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText);
} else { } else {
headerSubscribeButton.setText(R.string.subscribed_button_title); headerSubscribeButton.setText(R.string.subscribed_button_title);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground, subscribedBackground); animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground,
subscribedBackground);
animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText); animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText);
} }
@ -344,7 +375,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} }
@Override @Override
protected Single<ChannelInfo> loadResult(boolean forceLoad) { protected Single<ChannelInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad); return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
} }
@ -356,47 +387,55 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
public void showLoading() { public void showLoading() {
super.showLoading(); super.showLoading();
imageLoader.cancelDisplayTask(headerChannelBanner); IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
imageLoader.cancelDisplayTask(headerAvatarView); IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
animateView(headerSubscribeButton, false, 100); animateView(headerSubscribeButton, false, 100);
} }
@Override @Override
public void handleResult(@NonNull ChannelInfo result) { public void handleResult(@NonNull final ChannelInfo result) {
super.handleResult(result); super.handleResult(result);
headerRootLayout.setVisibility(View.VISIBLE); headerRootLayout.setVisibility(View.VISIBLE);
imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner, IMAGE_LOADER.displayImage(result.getBannerUrl(), headerChannelBanner,
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerSubscribersTextView.setVisibility(View.VISIBLE); headerSubscribersTextView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) { if (result.getSubscriberCount() >= 0) {
headerSubscribersTextView.setText(Localization.shortSubscriberCount(activity, result.getSubscriberCount())); headerSubscribersTextView.setText(Localization
.shortSubscriberCount(activity, result.getSubscriberCount()));
} else { } else {
headerSubscribersTextView.setText(R.string.subscribers_count_not_available); headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
} }
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); if (menuRssButton != null) {
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
}
playlistCtrl.setVisibility(View.VISIBLE); playlistCtrl.setVisibility(View.VISIBLE);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
} }
if (disposables != null) disposables.clear(); if (disposables != null) {
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); disposables.clear();
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
updateSubscription(result); updateSubscription(result);
monitorSubscription(result); monitorSubscription(result);
headerPlayAllButton.setOnClickListener( headerPlayAllButton.setOnClickListener(view -> NavigationHelper
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); .playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener( headerPopupButton.setOnClickListener(view -> NavigationHelper
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); .playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener( headerBackgroundButton.setOnClickListener(view -> NavigationHelper
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); .playOnBackgroundPlayer(activity, getPlayQueue(), false));
} }
private PlayQueue getPlayQueue() { private PlayQueue getPlayQueue() {
@ -410,17 +449,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
streamItems.add((StreamInfoItem) i); streamItems.add((StreamInfoItem) i);
} }
} }
return new ChannelPlayQueue( return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getServiceId(), currentInfo.getNextPageUrl(), streamItems, index);
currentInfo.getUrl(),
currentInfo.getNextPageUrl(),
streamItems,
index
);
} }
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result); super.handleNextItems(result);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
@ -437,8 +471,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
if (super.onError(exception)) return true; if (super.onError(exception)) {
return true;
}
int errorId = exception instanceof ExtractionException int errorId = exception instanceof ExtractionException
? R.string.parsing_error : R.string.general_error; ? R.string.parsing_error : R.string.general_error;
@ -454,8 +490,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void setTitle(String title) { public void setTitle(final String title) {
super.setTitle(title); super.setTitle(title);
if (!useAsFrontPage) headerTitleView.setText(title); if (!useAsFrontPage) {
headerTitleView.setText(title);
}
} }
} }

View File

@ -2,14 +2,15 @@ package org.schabi.newpipe.fragments.list.comments;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
@ -23,17 +24,12 @@ import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> { public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private CompositeDisposable disposables = new CompositeDisposable(); private CompositeDisposable disposables = new CompositeDisposable();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private boolean mIsVisibleToUser = false; private boolean mIsVisibleToUser = false;
public static CommentsFragment getInstance(int serviceId, String url, String name) { public static CommentsFragment getInstance(final int serviceId, final String url,
final String name) {
CommentsFragment instance = new CommentsFragment(); CommentsFragment instance = new CommentsFragment();
instance.setInitialData(serviceId, url, name); instance.setInitialData(serviceId, url, name);
return instance; return instance;
@ -44,28 +40,31 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser; mIsVisibleToUser = isVisibleToUser;
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_comments, container, false); return inflater.inflate(R.layout.fragment_comments, container, false);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (disposables != null) disposables.clear(); if (disposables != null) {
disposables.clear();
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Load and handle // Load and handle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -76,7 +75,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
} }
@Override @Override
protected Single<CommentsInfo> loadResult(boolean forceLoad) { protected Single<CommentsInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
} }
@ -90,27 +89,28 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
} }
@Override @Override
public void handleResult(@NonNull CommentsInfo result) { public void handleResult(@NonNull final CommentsInfo result) {
super.handleResult(result); super.handleResult(result);
AnimationUtils.slideUp(getView(),120, 150, 0.06f); AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
} }
if (disposables != null) disposables.clear(); if (disposables != null) {
disposables.clear();
}
} }
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result); super.handleNextItems(result);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), "Get next page of: " + url,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
R.string.general_error); R.string.general_error);
} }
} }
@ -120,11 +120,14 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
if (super.onError(exception)) return true; if (super.onError(exception)) {
return true;
}
hideLoading(); hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments); showSnackBarError(exception, UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
return true; return true;
} }
@ -133,14 +136,10 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void setTitle(String title) { public void setTitle(final String title) { }
return;
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { }
return;
}
@Override @Override
protected boolean isGridLayout() { protected boolean isGridLayout() {

View File

@ -10,9 +10,8 @@ import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
public class DefaultKioskFragment extends KioskFragment { public class DefaultKioskFragment extends KioskFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (serviceId < 0) { if (serviceId < 0) {
@ -25,7 +24,9 @@ public class DefaultKioskFragment extends KioskFragment {
super.onResume(); super.onResume();
if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) { if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) {
if (currentWorker != null) currentWorker.dispose(); if (currentWorker != null) {
currentWorker.dispose();
}
updateSelectedDefaultKiosk(); updateSelectedDefaultKiosk();
reloadContent(); reloadContent();
} }
@ -45,7 +46,8 @@ public class DefaultKioskFragment extends KioskFragment {
currentInfo = null; currentInfo = null;
currentNextPageUrl = null; currentNextPageUrl = null;
} catch (ExtractionException e) { } catch (ExtractionException e) {
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none", "Loading default kiosk from selected service", 0); onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
"Loading default kiosk from selected service", 0);
} }
} }
} }

View File

@ -1,17 +1,16 @@
package org.schabi.newpipe.fragments.list.kiosk; package org.schabi.newpipe.fragments.list.kiosk;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import android.preference.PreferenceManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
@ -33,45 +32,45 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
/** /**
* Created by Christian Schabesberger on 23.09.17. * Created by Christian Schabesberger on 23.09.17.
* * <p>
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
* KioskFragment.java is part of NewPipe. * KioskFragment.java is part of NewPipe.
* * </p>
* <p>
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* * </p>
* <p>
* NewPipe is distributed in the hope that it will be useful, * NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* * </p>
* <p>
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
* </p>
*/ */
public class KioskFragment extends BaseListInfoFragment<KioskInfo> { public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
@State @State
protected String kioskId = ""; String kioskId = "";
protected String kioskTranslatedName; String kioskTranslatedName;
@State @State
protected ContentCountry contentCountry; ContentCountry contentCountry;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static KioskFragment getInstance(int serviceId) public static KioskFragment getInstance(final int serviceId) throws ExtractionException {
throws ExtractionException {
return getInstance(serviceId, NewPipe.getService(serviceId) return getInstance(serviceId, NewPipe.getService(serviceId)
.getKioskList() .getKioskList().getDefaultKioskId());
.getDefaultKioskId());
} }
public static KioskFragment getInstance(int serviceId, String kioskId) public static KioskFragment getInstance(final int serviceId, final String kioskId)
throws ExtractionException { throws ExtractionException {
KioskFragment instance = new KioskFragment(); KioskFragment instance = new KioskFragment();
StreamingService service = NewPipe.getService(serviceId); StreamingService service = NewPipe.getService(serviceId);
@ -88,7 +87,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity); kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity);
@ -97,9 +96,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
} }
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
if(useAsFrontPage && isVisibleToUser && activity != null) { if (useAsFrontPage && isVisibleToUser && activity != null) {
try { try {
setTitle(kioskTranslatedName); setTitle(kioskTranslatedName);
} catch (Exception e) { } catch (Exception e) {
@ -111,7 +110,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_kiosk, container, false); return inflater.inflate(R.layout.fragment_kiosk, container, false);
} }
@ -129,7 +130,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar(); ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null && useAsFrontPage) { if (supportActionBar != null && useAsFrontPage) {
@ -142,18 +143,14 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public Single<KioskInfo> loadResult(boolean forceReload) { public Single<KioskInfo> loadResult(final boolean forceReload) {
contentCountry = Localization.getPreferredContentCountry(requireContext()); contentCountry = Localization.getPreferredContentCountry(requireContext());
return ExtractorHelper.getKioskInfo(serviceId, return ExtractorHelper.getKioskInfo(serviceId, url, forceReload);
url,
forceReload);
} }
@Override @Override
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() { public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreKioskItems(serviceId, return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
url,
currentNextPageUrl);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -181,13 +178,13 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
} }
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result); super.handleNextItems(result);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), showSnackBarError(result.getErrors(),
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId) UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId),
, "Get next page of: " + url, 0); "Get next page of: " + url, 0);
} }
} }
} }

View File

@ -3,9 +3,6 @@ package org.schabi.newpipe.fragments.list.playlist;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -17,6 +14,10 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
@ -57,7 +58,6 @@ import io.reactivex.disposables.Disposables;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> { public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private CompositeDisposable disposables; private CompositeDisposable disposables;
private Subscription bookmarkReactor; private Subscription bookmarkReactor;
private AtomicBoolean isBookmarkButtonReady; private AtomicBoolean isBookmarkButtonReady;
@ -82,7 +82,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private MenuItem playlistBookmarkButton; private MenuItem playlistBookmarkButton;
public static PlaylistFragment getInstance(int serviceId, String url, String name) { public static PlaylistFragment getInstance(final int serviceId, final String url,
final String name) {
PlaylistFragment instance = new PlaylistFragment(); PlaylistFragment instance = new PlaylistFragment();
instance.setInitialData(serviceId, url, name); instance.setInitialData(serviceId, url, name);
return instance; return instance;
@ -93,17 +94,18 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
disposables = new CompositeDisposable(); disposables = new CompositeDisposable();
isBookmarkButtonReady = new AtomicBoolean(false); isBookmarkButtonReady = new AtomicBoolean(false);
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase.getInstance( remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase
requireContext())); .getInstance(requireContext()));
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable Bundle savedInstanceState) { @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_playlist, container, false); return inflater.inflate(R.layout.fragment_playlist, container, false);
} }
@ -112,7 +114,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected View getListHeader() { protected View getListHeader() {
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_header, itemsList, false); headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.playlist_header, itemsList, false);
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view); headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout); headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout);
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name); headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
@ -129,21 +132,23 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
} }
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
infoListAdapter.useMiniItemVariants(true); infoListAdapter.setUseMiniVariant(true);
} }
private PlayQueue getPlayQueueStartingAt(StreamInfoItem infoItem) { private PlayQueue getPlayQueueStartingAt(final StreamInfoItem infoItem) {
return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0)); return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0));
} }
@Override @Override
protected void showStreamDialog(StreamInfoItem item) { protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext(); final Context context = getContext();
final Activity activity = getActivity(); final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) return; if (context == null || context.getResources() == null || activity == null) {
return;
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) { if (item.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries( StreamDialogEntry.setEnabledEntries(
@ -160,21 +165,25 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share);
StreamDialogEntry.start_here_on_popup.setCustomAction( StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItem) ->
(fragment, infoItem) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(infoItem), true)); NavigationHelper.playOnPopupPlayer(context,
getPlayQueueStartingAt(infoItem), true));
} }
StreamDialogEntry.start_here_on_background.setCustomAction( StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) ->
(fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(infoItem), true)); NavigationHelper.playOnBackgroundPlayer(context,
getPlayQueueStartingAt(infoItem), true));
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) -> new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
StreamDialogEntry.clickOn(which, this, item)).show(); (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + if (DEBUG) {
"], inflater = [" + inflater + "]"); Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_playlist, menu); inflater.inflate(R.menu.menu_playlist, menu);
@ -185,10 +194,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if (isBookmarkButtonReady != null) isBookmarkButtonReady.set(false); if (isBookmarkButtonReady != null) {
isBookmarkButtonReady.set(false);
}
if (disposables != null) disposables.clear(); if (disposables != null) {
if (bookmarkReactor != null) bookmarkReactor.cancel(); disposables.clear();
}
if (bookmarkReactor != null) {
bookmarkReactor.cancel();
}
bookmarkReactor = null; bookmarkReactor = null;
} }
@ -197,7 +212,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (disposables != null) disposables.dispose(); if (disposables != null) {
disposables.dispose();
}
disposables = null; disposables = null;
remotePlaylistManager = null; remotePlaylistManager = null;
@ -215,12 +232,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
} }
@Override @Override
protected Single<PlaylistInfo> loadResult(boolean forceLoad) { protected Single<PlaylistInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad); return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_settings: case R.id.action_settings:
NavigationHelper.openSettings(requireContext()); NavigationHelper.openSettings(requireContext());
@ -251,7 +268,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
animateView(headerRootLayout, false, 200); animateView(headerRootLayout, false, 200);
animateView(itemsList, false, 100); animateView(itemsList, false, 100);
imageLoader.cancelDisplayTask(headerUploaderAvatar); IMAGE_LOADER.cancelDisplayTask(headerUploaderAvatar);
animateView(headerUploaderLayout, false, 200); animateView(headerUploaderLayout, false, 200);
} }
@ -262,7 +279,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
animateView(headerRootLayout, true, 100); animateView(headerRootLayout, true, 100);
animateView(headerUploaderLayout, true, 300); animateView(headerUploaderLayout, true, 300);
headerUploaderLayout.setOnClickListener(null); headerUploaderLayout.setOnClickListener(null);
if (!TextUtils.isEmpty(result.getUploaderName())) { // If we have an uploader : Put them into the ui // If we have an uploader put them into the UI
if (!TextUtils.isEmpty(result.getUploaderName())) {
headerUploaderName.setText(result.getUploaderName()); headerUploaderName.setText(result.getUploaderName());
if (!TextUtils.isEmpty(result.getUploaderUrl())) { if (!TextUtils.isEmpty(result.getUploaderUrl())) {
headerUploaderLayout.setOnClickListener(v -> { headerUploaderLayout.setOnClickListener(v -> {
@ -276,19 +294,20 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
} }
}); });
} }
} else { // Else : say we have no uploader } else { // Else say we have no uploader
headerUploaderName.setText(R.string.playlist_no_uploader); headerUploaderName.setText(R.string.playlist_no_uploader);
} }
playlistCtrl.setVisibility(View.VISIBLE); playlistCtrl.setVisibility(View.VISIBLE);
imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos,
(int) result.getStreamCount(), (int) result.getStreamCount())); (int) result.getStreamCount(), (int) result.getStreamCount()));
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
} }
remotePlaylistManager.getPlaylist(result) remotePlaylistManager.getPlaylist(result)
@ -321,8 +340,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private PlayQueue getPlayQueue(final int index) { private PlayQueue getPlayQueue(final int index) {
final List<StreamInfoItem> infoItems = new ArrayList<>(); final List<StreamInfoItem> infoItems = new ArrayList<>();
for(InfoItem i : infoListAdapter.getItemsList()) { for (InfoItem i : infoListAdapter.getItemsList()) {
if(i instanceof StreamInfoItem) { if (i instanceof StreamInfoItem) {
infoItems.add((StreamInfoItem) i); infoItems.add((StreamInfoItem) i);
} }
} }
@ -336,12 +355,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
} }
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result); super.handleNextItems(result);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId) showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
, "Get next page of: " + url, 0); NewPipe.getNameOfService(serviceId), "Get next page of: " + url, 0);
} }
} }
@ -350,15 +369,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
if (super.onError(exception)) return true; if (super.onError(exception)) {
return true;
}
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error; int errorId = exception instanceof ExtractionException
onUnrecoverableError(exception, ? R.string.parsing_error : R.string.general_error;
UserAction.REQUESTED_PLAYLIST, onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(serviceId), NewPipe.getNameOfService(serviceId), url, errorId);
url,
errorId);
return true; return true;
} }
@ -366,13 +385,18 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private Flowable<Integer> getUpdateProcessor(@NonNull List<PlaylistRemoteEntity> playlists, private Flowable<Integer> getUpdateProcessor(
@NonNull PlaylistInfo result) { @NonNull final List<PlaylistRemoteEntity> playlists,
@NonNull final PlaylistInfo result) {
final Flowable<Integer> noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1); final Flowable<Integer> noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1);
if (playlists.isEmpty()) return noItemToUpdate; if (playlists.isEmpty()) {
return noItemToUpdate;
}
final PlaylistRemoteEntity playlistEntity = playlists.get(0); final PlaylistRemoteEntity playlistRemoteEntity = playlists.get(0);
if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate; if (playlistRemoteEntity.isIdenticalTo(result)) {
return noItemToUpdate;
}
return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable(); return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable();
} }
@ -380,56 +404,59 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private Subscriber<List<PlaylistRemoteEntity>> getPlaylistBookmarkSubscriber() { private Subscriber<List<PlaylistRemoteEntity>> getPlaylistBookmarkSubscriber() {
return new Subscriber<List<PlaylistRemoteEntity>>() { return new Subscriber<List<PlaylistRemoteEntity>>() {
@Override @Override
public void onSubscribe(Subscription s) { public void onSubscribe(final Subscription s) {
if (bookmarkReactor != null) bookmarkReactor.cancel(); if (bookmarkReactor != null) {
bookmarkReactor.cancel();
}
bookmarkReactor = s; bookmarkReactor = s;
bookmarkReactor.request(1); bookmarkReactor.request(1);
} }
@Override @Override
public void onNext(List<PlaylistRemoteEntity> playlist) { public void onNext(final List<PlaylistRemoteEntity> playlist) {
playlistEntity = playlist.isEmpty() ? null : playlist.get(0); playlistEntity = playlist.isEmpty() ? null : playlist.get(0);
updateBookmarkButtons(); updateBookmarkButtons();
isBookmarkButtonReady.set(true); isBookmarkButtonReady.set(true);
if (bookmarkReactor != null) bookmarkReactor.request(1); if (bookmarkReactor != null) {
bookmarkReactor.request(1);
}
} }
@Override @Override
public void onError(Throwable t) { public void onError(final Throwable t) {
PlaylistFragment.this.onError(t); PlaylistFragment.this.onError(t);
} }
@Override @Override
public void onComplete() { public void onComplete() { }
}
}; };
} }
@Override @Override
public void setTitle(String title) { public void setTitle(final String title) {
super.setTitle(title); super.setTitle(title);
headerTitleView.setText(title); headerTitleView.setText(title);
} }
private void onBookmarkClicked() { private void onBookmarkClicked() {
if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get() || if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get()
remotePlaylistManager == null) || remotePlaylistManager == null) {
return; return;
}
final Disposable action; final Disposable action;
if (currentInfo != null && playlistEntity == null) { if (currentInfo != null && playlistEntity == null) {
action = remotePlaylistManager.onBookmark(currentInfo) action = remotePlaylistManager.onBookmark(currentInfo)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> {/* Do nothing */}, this::onError); .subscribe(ignored -> { /* Do nothing */ }, this::onError);
} else if (playlistEntity != null) { } else if (playlistEntity != null) {
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid()) action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> playlistEntity = null) .doFinally(() -> playlistEntity = null)
.subscribe(ignored -> {/* Do nothing */}, this::onError); .subscribe(ignored -> { /* Do nothing */ }, this::onError);
} else { } else {
action = Disposables.empty(); action = Disposables.empty();
} }
@ -438,13 +465,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
} }
private void updateBookmarkButtons() { private void updateBookmarkButtons() {
if (playlistBookmarkButton == null || activity == null) return; if (playlistBookmarkButton == null || activity == null) {
return;
}
final int iconAttr = playlistEntity == null ? final int iconAttr = playlistEntity == null
R.attr.ic_playlist_add : R.attr.ic_playlist_check; ? R.attr.ic_playlist_add : R.attr.ic_playlist_check;
final int titleRes = playlistEntity == null ? final int titleRes = playlistEntity == null
R.string.bookmark_playlist : R.string.unbookmark_playlist; ? R.string.bookmark_playlist : R.string.unbookmark_playlist;
playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr)); playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
playlistBookmarkButton.setTitle(titleRes); playlistBookmarkButton.setTitle(titleRes);

View File

@ -6,13 +6,6 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.TooltipCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -30,6 +23,14 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
@ -40,7 +41,6 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo; import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.util.FireTvUtils;
import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -49,6 +49,7 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.FireTvUtils;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
@ -77,8 +78,7 @@ import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovement
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SearchFragment public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
implements BackPressable { implements BackPressable {
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -92,20 +92,19 @@ public class SearchFragment
private static final int THRESHOLD_NETWORK_SUGGESTION = 1; private static final int THRESHOLD_NETWORK_SUGGESTION = 1;
/** /**
* How much time have to pass without emitting a item (i.e. the user stop typing) to fetch/show the suggestions, in milliseconds. * How much time have to pass without emitting a item (i.e. the user stop typing)
* to fetch/show the suggestions, in milliseconds.
*/ */
private static final int SUGGESTIONS_DEBOUNCE = 120; //ms private static final int SUGGESTIONS_DEBOUNCE = 120; //ms
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
private final CompositeDisposable disposables = new CompositeDisposable();
@State @State
protected int filterItemCheckedId = -1; protected int filterItemCheckedId = -1;
@State @State
protected int serviceId = Constants.NO_SERVICE_ID; protected int serviceId = Constants.NO_SERVICE_ID;
// this three represet the current search query // this three represet the current search query
@State @State
protected String searchString; protected String searchString;
/** /**
* No content filter should add like contentfilter = all * No content filter should add like contentfilter = all
* be aware of this when implementing an extractor. * be aware of this when implementing an extractor.
@ -114,26 +113,19 @@ public class SearchFragment
protected String[] contentFilter = new String[0]; protected String[] contentFilter = new String[0];
@State @State
protected String sortFilter; protected String sortFilter;
// these represtent the last search // these represtent the last search
@State @State
protected String lastSearchedString; protected String lastSearchedString;
@State @State
protected boolean wasSearchFocused = false; protected boolean wasSearchFocused = false;
private Map<Integer, String> menuItemToFilterName; private Map<Integer, String> menuItemToFilterName;
private StreamingService service; private StreamingService service;
private String currentPageUrl; private String currentPageUrl;
private String nextPageUrl; private String nextPageUrl;
private String contentCountry; private String contentCountry;
private boolean isSuggestionsEnabled = true; private boolean isSuggestionsEnabled = true;
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
private Disposable searchDisposable; private Disposable searchDisposable;
private Disposable suggestionDisposable; private Disposable suggestionDisposable;
private final CompositeDisposable disposables = new CompositeDisposable();
private SuggestionListAdapter suggestionListAdapter; private SuggestionListAdapter suggestionListAdapter;
private HistoryRecordManager historyRecordManager; private HistoryRecordManager historyRecordManager;
@ -149,8 +141,9 @@ public class SearchFragment
private RecyclerView suggestionsRecyclerView; private RecyclerView suggestionsRecyclerView;
/*////////////////////////////////////////////////////////////////////////*/ /*////////////////////////////////////////////////////////////////////////*/
private TextWatcher textWatcher;
public static SearchFragment getInstance(int serviceId, String searchString) { public static SearchFragment getInstance(final int serviceId, final String searchString) {
SearchFragment searchFragment = new SearchFragment(); SearchFragment searchFragment = new SearchFragment();
searchFragment.setQuery(serviceId, searchString, new String[0], ""); searchFragment.setQuery(serviceId, searchString, new String[0], "");
@ -161,6 +154,10 @@ public class SearchFragment
return searchFragment; return searchFragment;
} }
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
/** /**
* Set wasLoading to true so when the fragment onResume is called, the initial search is done. * Set wasLoading to true so when the fragment onResume is called, the initial search is done.
*/ */
@ -168,38 +165,38 @@ public class SearchFragment
wasLoading.set(true); wasLoading.set(true);
} }
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onAttach(Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
suggestionListAdapter = new SuggestionListAdapter(activity); suggestionListAdapter = new SuggestionListAdapter(activity);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true); boolean isSearchHistoryEnabled = preferences
.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled); suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
historyRecordManager = new HistoryRecordManager(context); historyRecordManager = new HistoryRecordManager(context);
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true); isSuggestionsEnabled = preferences
contentCountry = preferences.getString(getString(R.string.content_country_key), getString(R.string.default_localization_key)); .getBoolean(getString(R.string.show_search_suggestions_key), true);
contentCountry = preferences.getString(getString(R.string.content_country_key),
getString(R.string.default_localization_key));
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_search, container, false); return inflater.inflate(R.layout.fragment_search, container, false);
} }
@Override @Override
public void onViewCreated(View rootView, Bundle savedInstanceState) { public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState); super.onViewCreated(rootView, savedInstanceState);
showSearchOnStart(); showSearchOnStart();
initSearchListeners(); initSearchListeners();
@ -211,15 +208,23 @@ public class SearchFragment
wasSearchFocused = searchEditText.hasFocus(); wasSearchFocused = searchEditText.hasFocus();
if (searchDisposable != null) searchDisposable.dispose(); if (searchDisposable != null) {
if (suggestionDisposable != null) suggestionDisposable.dispose(); searchDisposable.dispose();
if (disposables != null) disposables.clear(); }
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
if (disposables != null) {
disposables.clear();
}
hideKeyboardSearch(); hideKeyboardSearch();
} }
@Override @Override
public void onResume() { public void onResume() {
if (DEBUG) Log.d(TAG, "onResume() called"); if (DEBUG) {
Log.d(TAG, "onResume() called");
}
super.onResume(); super.onResume();
try { try {
@ -245,7 +250,9 @@ public class SearchFragment
} }
} }
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver(); if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
initSuggestionObserver();
}
if (TextUtils.isEmpty(searchString) || wasSearchFocused) { if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
showKeyboardSearch(); showKeyboardSearch();
@ -259,7 +266,9 @@ public class SearchFragment
@Override @Override
public void onDestroyView() { public void onDestroyView() {
if (DEBUG) Log.d(TAG, "onDestroyView() called"); if (DEBUG) {
Log.d(TAG, "onDestroyView() called");
}
unsetSearchListeners(); unsetSearchListeners();
super.onDestroyView(); super.onDestroyView();
} }
@ -267,19 +276,31 @@ public class SearchFragment
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (searchDisposable != null) searchDisposable.dispose(); if (searchDisposable != null) {
if (suggestionDisposable != null) suggestionDisposable.dispose(); searchDisposable.dispose();
if (disposables != null) disposables.clear(); }
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
if (disposables != null) {
disposables.clear();
}
} }
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) { switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST: case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK if (resultCode == Activity.RESULT_OK
&& !TextUtils.isEmpty(searchString)) { && !TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter); search(searchString, contentFilter, sortFilter);
} else Log.e(TAG, "ReCaptcha failed"); } else {
Log.e(TAG, "ReCaptcha failed");
}
break; break;
default: default:
@ -289,29 +310,31 @@ public class SearchFragment
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Init // State Saving
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel); suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list); suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
suggestionsRecyclerView.setAdapter(suggestionListAdapter); suggestionsRecyclerView.setAdapter(suggestionListAdapter);
new ItemTouchHelper(new ItemTouchHelper.Callback() { new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override @Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { public int getMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
return getSuggestionMovementFlags(recyclerView, viewHolder); return getSuggestionMovementFlags(recyclerView, viewHolder);
} }
@Override @Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, public boolean onMove(@NonNull final RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder1) { @NonNull final RecyclerView.ViewHolder viewHolder,
@NonNull final RecyclerView.ViewHolder viewHolder1) {
return false; return false;
} }
@Override @Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int i) {
onSuggestionItemSwiped(viewHolder, i); onSuggestionItemSwiped(viewHolder, i);
} }
}).attachToRecyclerView(suggestionsRecyclerView); }).attachToRecyclerView(suggestionsRecyclerView);
@ -321,26 +344,26 @@ public class SearchFragment
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear); searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
} }
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void writeTo(Queue<Object> objectsToSave) { public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave); super.writeTo(objectsToSave);
objectsToSave.add(currentPageUrl); objectsToSave.add(currentPageUrl);
objectsToSave.add(nextPageUrl); objectsToSave.add(nextPageUrl);
} }
@Override @Override
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception { public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects); super.readFrom(savedObjects);
currentPageUrl = (String) savedObjects.poll(); currentPageUrl = (String) savedObjects.poll();
nextPageUrl = (String) savedObjects.poll(); nextPageUrl = (String) savedObjects.poll();
} }
/*//////////////////////////////////////////////////////////////////////////
// Init's
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onSaveInstanceState(Bundle bundle) { public void onSaveInstanceState(final Bundle bundle) {
searchString = searchEditText != null searchString = searchEditText != null
? searchEditText.getText().toString() ? searchEditText.getText().toString()
: searchString; : searchString;
@ -348,7 +371,7 @@ public class SearchFragment
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Init's // Menu
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
@ -367,12 +390,8 @@ public class SearchFragment
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar(); ActionBar supportActionBar = activity.getSupportActionBar();
@ -386,13 +405,13 @@ public class SearchFragment
int itemId = 0; int itemId = 0;
boolean isFirstItem = true; boolean isFirstItem = true;
final Context c = getContext(); final Context c = getContext();
for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) { for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
menuItemToFilterName.put(itemId, filter); menuItemToFilterName.put(itemId, filter);
MenuItem item = menu.add(1, MenuItem item = menu.add(1,
itemId++, itemId++,
0, 0,
ServiceHelper.getTranslatedFilterString(filter, c)); ServiceHelper.getTranslatedFilterString(filter, c));
if(isFirstItem) { if (isFirstItem) {
item.setChecked(true); item.setChecked(true);
isFirstItem = false; isFirstItem = false;
} }
@ -403,35 +422,36 @@ public class SearchFragment
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
List<String> cf = new ArrayList<>(1);
List<String> contentFilter = new ArrayList<>(1); cf.add(menuItemToFilterName.get(item.getItemId()));
contentFilter.add(menuItemToFilterName.get(item.getItemId())); changeContentFilter(item, cf);
changeContentFilter(item, contentFilter);
return true; return true;
} }
private void restoreFilterChecked(Menu menu, int itemId) {
if (itemId != -1) {
MenuItem item = menu.findItem(itemId);
if (item == null) return;
item.setChecked(true);
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Search // Search
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private TextWatcher textWatcher; private void restoreFilterChecked(final Menu menu, final int itemId) {
if (itemId != -1) {
MenuItem item = menu.findItem(itemId);
if (item == null) {
return;
}
item.setChecked(true);
}
}
private void showSearchOnStart() { private void showSearchOnStart() {
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → " if (DEBUG) {
+ searchString Log.d(TAG, "showSearchOnStart() called, searchQuery → "
+ ", lastSearchedQuery → " + searchString
+ lastSearchedString); + ", lastSearchedQuery → "
+ lastSearchedString);
}
searchEditText.setText(searchString); searchEditText.setText(searchString);
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) { if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
@ -451,9 +471,13 @@ public class SearchFragment
} }
private void initSearchListeners() { private void initSearchListeners() {
if (DEBUG) Log.d(TAG, "initSearchListeners() called"); if (DEBUG) {
Log.d(TAG, "initSearchListeners() called");
}
searchClear.setOnClickListener(v -> { searchClear.setOnClickListener(v -> {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (TextUtils.isEmpty(searchEditText.getText())) { if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.gotoMainFragment(getFragmentManager()); NavigationHelper.gotoMainFragment(getFragmentManager());
return; return;
@ -467,53 +491,63 @@ public class SearchFragment
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear)); TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
searchEditText.setOnClickListener(v -> { searchEditText.setOnClickListener(v -> {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) { if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel(); showSuggestionsPanel();
} }
if(FireTvUtils.isFireTv()){ if (FireTvUtils.isFireTv()) {
showKeyboardSearch(); showKeyboardSearch();
} }
}); });
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]"); if (DEBUG) {
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) { Log.d(TAG, "onFocusChange() called with: "
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
}
if (isSuggestionsEnabled && hasFocus
&& errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel(); showSuggestionsPanel();
} }
}); });
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() { suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
@Override @Override
public void onSuggestionItemSelected(SuggestionItem item) { public void onSuggestionItemSelected(final SuggestionItem item) {
search(item.query, new String[0], ""); search(item.query, new String[0], "");
searchEditText.setText(item.query); searchEditText.setText(item.query);
} }
@Override @Override
public void onSuggestionItemInserted(SuggestionItem item) { public void onSuggestionItemInserted(final SuggestionItem item) {
searchEditText.setText(item.query); searchEditText.setText(item.query);
searchEditText.setSelection(searchEditText.getText().length()); searchEditText.setSelection(searchEditText.getText().length());
} }
@Override @Override
public void onSuggestionItemLongClick(SuggestionItem item) { public void onSuggestionItemLongClick(final SuggestionItem item) {
if (item.fromHistory) showDeleteSuggestionDialog(item); if (item.fromHistory) {
showDeleteSuggestionDialog(item);
}
} }
}); });
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); if (textWatcher != null) {
searchEditText.removeTextChangedListener(textWatcher);
}
textWatcher = new TextWatcher() { textWatcher = new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(final CharSequence s, final int start,
} final int count, final int after) { }
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(final CharSequence s, final int start,
} final int before, final int count) { }
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(final Editable s) {
String newText = searchEditText.getText().toString(); String newText = searchEditText.getText().toString();
suggestionPublisher.onNext(newText); suggestionPublisher.onNext(newText);
} }
@ -522,48 +556,62 @@ public class SearchFragment
searchEditText.setOnEditorActionListener( searchEditText.setOnEditorActionListener(
(TextView v, int actionId, KeyEvent event) -> { (TextView v, int actionId, KeyEvent event) -> {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]"); Log.d(TAG, "onEditorAction() called with: v = [" + v + "], "
+ "actionId = [" + actionId + "], event = [" + event + "]");
} }
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){ if (actionId == EditorInfo.IME_ACTION_PREVIOUS) {
hideKeyboardSearch(); hideKeyboardSearch();
} else if (event != null } else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) { || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString(), new String[0], ""); search(searchEditText.getText().toString(), new String[0], "");
return true; return true;
} }
return false; return false;
}); });
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
initSuggestionObserver(); initSuggestionObserver();
}
} }
private void unsetSearchListeners() { private void unsetSearchListeners() {
if (DEBUG) Log.d(TAG, "unsetSearchListeners() called"); if (DEBUG) {
Log.d(TAG, "unsetSearchListeners() called");
}
searchClear.setOnClickListener(null); searchClear.setOnClickListener(null);
searchClear.setOnLongClickListener(null); searchClear.setOnLongClickListener(null);
searchEditText.setOnClickListener(null); searchEditText.setOnClickListener(null);
searchEditText.setOnFocusChangeListener(null); searchEditText.setOnFocusChangeListener(null);
searchEditText.setOnEditorActionListener(null); searchEditText.setOnEditorActionListener(null);
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); if (textWatcher != null) {
searchEditText.removeTextChangedListener(textWatcher);
}
textWatcher = null; textWatcher = null;
} }
private void showSuggestionsPanel() { private void showSuggestionsPanel() {
if (DEBUG) Log.d(TAG, "showSuggestionsPanel() called"); if (DEBUG) {
Log.d(TAG, "showSuggestionsPanel() called");
}
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200); animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
} }
private void hideSuggestionsPanel() { private void hideSuggestionsPanel() {
if (DEBUG) Log.d(TAG, "hideSuggestionsPanel() called"); if (DEBUG) {
Log.d(TAG, "hideSuggestionsPanel() called");
}
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200); animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
} }
private void showKeyboardSearch() { private void showKeyboardSearch() {
if (DEBUG) Log.d(TAG, "showKeyboardSearch() called"); if (DEBUG) {
if (searchEditText == null) return; Log.d(TAG, "showKeyboardSearch() called");
}
if (searchEditText == null) {
return;
}
if (searchEditText.requestFocus()) { if (searchEditText.requestFocus()) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService( InputMethodManager imm = (InputMethodManager) activity.getSystemService(
@ -573,19 +621,26 @@ public class SearchFragment
} }
private void hideKeyboardSearch() { private void hideKeyboardSearch() {
if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called"); if (DEBUG) {
if (searchEditText == null) return; Log.d(TAG, "hideKeyboardSearch() called");
}
if (searchEditText == null) {
return;
}
InputMethodManager imm = (InputMethodManager) activity.getSystemService( InputMethodManager imm = (InputMethodManager) activity
Context.INPUT_METHOD_SERVICE); .getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
InputMethodManager.RESULT_UNCHANGED_SHOWN);
searchEditText.clearFocus(); searchEditText.clearFocus();
} }
private void showDeleteSuggestionDialog(final SuggestionItem item) { private void showDeleteSuggestionDialog(final SuggestionItem item) {
if (activity == null || historyRecordManager == null || suggestionPublisher == null || if (activity == null || historyRecordManager == null || suggestionPublisher == null
searchEditText == null || disposables == null) return; || searchEditText == null || disposables == null) {
return;
}
final String query = item.query; final String query = item.query;
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setTitle(query) .setTitle(query)
@ -624,15 +679,19 @@ public class SearchFragment
} }
private void initSuggestionObserver() { private void initSuggestionObserver() {
if (DEBUG) Log.d(TAG, "initSuggestionObserver() called"); if (DEBUG) {
if (suggestionDisposable != null) suggestionDisposable.dispose(); Log.d(TAG, "initSuggestionObserver() called");
}
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
final Observable<String> observable = suggestionPublisher final Observable<String> observable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWith(searchString != null .startWith(searchString != null
? searchString ? searchString
: "") : "")
.filter(searchString -> isSuggestionsEnabled); .filter(ss -> isSuggestionsEnabled);
suggestionDisposable = observable suggestionDisposable = observable
.switchMap(query -> { .switchMap(query -> {
@ -641,13 +700,15 @@ public class SearchFragment
final Observable<List<SuggestionItem>> local = flowable.toObservable() final Observable<List<SuggestionItem>> local = flowable.toObservable()
.map(searchHistoryEntries -> { .map(searchHistoryEntries -> {
List<SuggestionItem> result = new ArrayList<>(); List<SuggestionItem> result = new ArrayList<>();
for (SearchHistoryEntry entry : searchHistoryEntries) for (SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch())); result.add(new SuggestionItem(true, entry.getSearch()));
}
return result; return result;
}); });
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) { if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION // Only pass through if the query length
// is equal or greater than THRESHOLD_NETWORK_SUGGESTION
return local.materialize(); return local.materialize();
} }
@ -664,7 +725,9 @@ public class SearchFragment
return Observable.zip(local, network, (localResult, networkResult) -> { return Observable.zip(local, network, (localResult, networkResult) -> {
List<SuggestionItem> result = new ArrayList<>(); List<SuggestionItem> result = new ArrayList<>();
if (localResult.size() > 0) result.addAll(localResult); if (localResult.size() > 0) {
result.addAll(localResult);
}
// Remove duplicates // Remove duplicates
final Iterator<SuggestionItem> iterator = networkResult.iterator(); final Iterator<SuggestionItem> iterator = networkResult.iterator();
@ -678,7 +741,9 @@ public class SearchFragment
} }
} }
if (networkResult.size() > 0) result.addAll(networkResult); if (networkResult.size() > 0) {
result.addAll(networkResult);
}
return result; return result;
}).materialize(); }).materialize();
}) })
@ -703,17 +768,21 @@ public class SearchFragment
// no-op // no-op
} }
private void search(final String searchString, String[] contentFilter, String sortFilter) { private void search(final String ss, final String[] cf, final String sf) {
if (DEBUG) Log.d(TAG, "search() called with: query = [" + searchString + "]"); if (DEBUG) {
if (searchString.isEmpty()) return; Log.d(TAG, "search() called with: query = [" + ss + "]");
}
if (ss.isEmpty()) {
return;
}
try { try {
final StreamingService service = NewPipe.getServiceByUrl(searchString); final StreamingService streamingService = NewPipe.getServiceByUrl(ss);
if (service != null) { if (streamingService != null) {
showLoading(); showLoading();
disposables.add(Observable disposables.add(Observable
.fromCallable(() -> .fromCallable(() ->
NavigationHelper.getIntentByLink(activity, service, searchString)) NavigationHelper.getIntentByLink(activity, streamingService, ss))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> { .subscribe(intent -> {
@ -728,31 +797,36 @@ public class SearchFragment
} }
lastSearchedString = this.searchString; lastSearchedString = this.searchString;
this.searchString = searchString; this.searchString = ss;
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
hideSuggestionsPanel(); hideSuggestionsPanel();
hideKeyboardSearch(); hideKeyboardSearch();
historyRecordManager.onSearched(serviceId, searchString) historyRecordManager.onSearched(serviceId, ss)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
ignored -> {}, ignored -> {
},
error -> showSnackBarError(error, UserAction.SEARCHED, error -> showSnackBarError(error, UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), searchString, 0) NewPipe.getNameOfService(serviceId), ss, 0)
); );
suggestionPublisher.onNext(searchString); suggestionPublisher.onNext(ss);
startLoading(false); startLoading(false);
} }
@Override @Override
public void startLoading(boolean forceLoad) { public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad); super.startLoading(forceLoad);
if (disposables != null) disposables.clear(); if (disposables != null) {
if (searchDisposable != null) searchDisposable.dispose(); disposables.clear();
}
if (searchDisposable != null) {
searchDisposable.dispose();
}
searchDisposable = ExtractorHelper.searchFor(serviceId, searchDisposable = ExtractorHelper.searchFor(serviceId,
searchString, searchString,
Arrays.asList(contentFilter), Arrays.asList(contentFilter),
sortFilter) sortFilter)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnEvent((searchResult, throwable) -> isLoading.set(false)) .doOnEvent((searchResult, throwable) -> isLoading.set(false))
@ -762,16 +836,20 @@ public class SearchFragment
@Override @Override
protected void loadMoreItems() { protected void loadMoreItems() {
if(nextPageUrl == null || nextPageUrl.isEmpty()) return; if (nextPageUrl == null || nextPageUrl.isEmpty()) {
return;
}
isLoading.set(true); isLoading.set(true);
showListFooter(true); showListFooter(true);
if (searchDisposable != null) searchDisposable.dispose(); if (searchDisposable != null) {
searchDisposable.dispose();
}
searchDisposable = ExtractorHelper.getMoreSearchItems( searchDisposable = ExtractorHelper.getMoreSearchItems(
serviceId, serviceId,
searchString, searchString,
asList(contentFilter), asList(contentFilter),
sortFilter, sortFilter,
nextPageUrl) nextPageUrl)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false)) .doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
@ -785,7 +863,7 @@ public class SearchFragment
} }
@Override @Override
protected void onItemSelected(InfoItem selectedItem) { protected void onItemSelected(final InfoItem selectedItem) {
super.onItemSelected(selectedItem); super.onItemSelected(selectedItem);
hideKeyboardSearch(); hideKeyboardSearch();
} }
@ -794,22 +872,22 @@ public class SearchFragment
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void changeContentFilter(MenuItem item, List<String> contentFilter) { private void changeContentFilter(final MenuItem item, final List<String> cf) {
this.filterItemCheckedId = item.getItemId(); this.filterItemCheckedId = item.getItemId();
item.setChecked(true); item.setChecked(true);
this.contentFilter = new String[] {contentFilter.get(0)}; this.contentFilter = new String[]{cf.get(0)};
if (!TextUtils.isEmpty(searchString)) { if (!TextUtils.isEmpty(searchString)) {
search(searchString, this.contentFilter, sortFilter); search(searchString, this.contentFilter, sortFilter);
} }
} }
private void setQuery(int serviceId, String searchString, String[] contentfilter, String sortFilter) { private void setQuery(final int sid, final String ss, final String[] cf, final String sf) {
this.serviceId = serviceId; this.serviceId = sid;
this.searchString = searchString; this.searchString = searchString;
this.contentFilter = contentfilter; this.contentFilter = cf;
this.sortFilter = sortFilter; this.sortFilter = sf;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -817,7 +895,9 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) { public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) {
if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); if (DEBUG) {
Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]");
}
suggestionsRecyclerView.smoothScrollToPosition(0); suggestionsRecyclerView.smoothScrollToPosition(0);
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions)); suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
@ -826,9 +906,13 @@ public class SearchFragment
} }
} }
public void onSuggestionError(Throwable exception) { public void onSuggestionError(final Throwable exception) {
if (DEBUG) Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]"); if (DEBUG) {
if (super.onError(exception)) return; Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
}
if (super.onError(exception)) {
return;
}
int errorId = exception instanceof ParsingException int errorId = exception instanceof ParsingException
? R.string.parsing_error ? R.string.parsing_error
@ -848,7 +932,7 @@ public class SearchFragment
} }
@Override @Override
public void showError(String message, boolean showRetryButton) { public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton); super.showError(message, showRetryButton);
hideSuggestionsPanel(); hideSuggestionsPanel();
hideKeyboardSearch(); hideKeyboardSearch();
@ -859,11 +943,11 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void handleResult(@NonNull SearchInfo result) { public void handleResult(@NonNull final SearchInfo result) {
final List<Throwable> exceptions = result.getErrors(); final List<Throwable> exceptions = result.getErrors();
if (!exceptions.isEmpty() if (!exceptions.isEmpty()
&& !(exceptions.size() == 1 && !(exceptions.size() == 1
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){ && exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED, showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), searchString, 0); NewPipe.getNameOfService(serviceId), searchString, 0);
} }
@ -886,7 +970,7 @@ public class SearchFragment
} }
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
showListFooter(false); showListFooter(false);
currentPageUrl = result.getNextPageUrl(); currentPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems()); infoListAdapter.addInfoItemList(result.getItems());
@ -894,15 +978,17 @@ public class SearchFragment
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED, showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId) NewPipe.getNameOfService(serviceId),
, "\"" + searchString + "\" → page: " + nextPageUrl, 0); "\"" + searchString + "\" → page: " + nextPageUrl, 0);
} }
super.handleNextItems(result); super.handleNextItems(result);
} }
@Override @Override
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
if (super.onError(exception)) return true; if (super.onError(exception)) {
return true;
}
if (exception instanceof SearchExtractor.NothingFoundException) { if (exception instanceof SearchExtractor.NothingFoundException) {
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
@ -922,13 +1008,16 @@ public class SearchFragment
// Suggestion item touch helper // Suggestion item touch helper
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition(); final int position = viewHolder.getAdapterPosition();
final SuggestionItem item = suggestionListAdapter.getItem(position); final SuggestionItem item = suggestionListAdapter.getItem(position);
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0; return item.fromHistory ? makeMovementFlags(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
} }
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int i) {
final int position = viewHolder.getAdapterPosition(); final int position = viewHolder.getAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query; final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query) final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)

View File

@ -1,10 +1,10 @@
package org.schabi.newpipe.fragments.list.search; package org.schabi.newpipe.fragments.list.search;
public class SuggestionItem { public class SuggestionItem {
public final boolean fromHistory; final boolean fromHistory;
public final String query; public final String query;
public SuggestionItem(boolean fromHistory, String query) { public SuggestionItem(final boolean fromHistory, final String query) {
this.fromHistory = fromHistory; this.fromHistory = fromHistory;
this.query = query; this.query = query;
} }

View File

@ -2,36 +2,32 @@ package org.schabi.newpipe.fragments.list.search;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import androidx.annotation.AttrRes;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> { public class SuggestionListAdapter
extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> {
private final ArrayList<SuggestionItem> items = new ArrayList<>(); private final ArrayList<SuggestionItem> items = new ArrayList<>();
private final Context context; private final Context context;
private OnSuggestionItemSelected listener; private OnSuggestionItemSelected listener;
private boolean showSuggestionHistory = true; private boolean showSuggestionHistory = true;
public interface OnSuggestionItemSelected { public SuggestionListAdapter(final Context context) {
void onSuggestionItemSelected(SuggestionItem item);
void onSuggestionItemInserted(SuggestionItem item);
void onSuggestionItemLongClick(SuggestionItem item);
}
public SuggestionListAdapter(Context context) {
this.context = context; this.context = context;
} }
public void setItems(List<SuggestionItem> items) { public void setItems(final List<SuggestionItem> items) {
this.items.clear(); this.items.clear();
if (showSuggestionHistory) { if (showSuggestionHistory) {
this.items.addAll(items); this.items.addAll(items);
@ -46,36 +42,43 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setListener(OnSuggestionItemSelected listener) { public void setListener(final OnSuggestionItemSelected listener) {
this.listener = listener; this.listener = listener;
} }
public void setShowSuggestionHistory(boolean v) { public void setShowSuggestionHistory(final boolean v) {
showSuggestionHistory = v; showSuggestionHistory = v;
} }
@Override @Override
public SuggestionItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { public SuggestionItemHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new SuggestionItemHolder(LayoutInflater.from(context).inflate(R.layout.item_search_suggestion, parent, false)); return new SuggestionItemHolder(LayoutInflater.from(context)
.inflate(R.layout.item_search_suggestion, parent, false));
} }
@Override @Override
public void onBindViewHolder(SuggestionItemHolder holder, int position) { public void onBindViewHolder(final SuggestionItemHolder holder, final int position) {
final SuggestionItem currentItem = getItem(position); final SuggestionItem currentItem = getItem(position);
holder.updateFrom(currentItem); holder.updateFrom(currentItem);
holder.queryView.setOnClickListener(v -> { holder.queryView.setOnClickListener(v -> {
if (listener != null) listener.onSuggestionItemSelected(currentItem); if (listener != null) {
listener.onSuggestionItemSelected(currentItem);
}
}); });
holder.queryView.setOnLongClickListener(v -> { holder.queryView.setOnLongClickListener(v -> {
if (listener != null) listener.onSuggestionItemLongClick(currentItem); if (listener != null) {
return true; listener.onSuggestionItemLongClick(currentItem);
}
return true;
}); });
holder.insertView.setOnClickListener(v -> { holder.insertView.setOnClickListener(v -> {
if (listener != null) listener.onSuggestionItemInserted(currentItem); if (listener != null) {
listener.onSuggestionItemInserted(currentItem);
}
}); });
} }
SuggestionItem getItem(int position) { SuggestionItem getItem(final int position) {
return items.get(position); return items.get(position);
} }
@ -88,7 +91,15 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
return getItemCount() == 0; return getItemCount() == 0;
} }
public static class SuggestionItemHolder extends RecyclerView.ViewHolder { public interface OnSuggestionItemSelected {
void onSuggestionItemSelected(SuggestionItem item);
void onSuggestionItemInserted(SuggestionItem item);
void onSuggestionItemLongClick(SuggestionItem item);
}
public static final class SuggestionItemHolder extends RecyclerView.ViewHolder {
private final TextView itemSuggestionQuery; private final TextView itemSuggestionQuery;
private final ImageView suggestionIcon; private final ImageView suggestionIcon;
private final View queryView; private final View queryView;
@ -98,7 +109,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
private final int historyResId; private final int historyResId;
private final int searchResId; private final int searchResId;
private SuggestionItemHolder(View rootView) { private SuggestionItemHolder(final View rootView) {
super(rootView); super(rootView);
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon); suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query); itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query);
@ -110,16 +121,17 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search); searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
} }
private void updateFrom(SuggestionItem item) { private static int resolveResourceIdFromAttr(final Context context,
suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); @AttrRes final int attr) {
itemSuggestionQuery.setText(item.query);
}
private static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) {
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
int attributeResourceId = a.getResourceId(0, 0); int attributeResourceId = a.getResourceId(0, 0);
a.recycle(); a.recycle();
return attributeResourceId; return attributeResourceId;
} }
private void updateFrom(final SuggestionItem item) {
suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId);
itemSuggestionQuery.setText(item.query);
}
} }
} }

View File

@ -4,8 +4,6 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -14,6 +12,9 @@ import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.Switch; import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
@ -28,8 +29,9 @@ import java.io.Serializable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String INFO_KEY = "related_info_key";
private CompositeDisposable disposables = new CompositeDisposable(); private CompositeDisposable disposables = new CompositeDisposable();
private RelatedStreamInfo relatedStreamInfo; private RelatedStreamInfo relatedStreamInfo;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -37,44 +39,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout; private View headerRootLayout;
private Switch aSwitch; private Switch aSwitch;
private boolean mIsVisibleToUser = false; private boolean mIsVisibleToUser = false;
public static RelatedVideosFragment getInstance(StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// LifeCycle // LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static RelatedVideosFragment getInstance(final StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser; mIsVisibleToUser = isVisibleToUser;
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_related_streams, container, false); return inflater.inflate(R.layout.fragment_related_streams, container, false);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (disposables != null) disposables.clear(); if (disposables != null) {
disposables.clear();
}
} }
protected View getListHeader(){ protected View getListHeader() {
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){ if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false); headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.related_streams_header, itemsList, false);
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch); aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
@ -82,14 +88,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
aSwitch.setChecked(autoplay); aSwitch.setChecked(autoplay);
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) { public void onCheckedChanged(final CompoundButton compoundButton,
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); final boolean b) {
prefEdit.putBoolean(getString(R.string.auto_queue_key), b); PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
prefEdit.apply(); .putBoolean(getString(R.string.auto_queue_key), b).apply();
} }
}); });
return headerRootLayout; return headerRootLayout;
}else{ } else {
return null; return null;
} }
} }
@ -99,38 +105,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage()); return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
} }
@Override
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Contract // Contract
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<RelatedStreamInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
}
@Override @Override
public void showLoading() { public void showLoading() {
super.showLoading(); super.showLoading();
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE); if (headerRootLayout != null) {
headerRootLayout.setVisibility(View.INVISIBLE);
}
} }
@Override @Override
public void handleResult(@NonNull RelatedStreamInfo result) { public void handleResult(@NonNull final RelatedStreamInfo result) {
super.handleResult(result); super.handleResult(result);
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE); if (headerRootLayout != null) {
AnimationUtils.slideUp(getView(),120, 96, 0.06f); headerRootLayout.setVisibility(View.VISIBLE);
}
AnimationUtils.slideUp(getView(), 120, 96, 0.06f);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
} }
if (disposables != null) disposables.clear(); if (disposables != null) {
disposables.clear();
}
} }
/*//////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void handleNextItems(ListExtractor.InfoItemsPage result) { public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result); super.handleNextItems(result);
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
@ -142,63 +158,64 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
return true;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void setTitle(String title) { protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
hideLoading();
showSnackBarError(exception, UserAction.REQUESTED_STREAM,
NewPipe.getNameOfService(serviceId), url, R.string.general_error);
return true;
}
@Override
public void setTitle(final String title) {
return; return;
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
return; return;
} }
private void setInitialData(StreamInfo info) { private void setInitialData(final StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName()); super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info); if (this.relatedStreamInfo == null) {
this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
}
} }
private static final String INFO_KEY = "related_info_key";
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedStreamInfo); outState.putSerializable(INFO_KEY, relatedStreamInfo);
} }
@Override @Override
protected void onRestoreInstanceState(@NonNull Bundle savedState) { protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState); super.onRestoreInstanceState(savedState);
if (savedState != null) { if (savedState != null) {
Serializable serializable = savedState.getSerializable(INFO_KEY); Serializable serializable = savedState.getSerializable(INFO_KEY);
if(serializable instanceof RelatedStreamInfo){ if (serializable instanceof RelatedStreamInfo) {
this.relatedStreamInfo = (RelatedStreamInfo) serializable; this.relatedStreamInfo = (RelatedStreamInfo) serializable;
} }
} }
} }
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String s) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false); boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if(null != aSwitch) aSwitch.setChecked(autoplay); if (null != aSwitch) {
aSwitch.setChecked(autoplay);
}
} }
@Override @Override

View File

@ -1,10 +1,11 @@
package org.schabi.newpipe.info_list; package org.schabi.newpipe.info_list;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
@ -29,24 +30,26 @@ import org.schabi.newpipe.util.OnClickGesture;
* <p> * <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* InfoItemBuilder.java is part of NewPipe. * InfoItemBuilder.java is part of NewPipe.
* </p>
* <p> * <p>
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* </p>
* <p> * <p>
* NewPipe is distributed in the hope that it will be useful, * NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* </p>
* <p> * <p>
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
* </p>
*/ */
public class InfoItemBuilder { public class InfoItemBuilder {
private static final String TAG = InfoItemBuilder.class.toString();
private final Context context; private final Context context;
private final ImageLoader imageLoader = ImageLoader.getInstance(); private final ImageLoader imageLoader = ImageLoader.getInstance();
@ -55,31 +58,39 @@ public class InfoItemBuilder {
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener; private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener; private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
public InfoItemBuilder(Context context) { public InfoItemBuilder(final Context context) {
this.context = context; this.context = context;
} }
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
return buildView(parent, infoItem, historyRecordManager, false); return buildView(parent, infoItem, historyRecordManager, false);
} }
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
final HistoryRecordManager historyRecordManager, boolean useMiniVariant) { final HistoryRecordManager historyRecordManager,
final boolean useMiniVariant) {
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
holder.updateFromItem(infoItem, historyRecordManager); holder.updateFromItem(infoItem, historyRecordManager);
return holder.itemView; return holder.itemView;
} }
private InfoItemHolder holderFromInfoType(@NonNull ViewGroup parent, @NonNull InfoItem.InfoType infoType, boolean useMiniVariant) { private InfoItemHolder holderFromInfoType(@NonNull final ViewGroup parent,
@NonNull final InfoItem.InfoType infoType,
final boolean useMiniVariant) {
switch (infoType) { switch (infoType) {
case STREAM: case STREAM:
return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent) : new StreamInfoItemHolder(this, parent); return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent)
: new StreamInfoItemHolder(this, parent);
case CHANNEL: case CHANNEL:
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent); return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent)
: new ChannelInfoItemHolder(this, parent);
case PLAYLIST: case PLAYLIST:
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent); return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent)
: new PlaylistInfoItemHolder(this, parent);
case COMMENT: case COMMENT:
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent); return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent)
: new CommentsInfoItemHolder(this, parent);
default: default:
throw new RuntimeException("InfoType not expected = " + infoType.name()); throw new RuntimeException("InfoType not expected = " + infoType.name());
} }
@ -97,7 +108,7 @@ public class InfoItemBuilder {
return onStreamSelectedListener; return onStreamSelectedListener;
} }
public void setOnStreamSelectedListener(OnClickGesture<StreamInfoItem> listener) { public void setOnStreamSelectedListener(final OnClickGesture<StreamInfoItem> listener) {
this.onStreamSelectedListener = listener; this.onStreamSelectedListener = listener;
} }
@ -105,7 +116,7 @@ public class InfoItemBuilder {
return onChannelSelectedListener; return onChannelSelectedListener;
} }
public void setOnChannelSelectedListener(OnClickGesture<ChannelInfoItem> listener) { public void setOnChannelSelectedListener(final OnClickGesture<ChannelInfoItem> listener) {
this.onChannelSelectedListener = listener; this.onChannelSelectedListener = listener;
} }
@ -113,7 +124,7 @@ public class InfoItemBuilder {
return onPlaylistSelectedListener; return onPlaylistSelectedListener;
} }
public void setOnPlaylistSelectedListener(OnClickGesture<PlaylistInfoItem> listener) { public void setOnPlaylistSelectedListener(final OnClickGesture<PlaylistInfoItem> listener) {
this.onPlaylistSelectedListener = listener; this.onPlaylistSelectedListener = listener;
} }
@ -121,8 +132,8 @@ public class InfoItemBuilder {
return onCommentsSelectedListener; return onCommentsSelectedListener;
} }
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) { public void setOnCommentsSelectedListener(
final OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
this.onCommentsSelectedListener = onCommentsSelectedListener; this.onCommentsSelectedListener = onCommentsSelectedListener;
} }
} }

View File

@ -3,11 +3,12 @@ package org.schabi.newpipe.info_list;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;

View File

@ -1,13 +1,14 @@
package org.schabi.newpipe.info_list; package org.schabi.newpipe.info_list;
import android.content.Context; import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
@ -83,42 +84,33 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private View header = null; private View header = null;
private View footer = null; private View footer = null;
public class HFHolder extends RecyclerView.ViewHolder { public InfoListAdapter(final Context context) {
public View view;
public HFHolder(View v) {
super(v);
view = v;
}
}
public InfoListAdapter(Context context) {
this.recordManager = new HistoryRecordManager(context); this.recordManager = new HistoryRecordManager(context);
infoItemBuilder = new InfoItemBuilder(context); infoItemBuilder = new InfoItemBuilder(context);
infoItemList = new ArrayList<>(); infoItemList = new ArrayList<>();
} }
public void setOnStreamSelectedListener(OnClickGesture<StreamInfoItem> listener) { public void setOnStreamSelectedListener(final OnClickGesture<StreamInfoItem> listener) {
infoItemBuilder.setOnStreamSelectedListener(listener); infoItemBuilder.setOnStreamSelectedListener(listener);
} }
public void setOnChannelSelectedListener(OnClickGesture<ChannelInfoItem> listener) { public void setOnChannelSelectedListener(final OnClickGesture<ChannelInfoItem> listener) {
infoItemBuilder.setOnChannelSelectedListener(listener); infoItemBuilder.setOnChannelSelectedListener(listener);
} }
public void setOnPlaylistSelectedListener(OnClickGesture<PlaylistInfoItem> listener) { public void setOnPlaylistSelectedListener(final OnClickGesture<PlaylistInfoItem> listener) {
infoItemBuilder.setOnPlaylistSelectedListener(listener); infoItemBuilder.setOnPlaylistSelectedListener(listener);
} }
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) { public void setOnCommentsSelectedListener(final OnClickGesture<CommentsInfoItem> listener) {
infoItemBuilder.setOnCommentsSelectedListener(listener); infoItemBuilder.setOnCommentsSelectedListener(listener);
} }
public void useMiniItemVariants(boolean useMiniVariant) { public void setUseMiniVariant(final boolean useMiniVariant) {
this.useMiniVariant = useMiniVariant; this.useMiniVariant = useMiniVariant;
} }
public void setGridItemVariants(boolean useGridVariant) { public void setUseGridVariant(final boolean useGridVariant) {
this.useGridVariant = useGridVariant; this.useGridVariant = useGridVariant;
} }
@ -126,55 +118,67 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
if (data == null) { if (data == null) {
return; return;
} }
if (DEBUG) Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + if (DEBUG) {
infoItemList.size() + ", data.size() = " + data.size()); Log.d(TAG, "addInfoItemList() before > infoItemList.size() = "
+ infoItemList.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeaderOffset(); int offsetStart = sizeConsideringHeaderOffset();
infoItemList.addAll(data); infoItemList.addAll(data);
if (DEBUG) Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + if (DEBUG) {
", infoItemList.size() = " + infoItemList.size() + Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", "
", header = " + header + ", footer = " + footer + + "infoItemList.size() = " + infoItemList.size() + ", "
", showFooter = " + showFooter); + "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
notifyItemRangeInserted(offsetStart, data.size()); notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) { if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset(); int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(offsetStart, footerNow); notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + if (DEBUG) {
" to " + footerNow); Log.d(TAG, "addInfoItemList() footer from " + offsetStart
+ " to " + footerNow);
}
} }
} }
public void setInfoItemList(List<? extends InfoItem> data) { public void setInfoItemList(final List<? extends InfoItem> data) {
infoItemList.clear(); infoItemList.clear();
infoItemList.addAll(data); infoItemList.addAll(data);
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void addInfoItem(@Nullable InfoItem data) { public void addInfoItem(@Nullable final InfoItem data) {
if (data == null) { if (data == null) {
return; return;
} }
if (DEBUG) Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + if (DEBUG) {
infoItemList.size() + ", thread = " + Thread.currentThread()); Log.d(TAG, "addInfoItem() before > infoItemList.size() = "
+ infoItemList.size() + ", thread = " + Thread.currentThread());
}
int positionInserted = sizeConsideringHeaderOffset(); int positionInserted = sizeConsideringHeaderOffset();
infoItemList.add(data); infoItemList.add(data);
if (DEBUG) Log.d(TAG, "addInfoItem() after > position = " + positionInserted + if (DEBUG) {
", infoItemList.size() = " + infoItemList.size() + Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", "
", header = " + header + ", footer = " + footer + + "infoItemList.size() = " + infoItemList.size() + ", "
", showFooter = " + showFooter); + "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
notifyItemInserted(positionInserted); notifyItemInserted(positionInserted);
if (footer != null && showFooter) { if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset(); int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(positionInserted, footerNow); notifyItemMoved(positionInserted, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + if (DEBUG) {
" to " + footerNow); Log.d(TAG, "addInfoItem() footer from " + positionInserted
+ " to " + footerNow);
}
} }
} }
@ -186,29 +190,39 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setHeader(View header) { public void setHeader(final View header) {
boolean changed = header != this.header; boolean changed = header != this.header;
this.header = header; this.header = header;
if (changed) notifyDataSetChanged(); if (changed) {
notifyDataSetChanged();
}
} }
public void setFooter(View view) { public void setFooter(final View view) {
this.footer = view; this.footer = view;
} }
public void showFooter(boolean show) { public void showFooter(final boolean show) {
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]"); if (DEBUG) {
if (show == showFooter) return; Log.d(TAG, "showFooter() called with: show = [" + show + "]");
}
if (show == showFooter) {
return;
}
showFooter = show; showFooter = show;
if (show) notifyItemInserted(sizeConsideringHeaderOffset()); if (show) {
else notifyItemRemoved(sizeConsideringHeaderOffset()); notifyItemInserted(sizeConsideringHeaderOffset());
} else {
notifyItemRemoved(sizeConsideringHeaderOffset());
}
} }
private int sizeConsideringHeaderOffset() { private int sizeConsideringHeaderOffset() {
int i = infoItemList.size() + (header != null ? 1 : 0); int i = infoItemList.size() + (header != null ? 1 : 0);
if (DEBUG) Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i); if (DEBUG) {
Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
}
return i; return i;
} }
@ -219,18 +233,27 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@Override @Override
public int getItemCount() { public int getItemCount() {
int count = infoItemList.size(); int count = infoItemList.size();
if (header != null) count++; if (header != null) {
if (footer != null && showFooter) count++; count++;
}
if (footer != null && showFooter) {
count++;
}
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "getItemCount() called, count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter); Log.d(TAG, "getItemCount() called with: "
+ "count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", "
+ "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
} }
return count; return count;
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); if (DEBUG) {
Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
}
if (header != null && position == 0) { if (header != null && position == 0) {
return HEADER_TYPE; return HEADER_TYPE;
@ -243,11 +266,14 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
final InfoItem item = infoItemList.get(position); final InfoItem item = infoItemList.get(position);
switch (item.getInfoType()) { switch (item.getInfoType()) {
case STREAM: case STREAM:
return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE; return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant
? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
case CHANNEL: case CHANNEL:
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE; return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant
? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
case PLAYLIST: case PLAYLIST:
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE; return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant
? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
case COMMENT: case COMMENT:
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE; return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
default: default:
@ -257,9 +283,12 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
if (DEBUG) final int type) {
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]"); if (DEBUG) {
Log.d(TAG, "onCreateViewHolder() called with: "
+ "parent = [" + parent + "], type = [" + type + "]");
}
switch (type) { switch (type) {
case HEADER_TYPE: case HEADER_TYPE:
return new HFHolder(header); return new HFHolder(header);
@ -293,28 +322,38 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
} }
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]"); if (DEBUG) {
Log.d(TAG, "onBindViewHolder() called with: "
+ "holder = [" + holder.getClass().getSimpleName() + "], "
+ "position = [" + position + "]");
}
if (holder instanceof InfoItemHolder) { if (holder instanceof InfoItemHolder) {
// If header isn't null, offset the items by -1 // If header isn't null, offset the items by -1
if (header != null) position--; if (header != null) {
position--;
}
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager); ((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager);
} else if (holder instanceof HFHolder && position == 0 && header != null) { } else if (holder instanceof HFHolder && position == 0 && header != null) {
((HFHolder) holder).view = header; ((HFHolder) holder).view = header;
} else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) { } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset()
&& footer != null && showFooter) {
((HFHolder) holder).view = footer; ((HFHolder) holder).view = footer;
} }
} }
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) { public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
@NonNull final List<Object> payloads) {
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) { if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
for (Object payload : payloads) { for (Object payload : payloads) {
if (payload instanceof StreamStateEntity) { if (payload instanceof StreamStateEntity) {
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager); ((InfoItemHolder) holder).updateState(infoItemList
.get(header == null ? position : position - 1), recordManager);
} else if (payload instanceof Boolean) { } else if (payload instanceof Boolean) {
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager); ((InfoItemHolder) holder).updateState(infoItemList
.get(header == null ? position : position - 1), recordManager);
} }
} }
} else { } else {
@ -325,10 +364,19 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() { return new GridLayoutManager.SpanSizeLookup() {
@Override @Override
public int getSpanSize(int position) { public int getSpanSize(final int position) {
final int type = getItemViewType(position); final int type = getItemViewType(position);
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1; return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
} }
}; };
} }
public class HFHolder extends RecyclerView.ViewHolder {
public View view;
HFHolder(final View v) {
super(v);
view = v;
}
}
} }

View File

@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder { public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder {
public ChannelGridInfoItemHolder(final InfoItemBuilder infoItemBuilder,
public ChannelGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_grid_item, parent); super(infoItemBuilder, R.layout.list_channel_grid_item, parent);
} }
} }

View File

@ -31,19 +31,23 @@ import org.schabi.newpipe.util.Localization;
*/ */
public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder { public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
public final TextView itemChannelDescriptionView; private final TextView itemChannelDescriptionView;
public ChannelInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public ChannelInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_item, parent); super(infoItemBuilder, R.layout.list_channel_item, parent);
itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView); itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager); super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof ChannelInfoItem)) return; if (!(infoItem instanceof ChannelInfoItem)) {
final ChannelInfoItem item = (ChannelInfoItem) infoItem; return;
}
final ChannelInfoItem item;
item = (ChannelInfoItem) infoItem;
itemChannelDescriptionView.setText(item.getDescription()); itemChannelDescriptionView.setText(item.getDescription());
} }

View File

@ -16,9 +16,10 @@ import de.hdodenhof.circleimageview.CircleImageView;
public class ChannelMiniInfoItemHolder extends InfoItemHolder { public class ChannelMiniInfoItemHolder extends InfoItemHolder {
public final CircleImageView itemThumbnailView; public final CircleImageView itemThumbnailView;
public final TextView itemTitleView; public final TextView itemTitleView;
public final TextView itemAdditionalDetailView; private final TextView itemAdditionalDetailView;
ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -26,13 +27,17 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails); itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails);
} }
public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_channel_mini_item, parent); this(infoItemBuilder, R.layout.list_channel_mini_item, parent);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
if (!(infoItem instanceof ChannelInfoItem)) return; final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof ChannelInfoItem)) {
return;
}
final ChannelInfoItem item = (ChannelInfoItem) infoItem; final ChannelInfoItem item = (ChannelInfoItem) infoItem;
itemTitleView.setText(item.getName()); itemTitleView.setText(item.getName());

View File

@ -30,23 +30,24 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
*/ */
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
public final TextView itemTitleView; public final TextView itemTitleView;
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comments_item, parent); super(infoItemBuilder, R.layout.list_comments_item, parent);
itemTitleView = itemView.findViewById(R.id.itemTitleView); itemTitleView = itemView.findViewById(R.id.itemTitleView);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager); super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof CommentsInfoItem)) return; if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem; final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getAuthorName()); itemTitleView.setText(item.getAuthorName());
} }
} }

View File

@ -29,35 +29,41 @@ import java.util.regex.Pattern;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
public class CommentsMiniInfoItemHolder extends InfoItemHolder { public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final int COMMENT_DEFAULT_LINES = 2;
private static final int COMMENT_EXPANDED_LINES = 1000;
private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
public final CircleImageView itemThumbnailView; public final CircleImageView itemThumbnailView;
private final TextView itemContentView; private final TextView itemContentView;
private final TextView itemLikesCountView; private final TextView itemLikesCountView;
private final TextView itemDislikesCountView; private final TextView itemDislikesCountView;
private final TextView itemPublishedTime; private final TextView itemPublishedTime;
private static final int commentDefaultLines = 2;
private static final int commentExpandedLines = 1000;
private String commentText; private String commentText;
private String streamUrl; private String streamUrl;
private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() { private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
@Override @Override
public String transformUrl(Matcher match, String url) { public String transformUrl(final Matcher match, final String url) {
int timestamp = 0; int timestamp = 0;
String hours = match.group(1); String hours = match.group(1);
String minutes = match.group(2); String minutes = match.group(2);
String seconds = match.group(3); String seconds = match.group(3);
if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600); if (hours != null) {
if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60); timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
if(seconds != null) timestamp += (Integer.parseInt(seconds)); }
if (minutes != null) {
timestamp += (Integer.parseInt(minutes.replace(":", "")) * 60);
}
if (seconds != null) {
timestamp += (Integer.parseInt(seconds));
}
return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp); return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp);
} }
}; };
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -67,13 +73,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
itemContentView = itemView.findViewById(R.id.itemCommentContentView); itemContentView = itemView.findViewById(R.id.itemCommentContentView);
} }
public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_comments_mini_item, parent); this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
if (!(infoItem instanceof CommentsInfoItem)) return; final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem; final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemBuilder.getImageLoader() itemBuilder.getImageLoader()
@ -82,7 +92,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
itemThumbnailView.setOnClickListener(view -> { itemThumbnailView.setOnClickListener(view -> {
if(StringUtil.isBlank(item.getAuthorEndpoint())) return; if (StringUtil.isBlank(item.getAuthorEndpoint())) {
return;
}
try { try {
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
NavigationHelper.openChannelFragment( NavigationHelper.openChannelFragment(
@ -97,7 +109,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
streamUrl = item.getUrl(); streamUrl = item.getUrl();
itemContentView.setLines(commentDefaultLines); itemContentView.setLines(COMMENT_DEFAULT_LINES);
commentText = item.getCommentText(); commentText = item.getCommentText();
itemContentView.setText(commentText); itemContentView.setText(commentText);
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
@ -130,12 +142,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
itemView.setOnLongClickListener(new View.OnLongClickListener() { itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override @Override
public boolean onLongClick(View view) { public boolean onLongClick(final View view) {
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext() ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
.getSystemService(Context.CLIPBOARD_SERVICE); .getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText(null,commentText)); clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT).show(); Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
.show();
return true; return true;
} }
@ -144,10 +156,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
} }
private void ellipsize() { private void ellipsize() {
if (itemContentView.getLineCount() > commentDefaultLines){ if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); int endOfLastLine = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2); int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
if(end == -1) end = Math.max(endOfLastLine -2, 0); if (end == -1) {
end = Math.max(endOfLastLine - 2, 0);
}
String newVal = itemContentView.getText().subSequence(0, end) + ""; String newVal = itemContentView.getText().subSequence(0, end) + "";
itemContentView.setText(newVal); itemContentView.setText(newVal);
} }
@ -156,21 +170,23 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private void toggleEllipsize() { private void toggleEllipsize() {
if (itemContentView.getText().toString().equals(commentText)) { if (itemContentView.getText().toString().equals(commentText)) {
if (itemContentView.getLineCount() > commentDefaultLines) ellipsize(); if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
ellipsize();
}
} else { } else {
expand(); expand();
} }
} }
private void expand() { private void expand() {
itemContentView.setMaxLines(commentExpandedLines); itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
itemContentView.setText(commentText); itemContentView.setText(commentText);
linkify(); linkify();
} }
private void linkify(){ private void linkify() {
Linkify.addLinks(itemContentView, Linkify.WEB_URLS); Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink); Linkify.addLinks(itemContentView, PATTERN, null, null, timestampLink);
itemContentView.setMovementMethod(null); itemContentView.setMovementMethod(null);
} }
} }

View File

@ -1,9 +1,10 @@
package org.schabi.newpipe.info_list.holder; package org.schabi.newpipe.info_list.holder;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -31,13 +32,15 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
public abstract class InfoItemHolder extends RecyclerView.ViewHolder { public abstract class InfoItemHolder extends RecyclerView.ViewHolder {
protected final InfoItemBuilder itemBuilder; protected final InfoItemBuilder itemBuilder;
public InfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { public InfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(LayoutInflater.from(infoItemBuilder.getContext()).inflate(layoutId, parent, false)); super(LayoutInflater.from(infoItemBuilder.getContext()).inflate(layoutId, parent, false));
this.itemBuilder = infoItemBuilder; this.itemBuilder = infoItemBuilder;
} }
public abstract void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager); public abstract void updateFromItem(InfoItem infoItem,
HistoryRecordManager historyRecordManager);
public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateState(final InfoItem infoItem,
} final HistoryRecordManager historyRecordManager) { }
} }

View File

@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder { public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder {
public PlaylistGridInfoItemHolder(final InfoItemBuilder infoItemBuilder,
public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
} }
} }

View File

@ -6,8 +6,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder { public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder {
public PlaylistInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_item, parent); super(infoItemBuilder, R.layout.list_playlist_item, parent);
} }
} }

View File

@ -13,11 +13,12 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView; public final ImageView itemThumbnailView;
public final TextView itemStreamCountView; private final TextView itemStreamCountView;
public final TextView itemTitleView; public final TextView itemTitleView;
public final TextView itemUploaderView; public final TextView itemUploaderView;
public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -26,13 +27,17 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
itemUploaderView = itemView.findViewById(R.id.itemUploaderView); itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
} }
public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_playlist_mini_item, parent); this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
if (!(infoItem instanceof PlaylistInfoItem)) return; final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof PlaylistInfoItem)) {
return;
}
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
itemTitleView.setText(item.getName()); itemTitleView.setText(item.getName());
@ -41,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
itemBuilder.getImageLoader() itemBuilder.getImageLoader()
.displayImage(item.getThumbnailUrl(), itemThumbnailView, .displayImage(item.getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
itemView.setOnClickListener(view -> { itemView.setOnClickListener(view -> {
if (itemBuilder.getOnPlaylistSelectedListener() != null) { if (itemBuilder.getOnPlaylistSelectedListener() != null) {

View File

@ -6,8 +6,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
public class StreamGridInfoItemHolder extends StreamInfoItemHolder { public class StreamGridInfoItemHolder extends StreamInfoItemHolder {
public StreamGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
super(infoItemBuilder, R.layout.list_stream_grid_item, parent); }
}
} }

View File

@ -20,39 +20,46 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
* <p> * <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamInfoItemHolder.java is part of NewPipe. * StreamInfoItemHolder.java is part of NewPipe.
* </p>
* <p> * <p>
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* </p>
* <p> * <p>
* NewPipe is distributed in the hope that it will be useful, * NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* </p?
* <p> * <p>
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
* </p>
*/ */
public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
public final TextView itemAdditionalDetails; public final TextView itemAdditionalDetails;
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_stream_item, parent); this(infoItemBuilder, R.layout.list_stream_item, parent);
} }
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails); itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager); super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof StreamInfoItem)) return; if (!(infoItem instanceof StreamInfoItem)) {
return;
}
final StreamInfoItem item = (StreamInfoItem) infoItem; final StreamInfoItem item = (StreamInfoItem) infoItem;
itemAdditionalDetails.setText(getStreamInfoDetailLine(item)); itemAdditionalDetails.setText(getStreamInfoDetailLine(item));
@ -62,11 +69,14 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
String viewsAndDate = ""; String viewsAndDate = "";
if (infoItem.getViewCount() >= 0) { if (infoItem.getViewCount() >= 0) {
if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) { if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
viewsAndDate = Localization.listeningCount(itemBuilder.getContext(), infoItem.getViewCount()); viewsAndDate = Localization
.listeningCount(itemBuilder.getContext(), infoItem.getViewCount());
} else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) { } else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) {
viewsAndDate = Localization.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount()); viewsAndDate = Localization
.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount());
} else { } else {
viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); viewsAndDate = Localization
.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount());
} }
} }
@ -84,10 +94,12 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) { private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
if (infoItem.getUploadDate() != null) { if (infoItem.getUploadDate() != null) {
String formattedRelativeTime = Localization.relativeTime(infoItem.getUploadDate().date()); String formattedRelativeTime = Localization
.relativeTime(infoItem.getUploadDate().date());
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext()) if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
.getBoolean(itemBuilder.getContext().getString(R.string.show_original_time_ago_key), false)) { .getBoolean(itemBuilder.getContext()
.getString(R.string.show_original_time_ago_key), false)) {
formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")"; formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
} }
return formattedRelativeTime; return formattedRelativeTime;

View File

@ -1,11 +1,12 @@
package org.schabi.newpipe.info_list.holder; package org.schabi.newpipe.info_list.holder;
import androidx.core.content.ContextCompat;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
@ -21,14 +22,14 @@ import org.schabi.newpipe.views.AnimatedProgressBar;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class StreamMiniInfoItemHolder extends InfoItemHolder { public class StreamMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView; public final ImageView itemThumbnailView;
public final TextView itemVideoTitleView; public final TextView itemVideoTitleView;
public final TextView itemUploaderView; public final TextView itemUploaderView;
public final TextView itemDurationView; public final TextView itemDurationView;
public final AnimatedProgressBar itemProgressView; private final AnimatedProgressBar itemProgressView;
StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -38,13 +39,16 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
itemProgressView = itemView.findViewById(R.id.itemProgressView); itemProgressView = itemView.findViewById(R.id.itemProgressView);
} }
public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { public StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_stream_mini_item, parent); this(infoItemBuilder, R.layout.list_stream_mini_item, parent);
} }
@Override @Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateFromItem(final InfoItem infoItem,
if (!(infoItem instanceof StreamInfoItem)) return; final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof StreamInfoItem)) {
return;
}
final StreamInfoItem item = (StreamInfoItem) infoItem; final StreamInfoItem item = (StreamInfoItem) infoItem;
itemVideoTitleView.setText(item.getName()); itemVideoTitleView.setText(item.getName());
@ -56,11 +60,13 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
R.color.duration_background_color)); R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE); itemDurationView.setVisibility(View.VISIBLE);
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
.blockingGet()[0];
if (state2 != null) { if (state2 != null) {
itemProgressView.setVisibility(View.VISIBLE); itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getDuration()); itemProgressView.setMax((int) item.getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state2.getProgressTime())); itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state2.getProgressTime()));
} else { } else {
itemProgressView.setVisibility(View.GONE); itemProgressView.setVisibility(View.GONE);
} }
@ -103,16 +109,20 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
} }
@Override @Override
public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { public void updateState(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
final StreamInfoItem item = (StreamInfoItem) infoItem; final StreamInfoItem item = (StreamInfoItem) infoItem;
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) { if (state != null && item.getDuration() > 0
&& item.getStreamType() != StreamType.LIVE_STREAM) {
itemProgressView.setMax((int) item.getDuration()); itemProgressView.setMax((int) item.getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) { if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
} else { } else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500); AnimationUtils.animateView(itemProgressView, true, 500);
} }
} else if (itemProgressView.getVisibility() == View.VISIBLE) { } else if (itemProgressView.getVisibility() == View.VISIBLE) {

View File

@ -5,16 +5,17 @@ import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.ActionBar;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.list.ListViewContract; import org.schabi.newpipe.fragments.list.ListViewContract;
@ -25,10 +26,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
* This fragment is design to be used with persistent data such as * This fragment is design to be used with persistent data such as
* {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained * {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained
* in the list adapter to avoid extra writes when the it exits or re-enters its lifecycle. * in the list adapter to avoid extra writes when the it exits or re-enters its lifecycle.
* * <p>
* This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is * This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is
* called and is memory efficient when in backstack. * called and is memory efficient when in backstack.
* */ * </p>
*
* @param <I> List of {@link org.schabi.newpipe.database.LocalItem}s
* @param <N> {@link Void}
*/
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I> public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener { implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
@ -36,21 +41,19 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
// Views // Views
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected View headerRootView; private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected View footerRootView; private View headerRootView;
private View footerRootView;
protected LocalItemListAdapter itemListAdapter; protected LocalItemListAdapter itemListAdapter;
protected RecyclerView itemsList; protected RecyclerView itemsList;
private int updateFlags = 0; private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Lifecycle - Creation // Lifecycle - Creation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity) PreferenceManager.getDefaultSharedPreferences(activity)
@ -70,8 +73,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
if (updateFlags != 0) { if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout(); final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); itemsList.setLayoutManager(
itemListAdapter.setGridItemVariants(useGrid); useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setUseGridVariant(useGrid);
itemListAdapter.notifyDataSetChanged(); itemListAdapter.notifyDataSetChanged();
} }
updateFlags = 0; updateFlags = 0;
@ -94,7 +98,8 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
final Resources resources = activity.getResources(); final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width); int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density); width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width); final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
/ (double) width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount)); lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
return lm; return lm;
@ -105,7 +110,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
} }
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
itemListAdapter = new LocalItemListAdapter(activity); itemListAdapter = new LocalItemListAdapter(activity);
@ -114,9 +119,11 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
itemsList = rootView.findViewById(R.id.items_list); itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid); itemListAdapter.setUseGridVariant(useGrid);
itemListAdapter.setHeader(headerRootView = getListHeader()); headerRootView = getListHeader();
itemListAdapter.setFooter(footerRootView = getListFooter()); itemListAdapter.setHeader(headerRootView);
footerRootView = getListFooter();
itemListAdapter.setFooter(footerRootView);
itemsList.setAdapter(itemListAdapter); itemsList.setAdapter(itemListAdapter);
} }
@ -131,13 +138,17 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + if (DEBUG) {
"], inflater = [" + inflater + "]"); Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
final ActionBar supportActionBar = activity.getSupportActionBar(); final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar == null) return; if (supportActionBar == null) {
return;
}
supportActionBar.setDisplayShowTitleEnabled(true); supportActionBar.setDisplayShowTitleEnabled(true);
} }
@ -158,7 +169,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void startLoading(boolean forceLoad) { public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad); super.startLoading(forceLoad);
resetFragment(); resetFragment();
} }
@ -166,24 +177,36 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
@Override @Override
public void showLoading() { public void showLoading() {
super.showLoading(); super.showLoading();
if (itemsList != null) animateView(itemsList, false, 200); if (itemsList != null) {
if (headerRootView != null) animateView(headerRootView, false, 200); animateView(itemsList, false, 200);
}
if (headerRootView != null) {
animateView(headerRootView, false, 200);
}
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
super.hideLoading(); super.hideLoading();
if (itemsList != null) animateView(itemsList, true, 200); if (itemsList != null) {
if (headerRootView != null) animateView(headerRootView, true, 200); animateView(itemsList, true, 200);
}
if (headerRootView != null) {
animateView(headerRootView, true, 200);
}
} }
@Override @Override
public void showError(String message, boolean showRetryButton) { public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton); super.showError(message, showRetryButton);
showListFooter(false); showListFooter(false);
if (itemsList != null) animateView(itemsList, false, 200); if (itemsList != null) {
if (headerRootView != null) animateView(headerRootView, false, 200); animateView(itemsList, false, 200);
}
if (headerRootView != null) {
animateView(headerRootView, false, 200);
}
} }
@Override @Override
@ -194,14 +217,18 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
@Override @Override
public void showListFooter(final boolean show) { public void showListFooter(final boolean show) {
if (itemsList == null) return; if (itemsList == null) {
return;
}
itemsList.post(() -> { itemsList.post(() -> {
if (itemListAdapter != null) itemListAdapter.showFooter(show); if (itemListAdapter != null) {
itemListAdapter.showFooter(show);
}
}); });
} }
@Override @Override
public void handleNextItems(N result) { public void handleNextItems(final N result) {
isLoading.set(false); isLoading.set(false);
} }
@ -210,30 +237,35 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected void resetFragment() { protected void resetFragment() {
if (itemListAdapter != null) itemListAdapter.clearStreamItemList(); if (itemListAdapter != null) {
itemListAdapter.clearStreamItemList();
}
} }
@Override @Override
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
resetFragment(); resetFragment();
return super.onError(exception); return super.onError(exception);
} }
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.list_view_mode_key))) { if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG; updateFlags |= LIST_MODE_UPDATE_FLAG;
} }
} }
protected boolean isGridLayout() { protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)); final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
if ("auto".equals(list_mode)) { .getString(getString(R.string.list_view_mode_key),
getString(R.string.list_view_mode_value));
if ("auto".equals(listMode)) {
final Configuration configuration = getResources().getConfiguration(); final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else { } else {
return "grid".equals(list_mode); return "grid".equals(listMode);
} }
} }
} }

View File

@ -1,12 +1,13 @@
package org.schabi.newpipe.local; package org.schabi.newpipe.local;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View; import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public class HeaderFooterHolder extends RecyclerView.ViewHolder { public class HeaderFooterHolder extends RecyclerView.ViewHolder {
public View view; public View view;
public HeaderFooterHolder(View v) { public HeaderFooterHolder(final View v) {
super(v); super(v);
view = v; view = v;
} }

View File

@ -30,14 +30,12 @@ import org.schabi.newpipe.util.OnClickGesture;
*/ */
public class LocalItemBuilder { public class LocalItemBuilder {
private static final String TAG = LocalItemBuilder.class.toString();
private final Context context; private final Context context;
private final ImageLoader imageLoader = ImageLoader.getInstance(); private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnClickGesture<LocalItem> onSelectedListener; private OnClickGesture<LocalItem> onSelectedListener;
public LocalItemBuilder(Context context) { public LocalItemBuilder(final Context context) {
this.context = context; this.context = context;
} }
@ -54,7 +52,7 @@ public class LocalItemBuilder {
return onSelectedListener; return onSelectedListener;
} }
public void setOnItemSelectedListener(OnClickGesture<LocalItem> listener) { public void setOnItemSelectedListener(final OnClickGesture<LocalItem> listener) {
this.onSelectedListener = listener; this.onSelectedListener = listener;
} }
} }

View File

@ -1,13 +1,14 @@
package org.schabi.newpipe.local; package org.schabi.newpipe.local;
import android.content.Context; import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity;
@ -50,7 +51,6 @@ import java.util.List;
*/ */
public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = LocalItemListAdapter.class.getSimpleName(); private static final String TAG = LocalItemListAdapter.class.getSimpleName();
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@ -63,8 +63,8 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004; private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000; private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001; private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002; private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004; private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
private final LocalItemBuilder localItemBuilder; private final LocalItemBuilder localItemBuilder;
private final ArrayList<LocalItem> localItems; private final ArrayList<LocalItem> localItems;
@ -76,7 +76,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private View header = null; private View header = null;
private View footer = null; private View footer = null;
public LocalItemListAdapter(Context context) { public LocalItemListAdapter(final Context context) {
recordManager = new HistoryRecordManager(context); recordManager = new HistoryRecordManager(context);
localItemBuilder = new LocalItemBuilder(context); localItemBuilder = new LocalItemBuilder(context);
localItems = new ArrayList<>(); localItems = new ArrayList<>();
@ -84,7 +84,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
Localization.getPreferredLocale(context)); Localization.getPreferredLocale(context));
} }
public void setSelectedListener(OnClickGesture<LocalItem> listener) { public void setSelectedListener(final OnClickGesture<LocalItem> listener) {
localItemBuilder.setOnItemSelectedListener(listener); localItemBuilder.setOnItemSelectedListener(listener);
} }
@ -92,28 +92,34 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
localItemBuilder.setOnItemSelectedListener(null); localItemBuilder.setOnItemSelectedListener(null);
} }
public void addItems(@Nullable List<? extends LocalItem> data) { public void addItems(@Nullable final List<? extends LocalItem> data) {
if (data == null) { if (data == null) {
return; return;
} }
if (DEBUG) Log.d(TAG, "addItems() before > localItems.size() = " + if (DEBUG) {
localItems.size() + ", data.size() = " + data.size()); Log.d(TAG, "addItems() before > localItems.size() = "
+ localItems.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeader(); int offsetStart = sizeConsideringHeader();
localItems.addAll(data); localItems.addAll(data);
if (DEBUG) Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + if (DEBUG) {
", localItems.size() = " + localItems.size() + Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", "
", header = " + header + ", footer = " + footer + + "localItems.size() = " + localItems.size() + ", "
", showFooter = " + showFooter); + "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
notifyItemRangeInserted(offsetStart, data.size()); notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) { if (footer != null && showFooter) {
int footerNow = sizeConsideringHeader(); int footerNow = sizeConsideringHeader();
notifyItemMoved(offsetStart, footerNow); notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart + if (DEBUG) {
" to " + footerNow); Log.d(TAG, "addItems() footer from " + offsetStart
+ " to " + footerNow);
}
} }
} }
@ -123,12 +129,16 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
notifyItemRemoved(index + (header != null ? 1 : 0)); notifyItemRemoved(index + (header != null ? 1 : 0));
} }
public boolean swapItems(int fromAdapterPosition, int toAdapterPosition) { public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) {
final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition); final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition);
final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition); final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition);
if (actualFrom < 0 || actualTo < 0) return false; if (actualFrom < 0 || actualTo < 0) {
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) return false; return false;
}
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) {
return false;
}
localItems.add(actualTo, localItems.remove(actualFrom)); localItems.add(actualTo, localItems.remove(actualFrom));
notifyItemMoved(fromAdapterPosition, toAdapterPosition); notifyItemMoved(fromAdapterPosition, toAdapterPosition);
@ -143,27 +153,36 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setGridItemVariants(boolean useGridVariant) { public void setUseGridVariant(final boolean useGridVariant) {
this.useGridVariant = useGridVariant; this.useGridVariant = useGridVariant;
} }
public void setHeader(View header) { public void setHeader(final View header) {
boolean changed = header != this.header; boolean changed = header != this.header;
this.header = header; this.header = header;
if (changed) notifyDataSetChanged(); if (changed) {
notifyDataSetChanged();
}
} }
public void setFooter(View view) { public void setFooter(final View view) {
this.footer = view; this.footer = view;
} }
public void showFooter(boolean show) { public void showFooter(final boolean show) {
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]"); if (DEBUG) {
if (show == showFooter) return; Log.d(TAG, "showFooter() called with: show = [" + show + "]");
}
if (show == showFooter) {
return;
}
showFooter = show; showFooter = show;
if (show) notifyItemInserted(sizeConsideringHeader()); if (show) {
else notifyItemRemoved(sizeConsideringHeader()); notifyItemInserted(sizeConsideringHeader());
} else {
notifyItemRemoved(sizeConsideringHeader());
}
} }
private int adapterOffsetWithoutHeader(final int offset) { private int adapterOffsetWithoutHeader(final int offset) {
@ -181,21 +200,27 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
@Override @Override
public int getItemCount() { public int getItemCount() {
int count = localItems.size(); int count = localItems.size();
if (header != null) count++; if (header != null) {
if (footer != null && showFooter) count++; count++;
}
if (footer != null && showFooter) {
count++;
}
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "getItemCount() called, count = " + count + Log.d(TAG, "getItemCount() called, count = " + count + ", "
", localItems.size() = " + localItems.size() + + "localItems.size() = " + localItems.size() + ", "
", header = " + header + ", footer = " + footer + + "header = " + header + ", footer = " + footer + ", "
", showFooter = " + showFooter); + "showFooter = " + showFooter);
} }
return count; return count;
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); if (DEBUG) {
Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
}
if (header != null && position == 0) { if (header != null && position == 0) {
return HEADER_TYPE; return HEADER_TYPE;
@ -208,23 +233,34 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
final LocalItem item = localItems.get(position); final LocalItem item = localItems.get(position);
switch (item.getLocalItemType()) { switch (item.getLocalItemType()) {
case PLAYLIST_LOCAL_ITEM: return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE; case PLAYLIST_LOCAL_ITEM:
case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE; return useGridVariant
? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_REMOTE_ITEM:
return useGridVariant
? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE; case PLAYLIST_STREAM_ITEM:
case STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE; return useGridVariant
? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
case STATISTIC_STREAM_ITEM:
return useGridVariant
? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
default: default:
Log.e(TAG, "No holder type has been considered for item: [" + Log.e(TAG, "No holder type has been considered for item: ["
item.getLocalItemType() + "]"); + item.getLocalItemType() + "]");
return -1; return -1;
} }
} }
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + final int type) {
parent + "], type = [" + type + "]"); if (DEBUG) {
Log.d(TAG, "onCreateViewHolder() called with: "
+ "parent = [" + parent + "], type = [" + type + "]");
}
switch (type) { switch (type) {
case HEADER_TYPE: case HEADER_TYPE:
return new HeaderFooterHolder(header); return new HeaderFooterHolder(header);
@ -253,15 +289,21 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + if (DEBUG) {
holder.getClass().getSimpleName() + "], position = [" + position + "]"); Log.d(TAG, "onBindViewHolder() called with: "
+ "holder = [" + holder.getClass().getSimpleName() + "], "
+ "position = [" + position + "]");
}
if (holder instanceof LocalItemHolder) { if (holder instanceof LocalItemHolder) {
// If header isn't null, offset the items by -1 // If header isn't null, offset the items by -1
if (header != null) position--; if (header != null) {
position--;
}
((LocalItemHolder) holder).updateFromItem(localItems.get(position), recordManager, dateFormat); ((LocalItemHolder) holder)
.updateFromItem(localItems.get(position), recordManager, dateFormat);
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { } else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
((HeaderFooterHolder) holder).view = header; ((HeaderFooterHolder) holder).view = header;
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
@ -271,13 +313,16 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) { public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
@NonNull final List<Object> payloads) {
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) { if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
for (Object payload : payloads) { for (Object payload : payloads) {
if (payload instanceof StreamStateEntity) { if (payload instanceof StreamStateEntity) {
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager); ((LocalItemHolder) holder).updateState(localItems
.get(header == null ? position : position - 1), recordManager);
} else if (payload instanceof Boolean) { } else if (payload instanceof Boolean) {
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager); ((LocalItemHolder) holder).updateState(localItems
.get(header == null ? position : position - 1), recordManager);
} }
} }
} else { } else {
@ -288,7 +333,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() { return new GridLayoutManager.SpanSizeLookup() {
@Override @Override
public int getSpanSize(int position) { public int getSpanSize(final int position) {
final int type = getItemViewType(position); final int type = getItemViewType(position);
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1; return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
} }

View File

@ -5,15 +5,15 @@ import android.app.AlertDialog.Builder;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log; import android.util.Log;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import io.reactivex.disposables.Disposable;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
@ -39,10 +39,9 @@ import io.reactivex.Flowable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public final class BookmarkFragment public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
@State @State
protected Parcelable itemsListState; protected Parcelable itemsListState;
@ -55,10 +54,26 @@ public final class BookmarkFragment
// Fragment LifeCycle - Creation // Fragment LifeCycle - Creation
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
private static List<PlaylistLocalItem> merge(
final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
List<PlaylistLocalItem> items = new ArrayList<>(
localPlaylists.size() + remotePlaylists.size());
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) ->
left.getOrderingName().compareToIgnoreCase(right.getOrderingName()));
return items;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (activity == null) return; if (activity == null) {
return;
}
final AppDatabase database = NewPipeDatabase.getInstance(activity); final AppDatabase database = NewPipeDatabase.getInstance(activity);
localPlaylistManager = new LocalPlaylistManager(database); localPlaylistManager = new LocalPlaylistManager(database);
remotePlaylistManager = new RemotePlaylistManager(database); remotePlaylistManager = new RemotePlaylistManager(database);
@ -67,41 +82,44 @@ public final class BookmarkFragment
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable final ViewGroup container,
Bundle savedInstanceState) { final Bundle savedInstanceState) {
if(!useAsFrontPage) { if (!useAsFrontPage) {
setTitle(activity.getString(R.string.tab_bookmarks)); setTitle(activity.getString(R.string.tab_bookmarks));
} }
return inflater.inflate(R.layout.fragment_bookmarks, container, false); return inflater.inflate(R.layout.fragment_bookmarks, container, false);
} }
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.tab_bookmarks));
}
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Views // Fragment LifeCycle - Views
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override @Override
protected void initViews(View rootView, Bundle savedInstanceState) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.tab_bookmarks));
}
}
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
} }
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
///////////////////////////////////////////////////////////////////////////
@Override @Override
protected void initListeners() { protected void initListeners() {
super.initListeners(); super.initListeners();
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() { itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override @Override
public void selected(LocalItem selectedItem) { public void selected(final LocalItem selectedItem) {
final FragmentManager fragmentManager = getFM(); final FragmentManager fragmentManager = getFM();
if (selectedItem instanceof PlaylistMetadataEntry) { if (selectedItem instanceof PlaylistMetadataEntry) {
@ -120,7 +138,7 @@ public final class BookmarkFragment
} }
@Override @Override
public void held(LocalItem selectedItem) { public void held(final LocalItem selectedItem) {
if (selectedItem instanceof PlaylistMetadataEntry) { if (selectedItem instanceof PlaylistMetadataEntry) {
showLocalDialog((PlaylistMetadataEntry) selectedItem); showLocalDialog((PlaylistMetadataEntry) selectedItem);
} else if (selectedItem instanceof PlaylistRemoteEntity) { } else if (selectedItem instanceof PlaylistRemoteEntity) {
@ -131,26 +149,20 @@ public final class BookmarkFragment
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading // Fragment LifeCycle - Destruction
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override @Override
public void startLoading(boolean forceLoad) { public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad); super.startLoading(forceLoad);
Flowable.combineLatest( Flowable.combineLatest(localPlaylistManager.getPlaylists(),
localPlaylistManager.getPlaylists(), remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
remotePlaylistManager.getPlaylists(), .onBackpressureLatest()
BookmarkFragment::merge .observeOn(AndroidSchedulers.mainThread())
).onBackpressureLatest() .subscribe(getPlaylistsSubscriber());
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistsSubscriber());
} }
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Destruction
///////////////////////////////////////////////////////////////////////////
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
@ -161,16 +173,26 @@ public final class BookmarkFragment
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if (disposables != null) disposables.clear(); if (disposables != null) {
if (databaseSubscription != null) databaseSubscription.cancel(); disposables.clear();
}
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = null; databaseSubscription = null;
} }
///////////////////////////////////////////////////////////////////////////
// Subscriptions Loader
///////////////////////////////////////////////////////////////////////////
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (disposables != null) disposables.dispose(); if (disposables != null) {
disposables.dispose();
}
disposables = null; disposables = null;
localPlaylistManager = null; localPlaylistManager = null;
@ -178,39 +200,41 @@ public final class BookmarkFragment
itemsListState = null; itemsListState = null;
} }
///////////////////////////////////////////////////////////////////////////
// Subscriptions Loader
///////////////////////////////////////////////////////////////////////////
private Subscriber<List<PlaylistLocalItem>> getPlaylistsSubscriber() { private Subscriber<List<PlaylistLocalItem>> getPlaylistsSubscriber() {
return new Subscriber<List<PlaylistLocalItem>>() { return new Subscriber<List<PlaylistLocalItem>>() {
@Override @Override
public void onSubscribe(Subscription s) { public void onSubscribe(final Subscription s) {
showLoading(); showLoading();
if (databaseSubscription != null) databaseSubscription.cancel(); if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = s; databaseSubscription = s;
databaseSubscription.request(1); databaseSubscription.request(1);
} }
@Override @Override
public void onNext(List<PlaylistLocalItem> subscriptions) { public void onNext(final List<PlaylistLocalItem> subscriptions) {
handleResult(subscriptions); handleResult(subscriptions);
if (databaseSubscription != null) databaseSubscription.request(1); if (databaseSubscription != null) {
databaseSubscription.request(1);
}
} }
@Override @Override
public void onError(Throwable exception) { public void onError(final Throwable exception) {
BookmarkFragment.this.onError(exception); BookmarkFragment.this.onError(exception);
} }
@Override @Override
public void onComplete() { public void onComplete() { }
}
}; };
} }
///////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
@Override @Override
public void handleResult(@NonNull List<PlaylistLocalItem> result) { public void handleResult(@NonNull final List<PlaylistLocalItem> result) {
super.handleResult(result); super.handleResult(result);
itemListAdapter.clearStreamItemList(); itemListAdapter.clearStreamItemList();
@ -227,55 +251,58 @@ public final class BookmarkFragment
} }
hideLoading(); hideLoading();
} }
///////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
@Override @Override
protected boolean onError(Throwable exception) { protected boolean onError(final Throwable exception) {
if (super.onError(exception)) return true; if (super.onError(exception)) {
return true;
}
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
"none", "Bookmark", R.string.general_error); "none", "Bookmark", R.string.general_error);
return true; return true;
} }
@Override
protected void resetFragment() {
super.resetFragment();
if (disposables != null) disposables.clear();
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Utils // Utils
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override
protected void resetFragment() {
super.resetFragment();
if (disposables != null) {
disposables.clear();
}
}
private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) { private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid())); showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid()));
} }
private void showLocalDialog(PlaylistMetadataEntry selectedItem) { private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null); View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text); EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
editText.setText(selectedItem.name); editText.setText(selectedItem.name);
Builder builder = new AlertDialog.Builder(activity); Builder builder = new AlertDialog.Builder(activity);
builder.setView(dialogView) builder.setView(dialogView)
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> { .setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()); changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.delete, (dialog, which) -> { .setNeutralButton(R.string.delete, (dialog, which) -> {
showDeleteDialog(selectedItem.name, showDeleteDialog(selectedItem.name,
localPlaylistManager.deletePlaylist(selectedItem.uid)); localPlaylistManager.deletePlaylist(selectedItem.uid));
dialog.dismiss(); dialog.dismiss();
}) })
.create() .create()
.show(); .show();
} }
private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) { private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) {
if (activity == null || disposables == null) return; if (activity == null || disposables == null) {
return;
}
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setTitle(name) .setTitle(name)
@ -284,40 +311,27 @@ public final class BookmarkFragment
.setPositiveButton(R.string.delete, (dialog, i) -> .setPositiveButton(R.string.delete, (dialog, i) ->
disposables.add(deleteReactor disposables.add(deleteReactor
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> {/*Do nothing on success*/}, this::onError)) .subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
) )
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
} }
private void changeLocalPlaylistName(long id, String name) { private void changeLocalPlaylistName(final long id, final String name) {
if (localPlaylistManager == null) { if (localPlaylistManager == null) {
return; return;
} }
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Updating playlist id=[" + id + Log.d(TAG, "Updating playlist id=[" + id + "] "
"] with new name=[" + name + "] items"); + "with new name=[" + name + "] items");
} }
localPlaylistManager.renamePlaylist(id, name); localPlaylistManager.renamePlaylist(id, name);
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name) final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(longs -> {/*Do nothing on success*/}, this::onError); .subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
disposables.add(disposable); disposables.add(disposable);
} }
private static List<PlaylistLocalItem> merge(final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
List<PlaylistLocalItem> items = new ArrayList<>(
localPlaylists.size() + remotePlaylists.size());
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) ->
left.getOrderingName().compareToIgnoreCase(right.getOrderingName()));
return items;
}
} }

View File

@ -69,13 +69,13 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) { final Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_playlists, container); return inflater.inflate(R.layout.dialog_playlists, container);
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
final LocalPlaylistManager playlistManager = final LocalPlaylistManager playlistManager =
@ -84,9 +84,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
playlistAdapter = new LocalItemListAdapter(getActivity()); playlistAdapter = new LocalItemListAdapter(getActivity());
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() { playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override @Override
public void selected(LocalItem selectedItem) { public void selected(final LocalItem selectedItem) {
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) {
return; return;
}
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem,
getStreams()); getStreams());
} }
@ -126,7 +127,9 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void openCreatePlaylistDialog() { public void openCreatePlaylistDialog() {
if (getStreams() == null || getFragmentManager() == null) return; if (getStreams() == null || getFragmentManager() == null) {
return;
}
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG); PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG);
getDialog().dismiss(); getDialog().dismiss();
@ -145,16 +148,19 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
} }
} }
private void onPlaylistSelected(@NonNull LocalPlaylistManager manager, private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
@NonNull PlaylistMetadataEntry playlist, @NonNull final PlaylistMetadataEntry playlist,
@NonNull List<StreamEntity> streams) { @NonNull final List<StreamEntity> streams) {
if (getStreams() == null) return; if (getStreams() == null) {
return;
}
final Toast successToast = Toast.makeText(getContext(), final Toast successToast = Toast.makeText(getContext(),
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) {
playlistDisposables.add(manager.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) playlistDisposables.add(manager
.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> successToast.show())); .subscribe(ignored -> successToast.show()));
} }

View File

@ -3,12 +3,13 @@ package org.schabi.newpipe.local.dialog;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
@ -19,8 +20,6 @@ import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
public final class PlaylistCreationDialog extends PlaylistDialog { public final class PlaylistCreationDialog extends PlaylistDialog {
private static final String TAG = PlaylistCreationDialog.class.getCanonicalName();
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) { public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
PlaylistCreationDialog dialog = new PlaylistCreationDialog(); PlaylistCreationDialog dialog = new PlaylistCreationDialog();
dialog.setInfo(streams); dialog.setInfo(streams);
@ -33,8 +32,10 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
@NonNull @NonNull
@Override @Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
if (getStreams() == null) return super.onCreateDialog(savedInstanceState); if (getStreams() == null) {
return super.onCreateDialog(savedInstanceState);
}
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null); View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
EditText nameInput = dialogView.findViewById(R.id.playlist_name); EditText nameInput = dialogView.findViewById(R.id.playlist_name);

View File

@ -2,10 +2,11 @@ package org.schabi.newpipe.local.dialog;
import android.app.Dialog; import android.app.Dialog;
import android.os.Bundle; import android.os.Bundle;
import android.view.Window;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import android.view.Window;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
@ -14,7 +15,6 @@ import java.util.List;
import java.util.Queue; import java.util.Queue;
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead { public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
private List<StreamEntity> streamEntities; private List<StreamEntity> streamEntities;
private StateSaver.SavedState savedState; private StateSaver.SavedState savedState;
@ -32,7 +32,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
savedState = StateSaver.tryToRestore(savedInstanceState, this); savedState = StateSaver.tryToRestore(savedInstanceState, this);
} }
@ -45,7 +45,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
@NonNull @NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState); final Dialog dialog = super.onCreateDialog(savedInstanceState);
//remove title //remove title
final Window window = dialog.getWindow(); final Window window = dialog.getWindow();
@ -66,18 +66,18 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
} }
@Override @Override
public void writeTo(Queue<Object> objectsToSave) { public void writeTo(final Queue<Object> objectsToSave) {
objectsToSave.add(streamEntities); objectsToSave.add(streamEntities);
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) { public void readFrom(@NonNull final Queue<Object> savedObjects) {
streamEntities = (List<StreamEntity>) savedObjects.poll(); streamEntities = (List<StreamEntity>) savedObjects.poll();
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
if (getActivity() != null) { if (getActivity() != null) {
savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(), savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(),

View File

@ -41,7 +41,9 @@ import java.util.*
class FeedFragment : BaseListFragment<FeedState, Unit>() { class FeedFragment : BaseListFragment<FeedState, Unit>() {
private lateinit var viewModel: FeedViewModel private lateinit var viewModel: FeedViewModel
@State @JvmField var listState: Parcelable? = null @State
@JvmField
var listState: Parcelable? = null
private var groupId = FeedGroupEntity.GROUP_ALL_ID private var groupId = FeedGroupEntity.GROUP_ALL_ID
private var groupName = "" private var groupName = ""
@ -49,13 +51,14 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
useDefaultStateSaving(false) setUseDefaultStateSaving(false)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) ?: FeedGroupEntity.GROUP_ALL_ID groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
?: FeedGroupEntity.GROUP_ALL_ID
groupName = arguments?.getString(KEY_GROUP_NAME) ?: "" groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
} }
@ -107,7 +110,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
inflater.inflate(R.menu.menu_feed_fragment, menu) inflater.inflate(R.menu.menu_feed_fragment, menu)
if (useAsFrontPage) { if (useAsFrontPage) {
menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
} }
} }

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.history; package org.schabi.newpipe.local.history;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -14,19 +15,19 @@ import java.util.Date;
/** /**
* Adapter for history entries * This is an adapter for history entries.
* @param <E> the type of the entries *
* @param <E> the type of the entries
* @param <VH> the type of the view holder * @param <VH> the type of the view holder
*/ */
public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final ArrayList<E> mEntries; private final ArrayList<E> mEntries;
private final DateFormat mDateFormat; private final DateFormat mDateFormat;
private final Context mContext; private final Context mContext;
private OnHistoryItemClickListener<E> onHistoryItemClickListener = null; private OnHistoryItemClickListener<E> onHistoryItemClickListener = null;
public HistoryEntryAdapter(final Context context) {
public HistoryEntryAdapter(Context context) {
super(); super();
mContext = context; mContext = context;
mEntries = new ArrayList<>(); mEntries = new ArrayList<>();
@ -34,7 +35,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
Localization.getPreferredLocale(context)); Localization.getPreferredLocale(context));
} }
public void setEntries(@NonNull Collection<E> historyEntries) { public void setEntries(@NonNull final Collection<E> historyEntries) {
mEntries.clear(); mEntries.clear();
mEntries.addAll(historyEntries); mEntries.addAll(historyEntries);
notifyDataSetChanged(); notifyDataSetChanged();
@ -49,7 +50,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
notifyDataSetChanged(); notifyDataSetChanged();
} }
protected String getFormattedDate(Date date) { protected String getFormattedDate(final Date date) {
return mDateFormat.format(date); return mDateFormat.format(date);
} }
@ -63,10 +64,10 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
} }
@Override @Override
public void onBindViewHolder(VH holder, int position) { public void onBindViewHolder(final VH holder, final int position) {
final E entry = mEntries.get(position); final E entry = mEntries.get(position);
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if(onHistoryItemClickListener != null) { if (onHistoryItemClickListener != null) {
onHistoryItemClickListener.onHistoryItemClick(entry); onHistoryItemClickListener.onHistoryItemClick(entry);
} }
}); });
@ -83,14 +84,15 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
} }
@Override @Override
public void onViewRecycled(VH holder) { public void onViewRecycled(final VH holder) {
super.onViewRecycled(holder); super.onViewRecycled(holder);
holder.itemView.setOnClickListener(null); holder.itemView.setOnClickListener(null);
} }
abstract void onBindViewHolder(VH holder, E entry, int position); abstract void onBindViewHolder(VH holder, E entry, int position);
public void setOnHistoryItemClickListener(@Nullable OnHistoryItemClickListener<E> onHistoryItemClickListener) { public void setOnHistoryItemClickListener(
@Nullable final OnHistoryItemClickListener<E> onHistoryItemClickListener) {
this.onHistoryItemClickListener = onHistoryItemClickListener; this.onHistoryItemClickListener = onHistoryItemClickListener;
} }
@ -100,6 +102,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
public interface OnHistoryItemClickListener<E> { public interface OnHistoryItemClickListener<E> {
void onHistoryItemClick(E item); void onHistoryItemClick(E item);
void onHistoryItemLongClick(E item); void onHistoryItemLongClick(E item);
} }
} }

View File

@ -1,34 +0,0 @@
package org.schabi.newpipe.local.history;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
public interface HistoryListener {
/**
* Called when a video is played
*
* @param streamInfo the stream info
* @param videoStream the video stream that is played. Can be null if it's not sure what
* quality was viewed (e.g. with Kodi).
*/
void onVideoPlayed(StreamInfo streamInfo, @Nullable VideoStream videoStream);
/**
* Called when the audio is played in the background
*
* @param streamInfo the stream info
* @param audioStream the audio stream that is played
*/
void onAudioPlayed(StreamInfo streamInfo, AudioStream audioStream);
/**
* Called when the user searched for something
*
* @param serviceId which service the search was done
* @param query what the user searched for
*/
void onSearch(int serviceId, String query);
}

View File

@ -21,6 +21,7 @@ package org.schabi.newpipe.local.history;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
@ -55,7 +56,6 @@ import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
public class HistoryRecordManager { public class HistoryRecordManager {
private final AppDatabase database; private final AppDatabase database;
private final StreamDAO streamTable; private final StreamDAO streamTable;
private final StreamHistoryDAO streamHistoryTable; private final StreamHistoryDAO streamHistoryTable;
@ -81,7 +81,9 @@ public class HistoryRecordManager {
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
public Maybe<Long> onViewed(final StreamInfo info) { public Maybe<Long> onViewed(final StreamInfo info) {
if (!isStreamHistoryEnabled()) return Maybe.empty(); if (!isStreamHistoryEnabled()) {
return Maybe.empty();
}
final Date currentTime = new Date(); final Date currentTime = new Date();
return Maybe.fromCallable(() -> database.runInTransaction(() -> { return Maybe.fromCallable(() -> database.runInTransaction(() -> {
@ -149,7 +151,9 @@ public class HistoryRecordManager {
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
public Maybe<Long> onSearched(final int serviceId, final String search) { public Maybe<Long> onSearched(final int serviceId, final String search) {
if (!isSearchHistoryEnabled()) return Maybe.empty(); if (!isSearchHistoryEnabled()) {
return Maybe.empty();
}
final Date currentTime = new Date(); final Date currentTime = new Date();
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search); final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
@ -231,11 +235,13 @@ public class HistoryRecordManager {
public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) { public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) {
return Single.fromCallable(() -> { return Single.fromCallable(() -> {
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst(); final List<StreamEntity> entities = streamTable
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
if (entities.isEmpty()) { if (entities.isEmpty()) {
return new StreamStateEntity[]{null}; return new StreamStateEntity[]{null};
} }
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst(); final List<StreamStateEntity> states = streamStateTable
.getState(entities.get(0).getUid()).blockingFirst();
if (states.isEmpty()) { if (states.isEmpty()) {
return new StreamStateEntity[]{null}; return new StreamStateEntity[]{null};
} }
@ -247,12 +253,14 @@ public class HistoryRecordManager {
return Single.fromCallable(() -> { return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(infos.size()); final List<StreamStateEntity> result = new ArrayList<>(infos.size());
for (InfoItem info : infos) { for (InfoItem info : infos) {
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst(); final List<StreamEntity> entities = streamTable
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
if (entities.isEmpty()) { if (entities.isEmpty()) {
result.add(null); result.add(null);
continue; continue;
} }
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst(); final List<StreamStateEntity> states = streamStateTable
.getState(entities.get(0).getUid()).blockingFirst();
if (states.isEmpty()) { if (states.isEmpty()) {
result.add(null); result.add(null);
continue; continue;
@ -263,7 +271,8 @@ public class HistoryRecordManager {
}).subscribeOn(Schedulers.io()); }).subscribeOn(Schedulers.io());
} }
public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(final List<? extends LocalItem> items) { public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(
final List<? extends LocalItem> items) {
return Single.fromCallable(() -> { return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(items.size()); final List<StreamStateEntity> result = new ArrayList<>(items.size());
for (LocalItem item : items) { for (LocalItem item : items) {
@ -278,7 +287,8 @@ public class HistoryRecordManager {
result.add(null); result.add(null);
continue; continue;
} }
final List<StreamStateEntity> states = streamStateTable.getState(streamId).blockingFirst(); final List<StreamStateEntity> states = streamStateTable.getState(streamId)
.blockingFirst();
if (states.isEmpty()) { if (states.isEmpty()) {
result.add(null); result.add(null);
continue; continue;

View File

@ -4,10 +4,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -18,6 +14,12 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.snackbar.Snackbar;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -48,7 +50,10 @@ import io.reactivex.disposables.Disposable;
public class StatisticsPlaylistFragment public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> { extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
private final CompositeDisposable disposables = new CompositeDisposable();
@State
Parcelable itemsListState;
private StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
private View headerPlayAllButton; private View headerPlayAllButton;
private View headerPopupButton; private View headerPopupButton;
private View headerBackgroundButton; private View headerBackgroundButton;
@ -56,55 +61,44 @@ public class StatisticsPlaylistFragment
private View sortButton; private View sortButton;
private ImageView sortButtonIcon; private ImageView sortButtonIcon;
private TextView sortButtonText; private TextView sortButtonText;
@State
protected Parcelable itemsListState;
/* Used for independent events */ /* Used for independent events */
private Subscription databaseSubscription; private Subscription databaseSubscription;
private HistoryRecordManager recordManager; private HistoryRecordManager recordManager;
private final CompositeDisposable disposables = new CompositeDisposable();
private enum StatisticSortMode { private List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
LAST_PLAYED,
MOST_PLAYED,
}
StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
protected List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
switch (sortMode) { switch (sortMode) {
case LAST_PLAYED: case LAST_PLAYED:
Collections.sort(results, (left, right) -> Collections.sort(results, (left, right) ->
right.getLatestAccessDate().compareTo(left.getLatestAccessDate())); right.getLatestAccessDate().compareTo(left.getLatestAccessDate()));
return results; return results;
case MOST_PLAYED: case MOST_PLAYED:
Collections.sort(results, (left, right) -> Collections.sort(results, (left, right) ->
Long.compare(right.getWatchCount(), left.getWatchCount())); Long.compare(right.getWatchCount(), left.getWatchCount()));
return results; return results;
default: return null; default:
return null;
} }
} }
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recordManager = new HistoryRecordManager(getContext());
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Creation // Fragment LifeCycle - Creation
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override @Override
public void onCreate(Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater,
super.onCreate(savedInstanceState); @Nullable final ViewGroup container,
recordManager = new HistoryRecordManager(getContext()); @Nullable final Bundle savedInstanceState) {
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_playlist, container, false); return inflater.inflate(R.layout.fragment_playlist, container, false);
} }
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
if (activity != null && isVisibleToUser) { if (activity != null && isVisibleToUser) {
setTitle(activity.getString(R.string.title_activity_history)); setTitle(activity.getString(R.string.title_activity_history));
@ -112,27 +106,27 @@ public class StatisticsPlaylistFragment
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_history, menu); inflater.inflate(R.menu.menu_history, menu);
} }
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
if (!useAsFrontPage) {
setTitle(getString(R.string.title_last_played));
}
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Views // Fragment LifeCycle - Views
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
if(!useAsFrontPage) {
setTitle(getString(R.string.title_last_played));
}
}
@Override @Override
protected View getListHeader() { protected View getListHeader() {
final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.statistic_playlist_control, final View headerRootLayout = activity.getLayoutInflater()
itemsList, false); .inflate(R.layout.statistic_playlist_control, itemsList, false);
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control); playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button); headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button); headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
@ -149,7 +143,7 @@ public class StatisticsPlaylistFragment
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() { itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override @Override
public void selected(LocalItem selectedItem) { public void selected(final LocalItem selectedItem) {
if (selectedItem instanceof StreamStatisticsEntry) { if (selectedItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem; final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
NavigationHelper.openVideoDetailFragment(getFM(), NavigationHelper.openVideoDetailFragment(getFM(),
@ -160,7 +154,7 @@ public class StatisticsPlaylistFragment
} }
@Override @Override
public void held(LocalItem selectedItem) { public void held(final LocalItem selectedItem) {
if (selectedItem instanceof StreamStatisticsEntry) { if (selectedItem instanceof StreamStatisticsEntry) {
showStreamDialog((StreamStatisticsEntry) selectedItem); showStreamDialog((StreamStatisticsEntry) selectedItem);
} }
@ -169,7 +163,7 @@ public class StatisticsPlaylistFragment
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_history_clear: case R.id.action_history_clear:
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
@ -194,7 +188,8 @@ public class StatisticsPlaylistFragment
final Disposable onClearOrphans = recordManager.removeOrphanedRecords() final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
howManyDeleted -> {}, howManyDeleted -> {
},
throwable -> ErrorActivity.reportError(getContext(), throwable -> ErrorActivity.reportError(getContext(),
throwable, throwable,
SettingsActivity.class, null, SettingsActivity.class, null,
@ -215,12 +210,8 @@ public class StatisticsPlaylistFragment
return true; return true;
} }
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
///////////////////////////////////////////////////////////////////////////
@Override @Override
public void startLoading(boolean forceLoad) { public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad); super.startLoading(forceLoad);
recordManager.getStreamStatistics() recordManager.getStreamStatistics()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -228,7 +219,7 @@ public class StatisticsPlaylistFragment
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Destruction // Fragment LifeCycle - Loading
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override @Override
@ -237,16 +228,30 @@ public class StatisticsPlaylistFragment
itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
} }
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Destruction
///////////////////////////////////////////////////////////////////////////
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if (itemListAdapter != null) itemListAdapter.unsetSelectedListener(); if (itemListAdapter != null) {
if (headerBackgroundButton != null) headerBackgroundButton.setOnClickListener(null); itemListAdapter.unsetSelectedListener();
if (headerPlayAllButton != null) headerPlayAllButton.setOnClickListener(null); }
if (headerPopupButton != null) headerPopupButton.setOnClickListener(null); if (headerBackgroundButton != null) {
headerBackgroundButton.setOnClickListener(null);
}
if (headerPlayAllButton != null) {
headerPlayAllButton.setOnClickListener(null);
}
if (headerPopupButton != null) {
headerPopupButton.setOnClickListener(null);
}
if (databaseSubscription != null) databaseSubscription.cancel(); if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = null; databaseSubscription = null;
} }
@ -257,29 +262,29 @@ public class StatisticsPlaylistFragment
itemsListState = null; itemsListState = null;
} }
///////////////////////////////////////////////////////////////////////////
// Statistics Loader
///////////////////////////////////////////////////////////////////////////
private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() { private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() {
return new Subscriber<List<StreamStatisticsEntry>>() { return new Subscriber<List<StreamStatisticsEntry>>() {
@Override @Override
public void onSubscribe(Subscription s) { public void onSubscribe(final Subscription s) {
showLoading(); showLoading();
if (databaseSubscription != null) databaseSubscription.cancel(); if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = s; databaseSubscription = s;
databaseSubscription.request(1); databaseSubscription.request(1);
} }
@Override @Override
public void onNext(List<StreamStatisticsEntry> streams) { public void onNext(final List<StreamStatisticsEntry> streams) {
handleResult(streams); handleResult(streams);
if (databaseSubscription != null) databaseSubscription.request(1); if (databaseSubscription != null) {
databaseSubscription.request(1);
}
} }
@Override @Override
public void onError(Throwable exception) { public void onError(final Throwable exception) {
StatisticsPlaylistFragment.this.onError(exception); StatisticsPlaylistFragment.this.onError(exception);
} }
@ -289,10 +294,16 @@ public class StatisticsPlaylistFragment
}; };
} }
///////////////////////////////////////////////////////////////////////////
// Statistics Loader
///////////////////////////////////////////////////////////////////////////
@Override @Override
public void handleResult(@NonNull List<StreamStatisticsEntry> result) { public void handleResult(@NonNull final List<StreamStatisticsEntry> result) {
super.handleResult(result); super.handleResult(result);
if (itemListAdapter == null) return; if (itemListAdapter == null) {
return;
}
playlistCtrl.setVisibility(View.VISIBLE); playlistCtrl.setVisibility(View.VISIBLE);
@ -319,52 +330,60 @@ public class StatisticsPlaylistFragment
hideLoading(); hideLoading();
} }
@Override
protected void resetFragment() {
super.resetFragment();
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Fragment Error Handling // Fragment Error Handling
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@Override @Override
protected void resetFragment() { protected boolean onError(final Throwable exception) {
super.resetFragment(); if (super.onError(exception)) {
if (databaseSubscription != null) databaseSubscription.cancel(); return true;
} }
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
"none", "History Statistics", R.string.general_error); "none", "History Statistics", R.string.general_error);
return true; return true;
} }
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void toggleSortMode() { private void toggleSortMode() {
if(sortMode == StatisticSortMode.LAST_PLAYED) { if (sortMode == StatisticSortMode.LAST_PLAYED) {
sortMode = StatisticSortMode.MOST_PLAYED; sortMode = StatisticSortMode.MOST_PLAYED;
setTitle(getString(R.string.title_most_played)); setTitle(getString(R.string.title_most_played));
sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext())); sortButtonIcon
.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
sortButtonText.setText(R.string.title_last_played); sortButtonText.setText(R.string.title_last_played);
} else { } else {
sortMode = StatisticSortMode.LAST_PLAYED; sortMode = StatisticSortMode.LAST_PLAYED;
setTitle(getString(R.string.title_last_played)); setTitle(getString(R.string.title_last_played));
sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext())); sortButtonIcon
.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
sortButtonText.setText(R.string.title_most_played); sortButtonText.setText(R.string.title_most_played);
} }
startLoading(true); startLoading(true);
} }
private PlayQueue getPlayQueueStartingAt(StreamStatisticsEntry infoItem) { /*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private PlayQueue getPlayQueueStartingAt(final StreamStatisticsEntry infoItem) {
return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0));
} }
private void showStreamDialog(final StreamStatisticsEntry item) { private void showStreamDialog(final StreamStatisticsEntry item) {
final Context context = getContext(); final Context context = getContext();
final Activity activity = getActivity(); final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) return; if (context == null || context.getResources() == null || activity == null) {
return;
}
final StreamInfoItem infoItem = item.toStreamInfoItem(); final StreamInfoItem infoItem = item.toStreamInfoItem();
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
@ -384,29 +403,31 @@ public class StatisticsPlaylistFragment
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share);
StreamDialogEntry.start_here_on_popup.setCustomAction( StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItemDuplicate) ->
(fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); NavigationHelper
.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
} }
StreamDialogEntry.start_here_on_background.setCustomAction( StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
(fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); NavigationHelper
.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) -> StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) ->
deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0))); deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0)));
new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) -> new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context),
StreamDialogEntry.clickOn(which, this, infoItem)).show(); (dialog, which) -> StreamDialogEntry.clickOn(which, this, infoItem)).show();
} }
private void deleteEntry(final int index) { private void deleteEntry(final int index) {
final LocalItem infoItem = itemListAdapter.getItemsList() final LocalItem infoItem = itemListAdapter.getItemsList()
.get(index); .get(index);
if(infoItem instanceof StreamStatisticsEntry) { if (infoItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem; final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem;
final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId()) final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
howManyDeleted -> { howManyDeleted -> {
if(getView() != null) { if (getView() != null) {
Snackbar.make(getView(), R.string.one_item_deleted, Snackbar.make(getView(), R.string.one_item_deleted,
Snackbar.LENGTH_SHORT).show(); Snackbar.LENGTH_SHORT).show();
} else { } else {
@ -441,5 +462,10 @@ public class StatisticsPlaylistFragment
} }
return new SinglePlayQueue(streamInfoItems, index); return new SinglePlayQueue(streamInfoItems, index);
} }
private enum StatisticSortMode {
LAST_PLAYED,
MOST_PLAYED,
}
} }

View File

@ -1,9 +1,10 @@
package org.schabi.newpipe.local.holder; package org.schabi.newpipe.local.holder;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -33,14 +34,15 @@ import java.text.DateFormat;
public abstract class LocalItemHolder extends RecyclerView.ViewHolder { public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
protected final LocalItemBuilder itemBuilder; protected final LocalItemBuilder itemBuilder;
public LocalItemHolder(LocalItemBuilder itemBuilder, int layoutId, ViewGroup parent) { public LocalItemHolder(final LocalItemBuilder itemBuilder, final int layoutId,
super(LayoutInflater.from(itemBuilder.getContext()) final ViewGroup parent) {
.inflate(layoutId, parent, false)); super(LayoutInflater.from(itemBuilder.getContext()).inflate(layoutId, parent, false));
this.itemBuilder = itemBuilder; this.itemBuilder = itemBuilder;
} }
public abstract void updateFromItem(final LocalItem item, HistoryRecordManager historyRecordManager, final DateFormat dateFormat); public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager,
DateFormat dateFormat);
public void updateState(final LocalItem localItem, HistoryRecordManager historyRecordManager) { public void updateState(final LocalItem localItem,
} final HistoryRecordManager historyRecordManager) { }
} }

View File

@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder { public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
public LocalPlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder,
public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
} }
} }

View File

@ -12,18 +12,22 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import java.text.DateFormat; import java.text.DateFormat;
public class LocalPlaylistItemHolder extends PlaylistItemHolder { public class LocalPlaylistItemHolder extends PlaylistItemHolder {
public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
public LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, parent); super(infoItemBuilder, parent);
} }
LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
} }
@Override @Override
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { public void updateFromItem(final LocalItem localItem,
if (!(localItem instanceof PlaylistMetadataEntry)) return; final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistMetadataEntry)) {
return;
}
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
itemTitleView.setText(item.name); itemTitleView.setText(item.name);

View File

@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder { public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder {
public LocalPlaylistStreamGridItemHolder(final LocalItemBuilder infoItemBuilder,
public LocalPlaylistStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); //TODO super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); // TODO
} }
} }

View File

@ -1,12 +1,13 @@
package org.schabi.newpipe.local.holder; package org.schabi.newpipe.local.holder;
import androidx.core.content.ContextCompat;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
@ -24,15 +25,15 @@ import java.util.ArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class LocalPlaylistStreamItemHolder extends LocalItemHolder { public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
public final ImageView itemThumbnailView; public final ImageView itemThumbnailView;
public final TextView itemVideoTitleView; public final TextView itemVideoTitleView;
public final TextView itemAdditionalDetailsView; private final TextView itemAdditionalDetailsView;
public final TextView itemDurationView; public final TextView itemDurationView;
public final View itemHandleView; private final View itemHandleView;
public final AnimatedProgressBar itemProgressView; private final AnimatedProgressBar itemProgressView;
LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent); super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -43,30 +44,41 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
itemProgressView = itemView.findViewById(R.id.itemProgressView); itemProgressView = itemView.findViewById(R.id.itemProgressView);
} }
public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { public LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder,
final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_stream_playlist_item, parent); this(infoItemBuilder, R.layout.list_stream_playlist_item, parent);
} }
@Override @Override
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { public void updateFromItem(final LocalItem localItem,
if (!(localItem instanceof PlaylistStreamEntry)) return; final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistStreamEntry)) {
return;
}
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
itemVideoTitleView.setText(item.getStreamEntity().getTitle()); itemVideoTitleView.setText(item.getStreamEntity().getTitle());
itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getStreamEntity().getUploader(), itemAdditionalDetailsView.setText(Localization
NewPipe.getNameOfService(item.getStreamEntity().getServiceId()))); .concatenateStrings(item.getStreamEntity().getUploader(),
NewPipe.getNameOfService(item.getStreamEntity().getServiceId())));
if (item.getStreamEntity().getDuration() > 0) { if (item.getStreamEntity().getDuration() > 0) {
itemDurationView.setText(Localization.getDurationString(item.getStreamEntity().getDuration())); itemDurationView.setText(Localization
.getDurationString(item.getStreamEntity().getDuration()));
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
R.color.duration_background_color)); R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE); itemDurationView.setVisibility(View.VISIBLE);
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0); StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null) { if (state != null) {
itemProgressView.setVisibility(View.VISIBLE); itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration()); itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
} else { } else {
itemProgressView.setVisibility(View.GONE); itemProgressView.setVisibility(View.GONE);
} }
@ -97,17 +109,25 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
} }
@Override @Override
public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) { public void updateState(final LocalItem localItem,
if (!(localItem instanceof PlaylistStreamEntry)) return; final HistoryRecordManager historyRecordManager) {
if (!(localItem instanceof PlaylistStreamEntry)) {
return;
}
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0); StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null && item.getStreamEntity().getDuration() > 0) { if (state != null && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration()); itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) { if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
} else { } else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500); AnimationUtils.animateView(itemProgressView, true, 500);
} }
} else if (itemProgressView.getVisibility() == View.VISIBLE) { } else if (itemProgressView.getVisibility() == View.VISIBLE) {
@ -118,8 +138,8 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) { private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) {
return (view, motionEvent) -> { return (view, motionEvent) -> {
view.performClick(); view.performClick();
if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null && if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
itemBuilder.getOnItemSelectedListener().drag(item, itemBuilder.getOnItemSelectedListener().drag(item,
LocalPlaylistStreamItemHolder.this); LocalPlaylistStreamItemHolder.this);
} }

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