1
0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2024-11-21 18:42:35 +01:00

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"
}
@get:Rule val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory());
@get:Rule
val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory())
@Test
fun migrateDatabaseFrom2to3() {
@ -72,7 +73,7 @@ class AppDatabaseTest {
}
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3);
true, Migrations.MIGRATION_2_3)
val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()

View File

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

View File

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

View File

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

View File

@ -10,15 +10,15 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
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 FlingBehavior(Context context, AttributeSet attrs) {
public FlingBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@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()) {
case MotionEvent.ACTION_DOWN:
// remove reference to old nested scrolling child
@ -35,7 +35,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
@Nullable
private OverScroller getScrollerField() {
try {
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass();
Class<?> headerBehaviorType = this.getClass()
.getSuperclass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("scroller");
field.setAccessible(true);
@ -62,12 +63,14 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
return null;
}
private void resetNestedScrollingChild(){
private void resetNestedScrollingChild() {
Field field = getLastNestedScrollingChildRefField();
if(field != null){
if (field != null) {
try {
Object value = field.get(this);
if(value != null) field.set(this, null);
if (value != null) {
field.set(this, null);
}
} catch (IllegalAccessException e) {
// ?
}
@ -76,7 +79,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
private void stopAppBarLayoutFling() {
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:
* 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 {
private static ActivityCommunicator activityCommunicator;
private volatile Class returnActivity;
public static ActivityCommunicator getCommunicator() {
if(activityCommunicator == null) {
if (activityCommunicator == null) {
activityCommunicator = new 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 {
protected static final String TAG = App.class.toString();
private RefWatcher refWatcher;
private static App app;
@SuppressWarnings("unchecked")
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
protected void attachBaseContext(Context base) {
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
initACRA();
@ -123,24 +132,30 @@ public class App extends Application {
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
"throwable = [" + throwable.getClass().getName() + "]");
public void accept(@NonNull final Throwable throwable) {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
+ "throwable = [" + throwable.getClass().getName() + "]");
final Throwable actualThrowable;
if (throwable instanceof UndeliverableException) {
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
throwable = throwable.getCause();
// As UndeliverableException is a wrapper,
// get the cause of it to get the "real" exception
actualThrowable = throwable.getCause();
} else {
actualThrowable = throwable;
}
final List<Throwable> errors;
if (throwable instanceof CompositeException) {
errors = ((CompositeException) throwable).getExceptions();
if (actualThrowable instanceof CompositeException) {
errors = ((CompositeException) actualThrowable).getExceptions();
} else {
errors = Collections.singletonList(throwable);
errors = Collections.singletonList(actualThrowable);
}
for (final Throwable error : errors) {
if (isThrowableIgnored(error)) return;
if (isThrowableIgnored(error)) {
return;
}
if (isThrowableCritical(error)) {
reportException(error);
return;
@ -150,17 +165,19 @@ public class App extends Application {
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
// When exception is not reported, log it
if (isDisposedRxExceptionsReported()) {
reportException(throwable);
reportException(actualThrowable);
} else {
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable);
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
}
}
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
// Don't crash the application over a simple network problem
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
IOException.class, SocketException.class, // network api cancellation
InterruptedException.class, InterruptedIOException.class); // blocking code disposed
// network api cancellation
IOException.class, SocketException.class,
// blocking code disposed
InterruptedException.class, InterruptedIOException.class);
}
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
@ -191,7 +208,7 @@ public class App extends Application {
private void initACRA() {
try {
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
.setReportSenderFactoryClasses(reportSenderFactoryClasses)
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class)
.build();
ACRA.init(this, acraConfig);
@ -202,7 +219,7 @@ public class App extends Application {
null,
null,
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.
*
* @param importance
*/
@TargetApi(Build.VERSION_CODES.O)
private void setUpUpdateNotificationChannel(int importance) {
private void setUpUpdateNotificationChannel(final int importance) {
final String appUpdateId
= getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName
@ -251,12 +268,6 @@ public class App extends Application {
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}
@Nullable
public static RefWatcher getRefWatcher(Context context) {
final App application = (App) context.getApplicationContext();
return application.refWatcher;
}
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
@ -264,8 +275,4 @@ public class App extends Application {
protected boolean isDisposedRxExceptionsReported() {
return false;
}
public static App getApp() {
return app;
}
}

View File

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

View File

@ -12,9 +12,10 @@ import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
@ -42,62 +43,137 @@ import okhttp3.Response;
* the notification, the user will be directed to the download link.
*/
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
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 newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
private static final int timeoutPeriod = 30;
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 NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
private static final int TIMEOUT_PERIOD = 30;
private SharedPreferences mPrefs;
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
protected void onPreExecute() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
mPrefs = PreferenceManager.getDefaultSharedPreferences(APP);
// Check if user has enabled/ disabled update checking
// and if the current apk is a github one or not.
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk()) {
if (!mPrefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
this.cancel(true);
}
}
@Override
protected String doInBackground(Void... voids) {
if(isCancelled() || !isConnected()) return null;
// Make a network request to get latest NewPipe data.
if (client == null) {
client = new OkHttpClient
.Builder()
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
.build();
protected String doInBackground(final Void... voids) {
if (isCancelled() || !isConnected()) {
return null;
}
Request request = new Request.Builder()
.url(newPipeApiUrl)
.build();
// Make a network request to get latest NewPipe data.
// FIXME: Use DownloaderImp
if (client == null) {
client = new OkHttpClient.Builder()
.readTimeout(TIMEOUT_PERIOD, TimeUnit.SECONDS).build();
}
Request request = new Request.Builder().url(NEWPIPE_API_URL).build();
try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (IOException ex) {
// 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;
}
@Override
protected void onPostExecute(String response) {
protected void onPostExecute(final String response) {
// Parse the json from the response.
if (response != null) {
@ -115,7 +191,9 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
} catch (JSONException ex) {
// 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.
* 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,
String apkLocationUrl,
String versionCode) {
int NOTIFICATION_ID = 2000;
private void compareAppVersionAndShowNotification(final String versionName,
final String apkLocationUrl,
final String versionCode) {
int notificationId = 2000;
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
// A pending intent to open the apk location url in the browser.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
PendingIntent pendingIntent
= PendingIntent.getActivity(app, 0, intent, 0);
= PendingIntent.getActivity(APP, 0, intent, 0);
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)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
.setContentText(app.getString(R.string.app_update_notification_content_text)
.setContentTitle(APP.getString(R.string.app_update_notification_content_title))
.setContentText(APP.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(APP);
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() {
ConnectivityManager cm =
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
ConnectivityManager cm =
(ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE);
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.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
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.X509TrustManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
@ -37,20 +37,22 @@ import okhttp3.ResponseBody;
import static org.schabi.newpipe.MainActivity.DEBUG;
public 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 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";
private static DownloaderImpl instance;
private String mCookies;
private OkHttpClient client;
private DownloaderImpl(OkHttpClient.Builder builder) {
private DownloaderImpl(final OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
enableModernTLS(builder);
}
this.client = builder
.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();
}
@ -58,20 +60,72 @@ public class DownloaderImpl extends Downloader {
* It's recommended to call exactly once in the entire lifetime of the application.
*
* @param builder if null, default builder will be used
* @return a new instance of {@link DownloaderImpl}
*/
public static DownloaderImpl init(@Nullable OkHttpClient.Builder builder) {
return instance = new DownloaderImpl(builder != null ? builder : new OkHttpClient.Builder());
public static DownloaderImpl init(@Nullable final OkHttpClient.Builder builder) {
instance = new DownloaderImpl(
builder != null ? builder : new OkHttpClient.Builder());
return instance;
}
public static DownloaderImpl getInstance() {
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() {
return mCookies;
}
public void setCookies(String cookies) {
public void setCookies(final String cookies) {
mCookies = cookies;
}
@ -81,7 +135,7 @@ public class DownloaderImpl extends Downloader {
* @param url an url pointing to the content
* @return the size of the content, in bytes
*/
public long getContentLength(String url) throws IOException {
public long getContentLength(final String url) throws IOException {
try {
final Response response = head(url);
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 {
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method("GET", null).url(siteUrl)
@ -122,7 +176,8 @@ public class DownloaderImpl extends Downloader {
}
@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 url = request.url();
final Map<String, List<String>> headers = request.headers();
@ -172,49 +227,7 @@ public class DownloaderImpl extends Downloader {
}
final String latestUrl = response.request().url().toString();
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();
}
return new Response(response.code(), response.message(), response.headers().toMultimap(),
responseBodyToReturn, latestUrl);
}
}

View File

@ -1,4 +1,3 @@
package org.schabi.newpipe;
import android.annotation.SuppressLint;
@ -27,9 +26,20 @@ import android.os.Bundle;
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")
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 21) {
@ -40,15 +50,4 @@ public class ExitActivity extends Activity {
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 String downloadThumbnailKey;
public ImageDownloader(Context context) {
public ImageDownloader(final Context context) {
super(context);
this.resources = context.getResources();
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
@ -31,7 +31,7 @@ public class ImageDownloader extends BaseImageDownloader {
@SuppressLint("ResourceType")
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
public InputStream getStream(final String imageUri, final Object extra) throws IOException {
if (isDownloadingThumbnail()) {
return super.getStream(imageUri, extra);
} 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();
return downloader.stream(imageUri);
}

View File

@ -93,11 +93,11 @@ public class MainActivity extends AppCompatActivity {
private boolean servicesShown = false;
private ImageView serviceArrow;
private static final int ITEM_ID_SUBSCRIPTIONS = - 1;
private static final int ITEM_ID_FEED = - 2;
private static final int ITEM_ID_BOOKMARKS = - 3;
private static final int ITEM_ID_DOWNLOADS = - 4;
private static final int ITEM_ID_HISTORY = - 5;
private static final int ITEM_ID_SUBSCRIPTIONS = -1;
private static final int ITEM_ID_FEED = -2;
private static final int ITEM_ID_BOOKMARKS = -3;
private static final int ITEM_ID_DOWNLOADS = -4;
private static final int ITEM_ID_HISTORY = -5;
private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1;
@ -108,8 +108,11 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
protected void onCreate(final Bundle 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
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) {
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();
}
@ -151,13 +156,15 @@ public class MainActivity extends AppCompatActivity {
for (final String ks : service.getKioskList().getAvailableKiosks()) {
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));
kioskId ++;
kioskId++;
}
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));
drawerItems.getMenu()
.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)
.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();
drawer.addDrawerListener(toggle);
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
private int lastService;
@Override
public void onDrawerOpened(View drawerView) {
public void onDrawerOpened(final View drawerView) {
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
}
@Override
public void onDrawerClosed(View drawerView) {
if(servicesShown) {
public void onDrawerClosed(final View drawerView) {
if (servicesShown) {
toggleServices();
}
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
@ -206,7 +214,7 @@ public class MainActivity extends AppCompatActivity {
setupDrawerHeader();
}
private boolean drawerItemSelected(MenuItem item) {
private boolean drawerItemSelected(final MenuItem item) {
switch (item.getGroupId()) {
case R.id.menu_services_group:
changeService(item);
@ -229,14 +237,16 @@ public class MainActivity extends AppCompatActivity {
return true;
}
private void changeService(MenuItem item) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
private void changeService(final MenuItem item) {
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(false);
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 {
switch(item.getItemId()) {
private void tabSelected(final MenuItem item) throws ExtractionException {
switch (item.getItemId()) {
case ITEM_ID_SUBSCRIPTIONS:
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
break;
@ -259,19 +269,20 @@ public class MainActivity extends AppCompatActivity {
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
if(kioskId == item.getItemId()) {
if (kioskId == item.getItemId()) {
serviceName = ks;
}
kioskId ++;
kioskId++;
}
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName);
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId,
serviceName);
break;
}
}
private void optionsAboutSelected(MenuItem item) {
switch(item.getItemId()) {
private void optionsAboutSelected(final MenuItem item) {
switch (item.getItemId()) {
case ITEM_ID_SETTINGS:
NavigationHelper.openSettings(this);
break;
@ -283,7 +294,7 @@ public class MainActivity extends AppCompatActivity {
private void setupDrawerHeader() {
NavigationView navigationView = findViewById(R.id.navigation);
View hView = navigationView.getHeaderView(0);
View hView = navigationView.getHeaderView(0);
serviceArrow = hView.findViewById(R.id.drawer_arrow);
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_options_about_group);
if(servicesShown) {
if (servicesShown) {
showServices();
} else {
try {
@ -313,55 +324,62 @@ public class MainActivity extends AppCompatActivity {
private void showServices() {
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
for(StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : "");
for (StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName()
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
MenuItem menuItem = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
// peertube specifics
if(s.getServiceId() == 3){
if (s.getServiceId() == 3) {
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();
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<String> items = new ArrayList<>();
int defaultSelect = 0;
for(PeertubeInstance instance: instances){
for (PeertubeInstance instance : instances) {
items.add(instance.getName());
if(instance.getUrl().equals(currentInstace.getUrl())){
defaultSelect = items.size()-1;
if (instance.getUrl().equals(currentInstace.getUrl())) {
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);
spinner.setAdapter(adapter);
spinner.setSelection(defaultSelect, false);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@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);
if(newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) return;
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
return;
}
PeertubeHelper.selectInstance(newInstance, getApplicationContext());
changeService(menuItem);
drawer.closeDrawers();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
getSupportFragmentManager().popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
recreate();
}, 300);
}
@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()) {
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));
kioskId ++;
kioskId++;
}
drawerItems.getMenu()
@ -420,15 +439,17 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
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();
// close drawer on return, and don't show animation, so its looks like the drawer isn't open
// when the user returns to MainActivity
// Close drawer on return, and don't show animation,
// so it looks like the drawer isn't open when the user returns to MainActivity
drawer.closeDrawer(GravityCompat.START, false);
try {
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);
headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
@ -441,15 +462,20 @@ public class MainActivity extends AppCompatActivity {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
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();
// https://stackoverflow.com/questions/10844112/runtimeexception-performing-pause-of-activity-that-is-not-resumed
// Briefly, let the activity resume properly posting the recreate call to end of the message queue
// https://stackoverflow.com/questions/10844112/
// Briefly, let the activity resume
// properly posting the recreate call to end of the message queue
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
}
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();
NavigationHelper.openMainActivity(this);
}
@ -460,13 +486,18 @@ public class MainActivity extends AppCompatActivity {
}
@Override
protected void onNewIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
protected void onNewIntent(final Intent intent) {
if (DEBUG) {
Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
}
if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack
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);
@ -476,24 +507,32 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onBackPressed() {
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;
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;
}
}
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
finish();
} else super.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (int i: grantResults){
if (i == PackageManager.PERMISSION_DENIED){
public void onRequestPermissionsResult(final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults) {
for (int i : grantResults) {
if (i == PackageManager.PERMISSION_DENIED) {
return;
}
}
@ -502,7 +541,8 @@ public class MainActivity extends AppCompatActivity {
NavigationHelper.openDownloads(this);
break;
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) {
((VideoDetailFragment) fragment).openDownloadDialog();
}
@ -547,8 +587,10 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
public boolean onCreateOptionsMenu(final Menu menu) {
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
}
super.onCreateOptionsMenu(menu);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
@ -557,8 +599,8 @@ public class MainActivity extends AppCompatActivity {
}
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();
@ -572,8 +614,10 @@ public class MainActivity extends AppCompatActivity {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
public boolean onOptionsItemSelected(final MenuItem item) {
if (DEBUG) {
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
}
int id = item.getItemId();
switch (id) {
@ -590,11 +634,15 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/
private void initFragments() {
if (DEBUG) Log.d(TAG, "initFragments() called");
if (DEBUG) {
Log.d(TAG, "initFragments() called");
}
StateSaver.clearStateFiles();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
handleIntent(getIntent());
} else NavigationHelper.gotoMainFragment(getSupportFragmentManager());
} else {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
}
/*//////////////////////////////////////////////////////////////////////////
@ -602,12 +650,14 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/
private void updateDrawerNavigation() {
if (getSupportActionBar() == null) return;
if (getSupportActionBar() == null) {
return;
}
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) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
if (toggle != null) {
@ -622,26 +672,23 @@ public class MainActivity extends AppCompatActivity {
}
}
private void updateDrawerHeaderString(String content) {
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) {
private void handleIntent(final Intent intent) {
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)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
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:
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
boolean autoPlay = intent
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
serviceId, url, title, autoPlay);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
@ -658,7 +705,9 @@ public class MainActivity extends AppCompatActivity {
}
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
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);
NavigationHelper.openSearchFragment(
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;
public final class NewPipeDatabase {
private static volatile AppDatabase databaseInstance;
private NewPipeDatabase() {
//no instance
}
private static AppDatabase getDatabase(Context context) {
private static AppDatabase getDatabase(final Context context) {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
@ -28,13 +27,14 @@ public final class NewPipeDatabase {
}
@NonNull
public static AppDatabase getInstance(@NonNull Context context) {
public static AppDatabase getInstance(@NonNull final Context context) {
AppDatabase result = databaseInstance;
if (result == null) {
synchronized (NewPipeDatabase.class) {
result = databaseInstance;
if (result == null) {
databaseInstance = (result = getDatabase(context));
databaseInstance = getDatabase(context);
result = databaseInstance;
}
}
}

View File

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

View File

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

View File

@ -54,6 +54,8 @@ import org.schabi.newpipe.util.urlfinder.UrlFinder;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import icepick.Icepick;
@ -71,29 +73,31 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCap
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 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
protected int currentServiceId = -1;
private StreamingService currentService;
@State
protected LinkType currentLinkType;
@State
protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1;
protected String currentUrl;
protected boolean internalRoute = false;
protected final CompositeDisposable disposables = new CompositeDisposable();
private StreamingService currentService;
private boolean selectionIsDownload = false;
public static final String internalRouteKey = "internalRoute";
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(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)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
@ -132,7 +136,7 @@ public class RouterActivity extends AppCompatActivity {
disposables.clear();
}
private void handleUrl(String url) {
private void handleUrl(final String url) {
disposables.add(Observable
.fromCallable(() -> {
if (currentServiceId == -1) {
@ -157,13 +161,14 @@ public class RouterActivity extends AppCompatActivity {
}, this::handleError));
}
private void handleError(Throwable error) {
private void handleError(final Throwable error) {
error.printStackTrace();
if (error instanceof ExtractionException) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
} else {
ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null);
ExtractorHelper.handleGeneralException(this, -1, null, error,
UserAction.SOMETHING_ELSE, null);
}
finish();
@ -175,8 +180,11 @@ public class RouterActivity extends AppCompatActivity {
}
protected void onSuccess() {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final String selectedChoiceKey = preferences.getString(getString(R.string.preferred_open_action_key), getString(R.string.preferred_open_action_default));
final SharedPreferences preferences = PreferenceManager
.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 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);
if (selectedChoiceKey.equals(alwaysAskKey)) {
final List<AdapterChoiceItem> choices = getChoicesForService(currentService, currentLinkType);
final List<AdapterChoiceItem> choices
= getChoicesForService(currentService, currentLinkType);
switch (choices.size()) {
case 1:
@ -204,20 +213,26 @@ public class RouterActivity extends AppCompatActivity {
} else if (selectedChoiceKey.equals(downloadKey)) {
handleChoice(downloadKey);
} else {
final boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
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 isExtVideoEnabled = preferences.getBoolean(
getString(R.string.use_external_video_player_key), false);
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);
if (currentLinkType != LinkType.STREAM) {
if (isExtAudioEnabled && isAudioPlayerSelected || isExtVideoEnabled && isVideoPlayerSelected) {
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
if (isExtAudioEnabled && isAudioPlayerSelected
|| isExtVideoEnabled && isVideoPlayerSelected) {
Toast.makeText(this, R.string.external_player_unsupported_link_type,
Toast.LENGTH_LONG).show();
handleChoice(showInfoKey);
return;
}
}
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = currentService.getServiceInfo().getMediaCapabilities();
final List<StreamingService.ServiceInfo.MediaCapability> capabilities
= currentService.getServiceInfo().getMediaCapabilities();
boolean serviceSupportsChoice = false;
if (isVideoPlayerSelected) {
@ -239,7 +254,8 @@ public class RouterActivity extends AppCompatActivity {
final Context themeWrapperContext = getThemeWrapperContext();
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 DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
@ -250,7 +266,9 @@ public class RouterActivity extends AppCompatActivity {
handleChoice(choice.key);
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)
.setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> {
if (!selectionIsDownload) finish();
if (!selectionIsDownload) {
finish();
}
})
.create();
@ -270,10 +290,13 @@ public class RouterActivity extends AppCompatActivity {
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 int indexOfChild = radioGroup.indexOfChild(v);
if (indexOfChild == -1) return;
if (indexOfChild == -1) {
return;
}
selectedPreviously = selectedRadioPosition;
selectedRadioPosition = indexOfChild;
@ -285,18 +308,21 @@ public class RouterActivity extends AppCompatActivity {
int id = 12345;
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.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setChecked(false);
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);
radioGroup.addView(radioButton);
}
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)) {
for (int i = 0; i < choices.size(); i++) {
AdapterChoiceItem c = choices.get(i);
@ -317,46 +343,58 @@ public class RouterActivity extends AppCompatActivity {
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 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);
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);
final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
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)));
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)));
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)));
}
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)));
}
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)));
return returnList;
}
private Context getThemeWrapperContext() {
return new ContextThemeWrapper(this,
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
? 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 positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (negativeButton == null || positiveButton == null) return;
if (negativeButton == null || positiveButton == null) {
return;
}
negativeButton.setEnabled(state);
positiveButton.setEnabled(state);
@ -372,21 +410,25 @@ public class RouterActivity extends AppCompatActivity {
}
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)) {
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();
}
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);
finish();
return;
}
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;
openDownloadDialog();
}
@ -414,7 +456,8 @@ public class RouterActivity extends AppCompatActivity {
}
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);
startService(intent);
@ -427,12 +470,11 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
List<VideoStream> sortedVideoStreams = ListHelper.getSortedStreamVideosList(this,
result.getVideoStreams(),
result.getVideoOnlyStreams(),
false);
int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this,
sortedVideoStreams);
List<VideoStream> sortedVideoStreams = ListHelper
.getSortedStreamVideosList(this, result.getVideoStreams(),
result.getVideoOnlyStreams(), false);
int selectedVideoStreamIndex = ListHelper
.getDefaultResolutionIndex(this, sortedVideoStreams);
FragmentManager fm = getSupportFragmentManager();
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
@ -450,7 +492,9 @@ public class RouterActivity extends AppCompatActivity {
}
@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) {
if (i == PackageManager.PERMISSION_DENIED) {
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 {
final String description, key;
final String description;
final String key;
@DrawableRes
final int icon;
AdapterChoiceItem(String key, String description, int icon) {
AdapterChoiceItem(final String key, final String description, final int icon) {
this.description = description;
this.key = key;
this.icon = icon;
@ -476,10 +581,12 @@ public class RouterActivity extends AppCompatActivity {
private static class Choice implements Serializable {
final int serviceId;
final String url, playerChoice;
final String url;
final String playerChoice;
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.linkType = linkType;
this.url = url;
@ -492,14 +599,10 @@ public class RouterActivity extends AppCompatActivity {
}
}
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
public static class FetcherService extends IntentService {
private static final int ID = 456;
public static final String KEY_CHOICE = "key_choice";
private static final int ID = 456;
private Disposable fetcher;
public FetcherService() {
@ -513,16 +616,20 @@ public class RouterActivity extends AppCompatActivity {
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent == null) return;
protected void onHandleIntent(@Nullable final Intent intent) {
if (intent == null) {
return;
}
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
if (!(serializable instanceof Choice)) return;
if (!(serializable instanceof Choice)) {
return;
}
Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice);
}
public void handleChoice(Choice choice) {
public void handleChoice(final Choice choice) {
Single<? extends Info> single = null;
UserAction userAction = UserAction.SOMETHING_ELSE;
@ -549,22 +656,27 @@ public class RouterActivity extends AppCompatActivity {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
resultHandler.accept(info);
if (fetcher != null) fetcher.dispose();
if (fetcher != null) {
fetcher.dispose();
}
}, 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 -> {
final String videoPlayerKey = getString(R.string.video_player_key);
final String backgroundPlayerKey = getString(R.string.background_player_key);
final String popupPlayerKey = getString(R.string.popup_player_key);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
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);
;
final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
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;
String playerChoice = choice.playerChoice;
@ -590,7 +702,9 @@ public class RouterActivity extends AppCompatActivity {
}
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)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true);
@ -607,7 +721,9 @@ public class RouterActivity extends AppCompatActivity {
public void onDestroy() {
super.onDestroy();
stopForeground(true);
if (fetcher != null) fetcher.dispose();
if (fetcher != null) {
fetcher.dispose();
}
}
private NotificationCompat.Builder createNotification() {
@ -615,8 +731,10 @@ public class RouterActivity extends AppCompatActivity {
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(getString(R.string.preferred_player_fetcher_notification_title))
.setContentText(getString(R.string.preferred_player_fetcher_notification_message));
.setContentTitle(
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
private String getUrl(Intent intent) {
private String getUrl(final Intent intent) {
String foundUrl = null;
if (intent.getData() != null) {
// Called from another app

View File

@ -4,21 +4,22 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
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.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
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 com.google.android.material.tabs.TabLayout;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
@ -27,26 +28,41 @@ import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class AboutActivity extends AppCompatActivity {
/**
* List of all software components
* List of all software components.
*/
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("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT),
new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof", "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)
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
"https://github.com/jhy/jsoup", StandardLicenses.MIT),
new SoftwareComponent("Rhino", "2015", "Mozilla",
"https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
"http://www.acra.ch", StandardLicenses.APACHE2),
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
"https://github.com/nostra13/Android-Universal-Image-Loader",
StandardLicenses.APACHE2),
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
"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;
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
@ -88,10 +104,8 @@ public class AboutActivity extends AppCompatActivity {
tabLayout.setupWithViewPager(mViewPager);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
switch (id) {
@ -107,21 +121,20 @@ public class AboutActivity extends AppCompatActivity {
* A placeholder fragment containing a simple view.
*/
public static class AboutFragment extends Fragment {
public AboutFragment() {
}
public AboutFragment() { }
/**
* Returns a new instance of this fragment for the given section
* number.
* Created a new instance of this fragment for the given section number.
*
* @return New instance of {@link AboutFragment}
*/
public static AboutFragment newInstance() {
return new AboutFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
Context context = this.getContext();
@ -129,40 +142,42 @@ public class AboutActivity extends AppCompatActivity {
version.setText(BuildConfig.VERSION_NAME);
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);
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);
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);
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;
}
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));
context.startActivity(intent);
}
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
public SectionsPagerAdapter(final FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
public Fragment getItem(final int position) {
switch (position) {
case 0:
return AboutFragment.newInstance();
@ -179,7 +194,7 @@ public class AboutActivity extends AppCompatActivity {
}
@Override
public CharSequence getPageTitle(int position) {
public CharSequence getPageTitle(final int position) {
switch (position) {
case 0:
return getString(R.string.tab_about);

View File

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

View File

@ -5,26 +5,32 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
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.fragment.app.Fragment;
import android.view.*;
import android.widget.TextView;
import org.schabi.newpipe.R;
import java.util.Arrays;
import java.util.Comparator;
/**
* Fragment containing the software licenses
* Fragment containing the software licenses.
*/
public class LicenseFragment extends Fragment {
private static final String ARG_COMPONENTS = "components";
private SoftwareComponent[] softwareComponents;
private SoftwareComponent mComponentForContextMenu;
public static LicenseFragment newInstance(SoftwareComponent[] softwareComponents) {
if(softwareComponents == null) {
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
if (softwareComponents == null) {
throw new NullPointerException("softwareComponents is null");
}
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 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);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
softwareComponents = (SoftwareComponent[]) getArguments().getParcelableArray(ARG_COMPONENTS);
softwareComponents = (SoftwareComponent[]) getArguments()
.getParcelableArray(ARG_COMPONENTS);
// Sort components by name
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
@Override
public int compare(SoftwareComponent o1, SoftwareComponent o2) {
public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
return o1.getName().compareTo(o2.getName());
}
});
@ -59,7 +67,8 @@ public class LicenseFragment extends Fragment {
@Nullable
@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);
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
@ -67,7 +76,8 @@ public class LicenseFragment extends Fragment {
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
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 copyright = componentView.findViewById(R.id.copyright);
softwareName.setText(component.getName());
@ -79,7 +89,7 @@ public class LicenseFragment extends Fragment {
componentView.setTag(component);
componentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
public void onClick(final View v) {
Context context = v.getContext();
if (context != null) {
showLicense(context, component.getLicense());
@ -93,7 +103,8 @@ public class LicenseFragment extends Fragment {
}
@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();
SoftwareComponent component = (SoftwareComponent) v.getTag();
menu.setHeaderTitle(component.getName());
@ -103,7 +114,7 @@ public class LicenseFragment extends Fragment {
}
@Override
public boolean onContextItemSelected(MenuItem item) {
public boolean onContextItemSelected(final MenuItem item) {
// item.getMenuInfo() is null so we use the tag of the view
final SoftwareComponent component = mComponentForContextMenu;
if (component == null) {
@ -119,14 +130,14 @@ public class LicenseFragment extends Fragment {
return false;
}
private void openWebsite(String componentLink) {
private void openWebsite(final String componentLink) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
startActivity(browserIntent);
}
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
public void onClick(final View v) {
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
}
}

View File

@ -2,30 +2,103 @@ package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.webkit.WebView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import android.webkit.WebView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
final WeakReference<Activity> weakReference;
private final WeakReference<Activity> weakReference;
private License license;
public LicenseFragmentHelper(@Nullable Activity activity) {
public LicenseFragmentHelper(@Nullable final Activity 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
private Activity getActivity() {
Activity activity = weakReference.get();
@ -38,13 +111,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
}
@Override
protected Integer doInBackground(Object... objects) {
protected Integer doInBackground(final Object... objects) {
license = (License) objects[0];
return 1;
}
@Override
protected void onPostExecute(Integer result) {
protected void onPostExecute(final Integer result) {
Activity activity = getActivity();
if (activity == null) {
return;
@ -63,74 +136,4 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
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;
public class SoftwareComponent implements Parcelable {
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
@Override
public SoftwareComponent createFromParcel(Parcel source) {
public SoftwareComponent createFromParcel(final Parcel source) {
return new SoftwareComponent(source);
}
@Override
public SoftwareComponent[] newArray(int size) {
public SoftwareComponent[] newArray(final int size) {
return new SoftwareComponent[size];
}
};
private final License license;
private final String name;
private final String years;
private final String copyrightOwner;
private final String link;
private final String version;
public SoftwareComponent(final String name, final String years, final String copyrightOwner,
final String link, final License license) {
this.name = name;
this.years = years;
this.copyrightOwner = copyrightOwner;
this.link = link;
this.license = license;
this.version = null;
}
protected SoftwareComponent(final Parcel in) {
this.name = in.readString();
this.license = in.readParcelable(License.class.getClassLoader());
this.copyrightOwner = in.readString();
this.link = in.readString();
this.years = in.readString();
this.version = in.readString();
}
public String getName() {
return name;
}
@ -37,31 +62,6 @@ public class SoftwareComponent implements Parcelable {
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() {
return license;
}
@ -72,7 +72,7 @@ public class SoftwareComponent implements Parcelable {
}
@Override
public void writeToParcel(Parcel dest, int flags) {
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(name);
dest.writeParcelable(license, flags);
dest.writeString(copyrightOwner);

View File

@ -1,12 +1,19 @@
package org.schabi.newpipe.about;
/**
* Standard software licenses
* Class containing information about standard software licenses.
*/
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 GPL3 = new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.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");
public static final License GPL2
= new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html");
public static final License GPL3
= new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.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 StreamDAO streamDAO();
public abstract StreamHistoryDAO streamHistoryDAO();
public abstract StreamStateDAO streamStateDAO();
public abstract PlaylistDAO playlistDAO();
public abstract PlaylistStreamDAO playlistStreamDAO();
public abstract PlaylistRemoteDAO playlistRemoteDAO();
public abstract FeedDAO feedDAO();
public abstract FeedGroupDAO feedGroupDAO();
public abstract SubscriptionDAO subscriptionDAO();
}

View File

@ -15,13 +15,13 @@ import io.reactivex.Flowable;
public interface BasicDAO<Entity> {
/* Inserts */
@Insert(onConflict = OnConflictStrategy.FAIL)
long insert(final Entity entity);
long insert(Entity entity);
@Insert(onConflict = OnConflictStrategy.FAIL)
List<Long> insertAll(final Entity... entities);
List<Long> insertAll(Entity... entities);
@Insert(onConflict = OnConflictStrategy.FAIL)
List<Long> insertAll(final Collection<Entity> entities);
List<Long> insertAll(Collection<Entity> entities);
/* Searches */
Flowable<List<Entity>> getAll();
@ -30,17 +30,17 @@ public interface BasicDAO<Entity> {
/* Deletes */
@Delete
void delete(final Entity entity);
void delete(Entity entity);
@Delete
int delete(final Collection<Entity> entities);
int delete(Collection<Entity> entities);
int deleteAll();
/* Updates */
@Update
int update(final Entity entity);
int update(Entity entity);
@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;
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
* @return the date
*/
@TypeConverter
public static Date fromTimestamp(Long value) {
public static Date fromTimestamp(final Long 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
* @return the long value
*/
@TypeConverter
public static Long dateToTimestamp(Date date) {
public static Long dateToTimestamp(final Date date) {
return date == null ? null : date.getTime();
}
@TypeConverter
public static StreamType streamTypeOf(String value) {
public static StreamType streamTypeOf(final String value) {
return StreamType.valueOf(value);
}
@TypeConverter
public static String stringOf(StreamType streamType) {
public static String stringOf(final StreamType streamType) {
return streamType.name();
}
@TypeConverter
public static Integer integerOf(FeedGroupIcon feedGroupIcon) {
public static Integer integerOf(final FeedGroupIcon feedGroupIcon) {
return feedGroupIcon.getId();
}
@TypeConverter
public static FeedGroupIcon feedGroupIconOf(Integer id) {
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
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 + "\"");

View File

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

View File

@ -1,72 +1,103 @@
package org.schabi.newpipe.database;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.room.migration.Migration;
import androidx.annotation.NonNull;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
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_2 = 2;
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();
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) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
if(DEBUG) {
public void migrate(@NonNull final SupportSQLiteDatabase database) {
if (DEBUG) {
Log.d(TAG, "Start migrating database");
}
/*
* Unfortunately these queries must be hardcoded due to the possibility of
* schema and names changing at a later date, thus invalidating the older migration
* scripts if they are not hardcoded.
* */
* Unfortunately these queries must be hardcoded due to the possibility of
* schema and names changing at a later date, thus invalidating the older migration
* scripts if they are not hardcoded.
* */
// 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.
database.execSQL("CREATE INDEX `index_search_history_search` ON `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)");
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_search_history_search` "
+ "ON `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)");
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 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 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`)");
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 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
// Latest data first, thus ignoring older entries with the same indices
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, " +
"stream_type, duration, uploader, thumbnail_url) " +
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, "
+ "stream_type, duration, uploader, thumbnail_url) "
"SELECT service_id, url, title, 'VIDEO_STREAM', duration, " +
"uploader, thumbnail_url " +
+ "SELECT service_id, url, title, 'VIDEO_STREAM', duration, "
+ "uploader, thumbnail_url "
"FROM watch_history " +
"ORDER BY creation_date DESC");
+ "FROM watch_history "
+ "ORDER BY creation_date DESC");
// Once the streams have PKs, join them with the normalized history table
// and populate it with the remaining data from watch history
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" +
"SELECT uid, creation_date, 1 " +
"FROM watch_history INNER JOIN streams " +
"ON watch_history.service_id == streams.service_id " +
"AND watch_history.url == streams.url " +
"ORDER BY creation_date DESC");
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)"
+ "SELECT uid, creation_date, 1 "
+ "FROM watch_history INNER JOIN streams "
+ "ON watch_history.service_id == streams.service_id "
+ "AND watch_history.url == streams.url "
+ "ORDER BY creation_date DESC");
database.execSQL("DROP TABLE IF EXISTS watch_history");
if(DEBUG) {
if (DEBUG) {
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) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
public void migrate(@NonNull final SupportSQLiteDatabase database) {
// Add NOT NULLs and new fields
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," +
" 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("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, 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," +
"duration, uploader, thumbnail_url, view_count," +
"textual_upload_date, upload_date, is_upload_date_approximation) " +
database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type, "
+ "duration, uploader, thumbnail_url, view_count, textual_upload_date, "
+ "upload_date, is_upload_date_approximation) "
"SELECT uid, service_id, url, ifnull(title, ''), ifnull(stream_type, 'VIDEO_STREAM')," +
"ifnull(duration, 0), ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL," +
"NULL, NULL, NULL " +
+ "SELECT uid, service_id, url, ifnull(title, ''), "
+ "ifnull(stream_type, 'VIDEO_STREAM'), ifnull(duration, 0), "
+ "ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL, NULL, NULL, NULL "
"FROM streams " +
"WHERE url IS NOT NULL");
+ "FROM streams WHERE url IS NOT NULL");
database.execSQL("DROP TABLE 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
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 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 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 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)");
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 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;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.annotation.Nullable;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
@ -18,11 +18,10 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE
@Dao
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
@Query("SELECT * FROM " + TABLE_NAME +
" WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
@Nullable
SearchHistoryEntry getLatestEntry();
@ -37,13 +36,16 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
@Override
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);
@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
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);
}

View File

@ -1,32 +1,31 @@
package org.schabi.newpipe.database.history.dao;
import androidx.annotation.Nullable;
import androidx.room.Dao;
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.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import java.util.List;
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.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.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
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE +
" WHERE " + STREAM_ACCESS_DATE + " = " +
"(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE
+ " WHERE " + STREAM_ACCESS_DATE + " = "
+ "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
@Override
@Nullable
public abstract StreamHistoryEntity getLatestEntry();
@ -40,33 +39,33 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
public abstract int deleteAll();
@Override
public Flowable<List<StreamHistoryEntity>> listByService(int serviceId) {
public Flowable<List<StreamHistoryEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
@Query("SELECT * FROM " + STREAM_TABLE +
" INNER JOIN " + STREAM_HISTORY_TABLE +
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
" ORDER BY " + STREAM_ACCESS_DATE + " DESC")
@Query("SELECT * FROM " + STREAM_TABLE
+ " INNER JOIN " + STREAM_HISTORY_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + STREAM_ACCESS_DATE + " DESC")
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID +
" = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID
+ " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
@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")
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
" INNER JOIN " +
"(SELECT " + JOIN_STREAM_ID + ", " +
" MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " +
" SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT +
" FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" +
+ " INNER JOIN "
+ "(SELECT " + JOIN_STREAM_ID + ", "
+ " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", "
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
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,
indices = {@Index(value = SEARCH)})
public class SearchHistoryEntry {
public static final String ID = "id";
public static final String TABLE_NAME = "search_history";
public static final String SERVICE_ID = "service_id";
@ -33,7 +32,7 @@ public class SearchHistoryEntry {
@ColumnInfo(name = 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.creationDate = creationDate;
this.search = search;
@ -43,7 +42,7 @@ public class SearchHistoryEntry {
return id;
}
public void setId(long id) {
public void setId(final long id) {
this.id = id;
}
@ -51,7 +50,7 @@ public class SearchHistoryEntry {
return creationDate;
}
public void setCreationDate(Date creationDate) {
public void setCreationDate(final Date creationDate) {
this.creationDate = creationDate;
}
@ -59,7 +58,7 @@ public class SearchHistoryEntry {
return serviceId;
}
public void setServiceId(int serviceId) {
public void setServiceId(final int serviceId) {
this.serviceId = serviceId;
}
@ -67,13 +66,13 @@ public class SearchHistoryEntry {
return search;
}
public void setSearch(String search) {
public void setSearch(final String search) {
this.search = search;
}
@Ignore
public boolean hasEqualValues(SearchHistoryEntry otherEntry) {
return getServiceId() == otherEntry.getServiceId() &&
getSearch().equals(otherEntry.getSearch());
public boolean hasEqualValues(final SearchHistoryEntry otherEntry) {
return getServiceId() == otherEntry.getServiceId()
&& getSearch().equals(otherEntry.getSearch());
}
}

View File

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

View File

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

View File

@ -14,9 +14,16 @@ import java.util.List;
import io.reactivex.Flowable;
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.PlaylistStreamEntity.*;
import static org.schabi.newpipe.database.stream.model.StreamEntity.*;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
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
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
@ -29,40 +36,39 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
public abstract int deleteAll();
@Override
public Flowable<List<PlaylistStreamEntity>> listByService(int serviceId) {
public Flowable<List<PlaylistStreamEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE +
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
public abstract void deleteBatch(final long playlistId);
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
public abstract void deleteBatch(long playlistId);
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" +
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
public abstract Flowable<Integer> getMaximumIndexOf(final long playlistId);
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)"
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
public abstract Flowable<Integer> getMaximumIndexOf(long playlistId);
@Transaction
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " +
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN "
// get ids of streams of the given playlist
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX +
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" +
+ "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)"
// then merge with the stream metadata
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
" ORDER BY " + JOIN_INDEX + " ASC")
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + JOIN_INDEX + " ASC")
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
@Transaction
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " +
PLAYLIST_THUMBNAIL_URL + ", " +
"COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT +
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
" FROM " + PLAYLIST_TABLE +
" LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
" ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID +
" GROUP BY " + JOIN_PLAYLIST_ID +
" ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
+ " FROM " + PLAYLIST_TABLE
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
+ " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
+ " GROUP BY " + JOIN_PLAYLIST_ID
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
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,
indices = {@Index(value = {PLAYLIST_NAME})})
public class PlaylistEntity {
final public static String PLAYLIST_TABLE = "playlists";
final public static String PLAYLIST_ID = "uid";
final public static String PLAYLIST_NAME = "name";
final public static String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
public static final String PLAYLIST_TABLE = "playlists";
public static final String PLAYLIST_ID = "uid";
public static final String PLAYLIST_NAME = "name";
public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = PLAYLIST_ID)
@ -26,7 +26,7 @@ public class PlaylistEntity {
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
private String thumbnailUrl;
public PlaylistEntity(String name, String thumbnailUrl) {
public PlaylistEntity(final String name, final String thumbnailUrl) {
this.name = name;
this.thumbnailUrl = thumbnailUrl;
}
@ -35,7 +35,7 @@ public class PlaylistEntity {
return uid;
}
public void setUid(long uid) {
public void setUid(final long uid) {
this.uid = uid;
}
@ -43,7 +43,7 @@ public class PlaylistEntity {
return name;
}
public void setName(String name) {
public void setName(final String name) {
this.name = name;
}
@ -51,7 +51,7 @@ public class PlaylistEntity {
return thumbnailUrl;
}
public void setThumbnailUrl(String thumbnailUrl) {
public void setThumbnailUrl(final String 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)
})
public class PlaylistRemoteEntity implements PlaylistLocalItem {
final public static String REMOTE_PLAYLIST_TABLE = "remote_playlists";
final public static String REMOTE_PLAYLIST_ID = "uid";
final public static String REMOTE_PLAYLIST_SERVICE_ID = "service_id";
final public static String REMOTE_PLAYLIST_NAME = "name";
final public static String REMOTE_PLAYLIST_URL = "url";
final public static String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
final public static String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
final public static String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
public static final String REMOTE_PLAYLIST_TABLE = "remote_playlists";
public static final String REMOTE_PLAYLIST_ID = "uid";
public static final String REMOTE_PLAYLIST_SERVICE_ID = "service_id";
public static final String REMOTE_PLAYLIST_NAME = "name";
public static final String REMOTE_PLAYLIST_URL = "url";
public static final String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
public static final String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
public static final String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = REMOTE_PLAYLIST_ID)
@ -55,8 +55,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
private Long streamCount;
public PlaylistRemoteEntity(int serviceId, String name, String url, String thumbnailUrl,
String uploader, Long streamCount) {
public PlaylistRemoteEntity(final int serviceId, final String name, final String url,
final String thumbnailUrl, final String uploader,
final Long streamCount) {
this.serviceId = serviceId;
this.name = name;
this.url = url;
@ -68,7 +69,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@Ignore
public PlaylistRemoteEntity(final PlaylistInfo info) {
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());
}
@ -90,7 +92,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return uid;
}
public void setUid(long uid) {
public void setUid(final long uid) {
this.uid = uid;
}
@ -98,7 +100,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return serviceId;
}
public void setServiceId(int serviceId) {
public void setServiceId(final int serviceId) {
this.serviceId = serviceId;
}
@ -106,7 +108,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return name;
}
public void setName(String name) {
public void setName(final String name) {
this.name = name;
}
@ -114,7 +116,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return thumbnailUrl;
}
public void setThumbnailUrl(String thumbnailUrl) {
public void setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
@ -122,7 +124,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return url;
}
public void setUrl(String url) {
public void setUrl(final String url) {
this.url = url;
}
@ -130,7 +132,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return uploader;
}
public void setUploader(String uploader) {
public void setUploader(final String uploader) {
this.uploader = uploader;
}
@ -138,7 +140,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
return streamCount;
}
public void setStreamCount(Long streamCount) {
public void setStreamCount(final Long 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)
})
public class PlaylistStreamEntity {
final public static String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
final public static String JOIN_PLAYLIST_ID = "playlist_id";
final public static String JOIN_STREAM_ID = "stream_id";
final public static String JOIN_INDEX = "join_index";
public static final String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
public static final String JOIN_PLAYLIST_ID = "playlist_id";
public static final String JOIN_STREAM_ID = "stream_id";
public static final String JOIN_INDEX = "join_index";
@ColumnInfo(name = JOIN_PLAYLIST_ID)
private long playlistUid;
@ -55,23 +54,23 @@ public class PlaylistStreamEntity {
return playlistUid;
}
public void setPlaylistUid(final long playlistUid) {
this.playlistUid = playlistUid;
}
public long getStreamUid() {
return streamUid;
}
public void setStreamUid(final long streamUid) {
this.streamUid = streamUid;
}
public int getIndex() {
return index;
}
public void setPlaylistUid(long playlistUid) {
this.playlistUid = playlistUid;
}
public void setStreamUid(long streamUid) {
this.streamUid = streamUid;
}
public void setIndex(int index) {
public void setIndex(final int index) {
this.index = index;
}
}

View File

@ -27,21 +27,21 @@ public abstract class StreamStateDAO implements BasicDAO<StreamStateEntity> {
public abstract int deleteAll();
@Override
public Flowable<List<StreamStateEntity>> listByService(int serviceId) {
public Flowable<List<StreamStateEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
@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")
public abstract int deleteState(final long streamId);
public abstract int deleteState(long streamId);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract void silentInsertInternal(final StreamStateEntity streamState);
abstract void silentInsertInternal(StreamStateEntity streamState);
@Transaction
public long upsert(StreamStateEntity stream) {
public long upsert(final StreamStateEntity stream) {
silentInsertInternal(stream);
return update(stream);
}

View File

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

View File

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

View File

@ -48,7 +48,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
entity.uid = uidFromInsert
} else {
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
update(entity)

View File

@ -1,11 +1,11 @@
package org.schabi.newpipe.database.subscription;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
@ -18,15 +18,14 @@ import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCR
@Entity(tableName = SUBSCRIPTION_TABLE,
indices = {@Index(value = {SUBSCRIPTION_SERVICE_ID, SUBSCRIPTION_URL}, unique = true)})
public class SubscriptionEntity {
public static final String SUBSCRIPTION_UID = "uid";
public static final String SUBSCRIPTION_TABLE = "subscriptions";
public static final String SUBSCRIPTION_SERVICE_ID = "service_id";
public static final String SUBSCRIPTION_URL = "url";
public static final String SUBSCRIPTION_NAME = "name";
public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url";
public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count";
public static final String SUBSCRIPTION_DESCRIPTION = "description";
public static final String SUBSCRIPTION_UID = "uid";
public static final String SUBSCRIPTION_TABLE = "subscriptions";
public static final String SUBSCRIPTION_SERVICE_ID = "service_id";
public static final String SUBSCRIPTION_URL = "url";
public static final String SUBSCRIPTION_NAME = "name";
public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url";
public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count";
public static final String SUBSCRIPTION_DESCRIPTION = "description";
@PrimaryKey(autoGenerate = true)
private long uid = 0;
@ -49,11 +48,21 @@ public class SubscriptionEntity {
@ColumnInfo(name = SUBSCRIPTION_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() {
return uid;
}
public void setUid(long uid) {
public void setUid(final long uid) {
this.uid = uid;
}
@ -61,7 +70,7 @@ public class SubscriptionEntity {
return serviceId;
}
public void setServiceId(int serviceId) {
public void setServiceId(final int serviceId) {
this.serviceId = serviceId;
}
@ -69,7 +78,7 @@ public class SubscriptionEntity {
return url;
}
public void setUrl(String url) {
public void setUrl(final String url) {
this.url = url;
}
@ -77,7 +86,7 @@ public class SubscriptionEntity {
return name;
}
public void setName(String name) {
public void setName(final String name) {
this.name = name;
}
@ -85,7 +94,7 @@ public class SubscriptionEntity {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
public void setAvatarUrl(final String avatarUrl) {
this.avatarUrl = avatarUrl;
}
@ -93,7 +102,7 @@ public class SubscriptionEntity {
return subscriberCount;
}
public void setSubscriberCount(Long subscriberCount) {
public void setSubscriberCount(final Long subscriberCount) {
this.subscriberCount = subscriberCount;
}
@ -101,19 +110,16 @@ public class SubscriptionEntity {
return description;
}
public void setDescription(String description) {
public void setDescription(final String description) {
this.description = description;
}
@Ignore
public void setData(final String name,
final String avatarUrl,
final String description,
final Long subscriberCount) {
this.setName(name);
this.setAvatarUrl(avatarUrl);
this.setDescription(description);
this.setSubscriberCount(subscriberCount);
public void setData(final String n, final String au, final String d, final Long sc) {
this.setName(n);
this.setAvatarUrl(au);
this.setDescription(d);
this.setSubscriberCount(sc);
}
@Ignore
@ -124,13 +130,4 @@ public class SubscriptionEntity {
item.setDescription(getDescription());
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.content.Intent;
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.MenuInflater;
import android.view.MenuItem;
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.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import us.shandian.giga.service.DownloadManagerService;
@ -25,7 +25,7 @@ public class DownloadActivity extends AppCompatActivity {
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(final Bundle savedInstanceState) {
// Service
Intent i = new Intent();
i.setClass(this, DownloadManagerService.class);
@ -46,7 +46,8 @@ public class DownloadActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true);
}
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
getWindow().getDecorView().getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
updateFragments();
@ -65,7 +66,7 @@ public class DownloadActivity extends AppCompatActivity {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
public boolean onCreateOptionsMenu(final Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
@ -75,7 +76,7 @@ public class DownloadActivity extends AppCompatActivity {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();

View File

@ -81,11 +81,12 @@ import us.shandian.giga.service.MissionState;
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 boolean DEBUG = MainActivity.DEBUG;
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
private final CompositeDisposable disposables = new CompositeDisposable();
@State
protected StreamInfo currentInfo;
@State
@ -100,30 +101,32 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
protected int selectedAudioIndex = 0;
@State
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<VideoStream, AudioStream> videoStreamsAdapter;
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
private final CompositeDisposable disposables = new CompositeDisposable();
private EditText nameEditText;
private Spinner streamsSpinner;
private RadioGroup radioStreamsGroup;
private TextView threadsCountTextView;
private SeekBar threadsSeekBar;
private SharedPreferences prefs;
public static DownloadDialog newInstance(StreamInfo info) {
public static DownloadDialog newInstance(final StreamInfo info) {
DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info);
return dialog;
}
public static DownloadDialog newInstance(Context context, StreamInfo info) {
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context,
info.getVideoStreams(), info.getVideoOnlyStreams(), false));
public static DownloadDialog newInstance(final Context context, final StreamInfo info) {
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper
.getSortedStreamVideosList(context, info.getVideoStreams(),
info.getVideoOnlyStreams(), false));
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
final DownloadDialog instance = newInstance(info);
@ -135,57 +138,61 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
return instance;
}
private void setInfo(StreamInfo info) {
private void setInfo(final StreamInfo info) {
this.currentInfo = info;
}
public void setAudioStreams(List<AudioStream> audioStreams) {
public void setAudioStreams(final List<AudioStream> audioStreams) {
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext()));
}
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) {
this.wrappedAudioStreams = wrappedAudioStreams;
public void setAudioStreams(final StreamSizeWrapper<AudioStream> was) {
this.wrappedAudioStreams = was;
}
public void setVideoStreams(List<VideoStream> videoStreams) {
public void setVideoStreams(final List<VideoStream> videoStreams) {
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
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG)
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
public void setVideoStreams(final StreamSizeWrapper<VideoStream> wvs) {
this.wrappedVideoStreams = wvs;
}
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();
return;
}
@ -199,17 +206,23 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) continue;
AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (!videoStreams.get(i).isVideoOnly()) {
continue;
}
AudioStream audioStream = SecondaryStreamHelper
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
secondaryStreams
.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
} 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.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
@ -218,7 +231,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
context.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName cname, IBinder service) {
public void onServiceConnected(final ComponentName cname, final IBinder service) {
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
mainStorageAudio = mgr.getMainStorageAudio();
@ -232,25 +245,34 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
@Override
public void onServiceDisconnected(ComponentName name) {
public void onServiceDisconnected(final ComponentName name) {
// nothing to do
}
}, Context.BIND_AUTO_CREATE);
}
/*//////////////////////////////////////////////////////////////////////////
// Inits
//////////////////////////////////////////////////////////////////////////*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (DEBUG)
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
if (DEBUG) {
Log.d(TAG, "onCreateView() called with: "
+ "inflater = [" + inflater + "], container = [" + container + "], "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
return inflater.inflate(R.layout.download_dialog, container);
}
@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);
nameEditText = view.findViewById(R.id.file_name);
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
selectedAudioIndex = ListHelper
.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
@ -272,21 +294,20 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
threadsCountTextView.setText(String.valueOf(threads));
threadsSeekBar.setProgress(threads - 1);
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
progress++;
prefs.edit().putInt(getString(R.string.default_download_threads), progress).apply();
threadsCountTextView.setText(String.valueOf(progress));
public void onProgressChanged(final SeekBar seekbar, final int progress,
final boolean fromUser) {
final int newProgress = progress + 1;
prefs.edit().putInt(getString(R.string.default_download_threads), newProgress)
.apply();
threadsCountTextView.setText(String.valueOf(newProgress));
}
@Override
public void onStartTrackingTouch(SeekBar p1) {
}
public void onStartTrackingTouch(final SeekBar p1) { }
@Override
public void onStopTrackingTouch(SeekBar p1) {
}
public void onStopTrackingTouch(final SeekBar p1) { }
});
fetchStreamsSize();
@ -295,17 +316,20 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private void fetchStreamsSize() {
disposables.clear();
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> {
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
.subscribe(result -> {
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) {
setupVideoSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> {
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
.subscribe(result -> {
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
setupAudioSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> {
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
.subscribe(result -> {
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
setupSubtitleSpinner();
}
@ -318,14 +342,22 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
disposables.clear();
}
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
/*//////////////////////////////////////////////////////////////////////////
// Streams Spinner Listener
//////////////////////////////////////////////////////////////////////////*/
@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);
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())) {
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;
}
@ -347,27 +380,27 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
// check if the selected file was previously used
checkSelectedDownload(null, data.getData(), docFile.getName(), docFile.getType());
checkSelectedDownload(null, data.getData(), docFile.getName(),
docFile.getType());
}
}
/*//////////////////////////////////////////////////////////////////////////
// Inits
//////////////////////////////////////////////////////////////////////////*/
private void initToolbar(Toolbar toolbar) {
if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
private void initToolbar(final Toolbar toolbar) {
if (DEBUG) {
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
}
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
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.setNavigationOnClickListener(v -> getDialog().dismiss());
toolbar.setNavigationContentDescription(R.string.cancel);
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 -> {
if (item.getItemId() == R.id.okay) {
@ -381,8 +414,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
});
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void setupAudioSpinner() {
if (getContext() == null) return;
if (getContext() == null) {
return;
}
streamsSpinner.setAdapter(audioStreamsAdapter);
streamsSpinner.setSelection(selectedAudioIndex);
@ -390,7 +429,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
private void setupVideoSpinner() {
if (getContext() == null) return;
if (getContext() == null) {
return;
}
streamsSpinner.setAdapter(videoStreamsAdapter);
streamsSpinner.setSelection(selectedVideoIndex);
@ -398,21 +439,21 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
private void setupSubtitleSpinner() {
if (getContext() == null) return;
if (getContext() == null) {
return;
}
streamsSpinner.setAdapter(subtitleStreamsAdapter);
streamsSpinner.setSelection(selectedSubtitleIndex);
setRadioButtonsState(true);
}
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
if (DEBUG)
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) {
if (DEBUG) {
Log.d(TAG, "onCheckedChanged() called with: "
+ "group = [" + group + "], checkedId = [" + checkedId + "]");
}
boolean flag = true;
switch (checkedId) {
@ -431,14 +472,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
threadsSeekBar.setEnabled(flag);
}
/*//////////////////////////////////////////////////////////////////////////
// Streams Spinner Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (DEBUG)
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
public void onItemSelected(final AdapterView<?> parent, final View view,
final int position, final long id) {
if (DEBUG) {
Log.d(TAG, "onItemSelected() called with: "
+ "parent = [" + parent + "], view = [" + view + "], "
+ "position = [" + position + "], id = [" + id + "]");
}
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedAudioIndex = position;
@ -453,13 +494,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
public void onNothingSelected(final AdapterView<?> parent) {
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void setupDownloadOptions() {
setRadioButtonsState(false);
@ -484,30 +521,36 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
subtitleButton.setChecked(true);
setupSubtitleSpinner();
} 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();
}
}
private void setRadioButtonsState(boolean enabled) {
private void setRadioButtonsState(final boolean enabled) {
radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled);
radioStreamsGroup.findViewById(R.id.video_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();
int candidate = 0;
for (int i = 0; i < streams.size(); i++) {
final Locale streamLocale = streams.get(i).getLocale();
final boolean languageEquals = streamLocale.getLanguage() != null && preferredLocalization.getLanguageCode() != null &&
streamLocale.getLanguage().equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage());
final boolean countryEquals = streamLocale.getCountry() != null && streamLocale.getCountry().equals(preferredLocalization.getCountryCode());
final boolean languageEquals = streamLocale.getLanguage() != null
&& preferredLocalization.getLanguageCode() != null
&& streamLocale.getLanguage()
.equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage());
final boolean countryEquals = streamLocale.getCountry() != null
&& streamLocale.getCountry().equals(preferredLocalization.getCountryCode());
if (languageEquals) {
if (countryEquals) return i;
if (countryEquals) {
return i;
}
candidate = i;
}
@ -516,20 +559,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
return candidate;
}
StoredDirectoryHelper mainStorageAudio = null;
StoredDirectoryHelper mainStorageVideo = null;
DownloadManager downloadManager = null;
ActionMenuItemView okButton = null;
Context context;
boolean askForSavePath;
private String getNameEditText() {
String str = nameEditText.getText().toString().trim();
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
}
private void showFailedDialog(@StringRes int msg) {
private void showFailedDialog(@StringRes final int msg) {
assureCorrectAppLanguage(getContext());
new AlertDialog.Builder(context)
.setTitle(R.string.general_error)
@ -539,13 +575,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
.show();
}
private void showErrorActivity(Exception e) {
private void showErrorActivity(final Exception e) {
ErrorActivity.reportError(
context,
Collections.singletonList(e),
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:
mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
switch(format) {
switch (format) {
case WEBMA_OPUS:
mime = "audio/ogg";
filename += "opus";
@ -581,7 +618,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
filename += format.suffix;
break;
case R.id.subtitle_button:
mainStorage = mainStorageVideo;// subtitle & video files go together
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
mime = format.mimeType;
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)
// * the user checked the "ask where to download" option
if (!askForSavePath)
Toast.makeText(context, getString(R.string.no_available_dir), Toast.LENGTH_LONG).show();
if (!askForSavePath) {
Toast.makeText(context, getString(R.string.no_available_dir),
Toast.LENGTH_LONG).show();
}
if (NewPipeSettings.useStorageAccessFramework(context)) {
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, filename, mime);
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS,
filename, mime);
} else {
File initialSavePath;
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button)
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
else
} else {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
}
initialSavePath = new File(initialSavePath, filename);
startActivityForResult(
FilePickerActivityHelper.chooseFileToSave(context, initialSavePath.getAbsolutePath()),
REQUEST_DOWNLOAD_SAVE_AS
);
startActivityForResult(FilePickerActivityHelper.chooseFileToSave(context,
initialSavePath.getAbsolutePath()), REQUEST_DOWNLOAD_SAVE_AS);
}
return;
@ -622,7 +661,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
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;
try {
@ -631,10 +672,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
storage = new StoredFileHelper(context, null, targetFile, "");
} else if (targetFile == null) {
// 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 {
// 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) {
showErrorActivity(e);
@ -738,24 +781,28 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
} else {
try {
// 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) {
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;
}
}
if (storageNew != null && storageNew.canWrite())
if (storageNew != null && storageNew.canWrite()) {
continueSelectedDownload(storageNew);
else
} else {
showFailedDialog(R.string.error_file_creation);
}
break;
case PendingRunning:
storageNew = mainStorage.createUniqueFile(filename, mime);
if (storageNew == null)
if (storageNew == null) {
showFailedDialog(R.string.error_file_creation);
else
} else {
continueSelectedDownload(storageNew);
}
break;
}
});
@ -763,7 +810,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
askDialog.create().show();
}
private void continueSelectedDownload(@NonNull StoredFileHelper storage) {
private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
if (!storage.canWrite()) {
showFailedDialog(R.string.permission_denied);
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
try {
if (storage.length() > 0) storage.truncate();
if (storage.length() > 0) {
storage.truncate();
}
} catch (IOException e) {
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
showFailedDialog(R.string.overwrite_failed);
@ -811,13 +860,15 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
if (secondary != null) {
secondaryStream = secondary.getStream();
if (selectedStream.getFormat() == MediaFormat.MPEG_4)
if (selectedStream.getFormat() == MediaFormat.MPEG_4) {
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
else
} else {
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
}
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
// 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;
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';
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
@ -835,7 +886,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
psArgs = new String[]{
selectedStream.getFormat().getSuffix(),
"false",// ignore empty frames
"false" // ignore empty frames
};
}
break;
@ -854,14 +905,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
urls = new String[]{
selectedStream.getUrl(), secondaryStream.getUrl()
};
recoveryInfo = new MissionRecoveryInfo[]{
new MissionRecoveryInfo(selectedStream), new MissionRecoveryInfo(secondaryStream)
};
recoveryInfo = new MissionRecoveryInfo[]{new MissionRecoveryInfo(selectedStream),
new MissionRecoveryInfo(secondaryStream)};
}
DownloadManagerService.startMission(
context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo
);
DownloadManagerService.startMission(context, urls, storage, kind, threads,
currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo);
dismiss();
}

View File

@ -1,11 +1,11 @@
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 {
/**
* A back press was delegated to this fragment
* A back press was delegated to this fragment.
*
* @return if the back press was handled
*/

View File

@ -1,9 +1,8 @@
package org.schabi.newpipe.fragments;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@ -11,6 +10,9 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.jakewharton.rxbinding2.view.RxView;
import org.schabi.newpipe.BaseFragment;
@ -36,22 +38,21 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {
@State
protected AtomicBoolean wasLoading = new AtomicBoolean();
protected AtomicBoolean isLoading = new AtomicBoolean();
@Nullable
protected View emptyStateView;
private View emptyStateView;
@Nullable
protected ProgressBar loadingProgressBar;
private ProgressBar loadingProgressBar;
protected View errorPanelRoot;
protected Button errorButtonRetry;
protected TextView errorTextView;
private Button errorButtonRetry;
private TextView errorTextView;
@Override
public void onViewCreated(View rootView, Bundle savedInstanceState) {
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
doInitialLoadLogic();
}
@ -62,14 +63,12 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
wasLoading.set(isLoading.get());
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
emptyStateView = rootView.findViewById(R.id.empty_state_view);
@ -105,8 +104,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
startLoading(true);
}
protected void startLoading(boolean forceLoad) {
if (DEBUG) Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]");
protected void startLoading(final boolean forceLoad) {
if (DEBUG) {
Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]");
}
showLoading();
isLoading.set(true);
}
@ -117,42 +118,62 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
@Override
public void showLoading() {
if (emptyStateView != null) animateView(emptyStateView, false, 150);
if (loadingProgressBar != null) animateView(loadingProgressBar, true, 400);
if (emptyStateView != null) {
animateView(emptyStateView, false, 150);
}
if (loadingProgressBar != null) {
animateView(loadingProgressBar, true, 400);
}
animateView(errorPanelRoot, false, 150);
}
@Override
public void hideLoading() {
if (emptyStateView != null) animateView(emptyStateView, false, 150);
if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0);
if (emptyStateView != null) {
animateView(emptyStateView, false, 150);
}
if (loadingProgressBar != null) {
animateView(loadingProgressBar, false, 0);
}
animateView(errorPanelRoot, false, 150);
}
@Override
public void showEmptyState() {
isLoading.set(false);
if (emptyStateView != null) animateView(emptyStateView, true, 200);
if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0);
if (emptyStateView != null) {
animateView(emptyStateView, true, 200);
}
if (loadingProgressBar != null) {
animateView(loadingProgressBar, false, 0);
}
animateView(errorPanelRoot, false, 150);
}
@Override
public void showError(String message, boolean showRetryButton) {
if (DEBUG) Log.d(TAG, "showError() called with: message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
public void showError(final String message, final boolean showRetryButton) {
if (DEBUG) {
Log.d(TAG, "showError() called with: "
+ "message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
}
isLoading.set(false);
InfoCache.getInstance().clearCache();
hideLoading();
errorTextView.setText(message);
if (showRetryButton) animateView(errorButtonRetry, true, 600);
else animateView(errorButtonRetry, false, 0);
if (showRetryButton) {
animateView(errorButtonRetry, true, 600);
} else {
animateView(errorButtonRetry, false, 0);
}
animateView(errorPanelRoot, true, 300);
}
@Override
public void handleResult(I result) {
if (DEBUG) Log.d(TAG, "handleResult() called with: result = [" + result + "]");
public void handleResult(final I result) {
if (DEBUG) {
Log.d(TAG, "handleResult() called with: result = [" + result + "]");
}
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) {
if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]");
protected boolean onError(final Throwable exception) {
if (DEBUG) {
Log.d(TAG, "onError() called with: exception = [" + exception + "]");
}
isLoading.set(false);
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;
}
if (ExtractorHelper.isInterruptedCaused(exception)) {
if (DEBUG) Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
if (DEBUG) {
Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
}
return true;
}
@ -193,8 +221,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
return false;
}
public void onReCaptchaException(ReCaptchaException exception) {
if (DEBUG) Log.d(TAG, "onReCaptchaException() called");
public void onReCaptchaException(final ReCaptchaException exception) {
if (DEBUG) {
Log.d(TAG, "onReCaptchaException() called");
}
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
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);
}
public void onUnrecoverableError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName, request, errorId);
public void onUnrecoverableError(final Throwable exception, final UserAction userAction,
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) {
if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
public void onUnrecoverableError(final List<Throwable> exception, final UserAction userAction,
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";
if (request == null) request = "none";
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
ErrorActivity.ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
request == null ? "none" : request, errorId));
}
public void showSnackBarError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request, errorId);
public void showSnackBarError(final Throwable exception, final UserAction userAction,
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) {
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;
if (rootView == null && getView() != null) rootView = getView();
if (rootView == null) return;
if (rootView == null && getView() != null) {
rootView = getView();
}
if (rootView == null) {
return;
}
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));

View File

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

View File

@ -1,17 +1,19 @@
package org.schabi.newpipe.fragments;
import android.os.Bundle;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
public class EmptyFragment extends BaseFragment {
@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);
}
}

View File

@ -50,14 +50,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
tabsManager = TabsManager.getManager(activity);
tabsManager.setSavedTabsListener(() -> {
if (DEBUG) {
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed());
Log.d(TAG, "TabsManager.SavedTabsChangeListener: "
+ "onTabsChanged called, isResumed = " + isResumed());
}
if (isResumed()) {
setupTabs();
@ -68,12 +69,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
@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);
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
tabLayout = rootView.findViewById(R.id.main_tab_layout);
@ -89,14 +92,18 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onResume() {
super.onResume();
if (hasTabsChanged) setupTabs();
if (hasTabsChanged) {
setupTabs();
}
}
@Override
public void onDestroy() {
super.onDestroy();
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
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater 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);
ActionBar supportActionBar = activity.getSupportActionBar();
@ -116,7 +126,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
try {
@ -141,7 +151,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
tabsList.addAll(tabsManager.getTabs());
if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) {
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), getChildFragmentManager(), tabsList);
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(),
getChildFragmentManager(), tabsList);
}
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()));
}
@Override
public void onTabSelected(TabLayout.Tab selectedTab) {
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
public void onTabSelected(final TabLayout.Tab selectedTab) {
if (DEBUG) {
Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
}
updateTitleForTab(selectedTab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
public void onTabUnselected(final TabLayout.Tab tab) { }
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
public void onTabReselected(final TabLayout.Tab tab) {
if (DEBUG) {
Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
}
updateTitleForTab(tab.getPosition());
}
private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapterMenuWorkaround {
private static final class SelectedTabsPagerAdapter
extends FragmentStatePagerAdapterMenuWorkaround {
private final Context context;
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);
this.context = context;
this.internalTabsList = new ArrayList<>(tabsList);
@ -197,7 +214,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
@NonNull
@Override
public Fragment getItem(int position) {
public Fragment getItem(final int position) {
final Tab tab = internalTabsList.get(position);
Throwable throwable = null;
@ -209,8 +226,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
if (throwable != null) {
ErrorActivity.reportError(context, throwable, null, null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
return new BlankFragment();
}
@ -222,7 +239,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
@Override
public int getItemPosition(Object object) {
public int getItemPosition(final Object object) {
// Causes adapter to reload all Fragments when
// notifyDataSetChanged is called
return POSITION_NONE;
@ -233,7 +250,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
return internalTabsList.size();
}
public boolean sameTabs(List<Tab> tabsToCompare) {
public boolean sameTabs(final List<Tab> 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.
*/
public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener {
@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);
if (dy > 0) {
int pastVisibleItems = 0, visibleItemCount, totalItemCount;
int pastVisibleItems = 0;
int visibleItemCount;
int totalItemCount;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
visibleItemCount = layoutManager.getChildCount();
@ -22,10 +23,14 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
// Already covers the GridLayoutManager case
if (layoutManager instanceof LinearLayoutManager) {
pastVisibleItems = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
pastVisibleItems = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null);
if (positions != null && positions.length > 0) pastVisibleItems = positions[0];
int[] positions = ((StaggeredGridLayoutManager) layoutManager)
.findFirstVisibleItemPositions(null);
if (positions != null && positions.length > 0) {
pastVisibleItems = positions[0];
}
}
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {

View File

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

View File

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

View File

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

View File

@ -7,16 +7,17 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
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.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
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.extractor.InfoItem;
@ -40,24 +41,26 @@ import java.util.Queue;
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
//////////////////////////////////////////////////////////////////////////*/
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected InfoListAdapter infoListAdapter;
protected RecyclerView itemsList;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected StateSaver.SavedState savedState;
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
private boolean useDefaultStateSaving = true;
private int updateFlags = 0;
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
if (infoListAdapter == null) {
@ -71,17 +74,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onDestroy() {
super.onDestroy();
if (useDefaultStateSaving) StateSaver.onDestroy(savedState);
if (useDefaultStateSaving) {
StateSaver.onDestroy(savedState);
}
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
}
@ -93,28 +102,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
itemsList.setLayoutManager(useGrid
? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
}
}
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
protected StateSaver.SavedState savedState;
protected boolean useDefaultStateSaving = true;
/**
* If the default implementation of {@link StateSaver.WriteRead} should be used.
*
* @see StateSaver
* @param useDefaultStateSaving Whether the default implementation should be used
*/
public void useDefaultStateSaving(boolean useDefault) {
this.useDefaultStateSaving = useDefault;
public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
this.useDefaultStateSaving = useDefaultStateSaving;
}
@Override
@ -124,13 +128,15 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void writeTo(Queue<Object> objectsToSave) {
if (useDefaultStateSaving) objectsToSave.add(infoListAdapter.getItemsList());
public void writeTo(final Queue<Object> objectsToSave) {
if (useDefaultStateSaving) {
objectsToSave.add(infoListAdapter.getItemsList());
}
}
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
if (useDefaultStateSaving) {
infoListAdapter.getItemsList().clear();
infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll());
@ -138,15 +144,20 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void onSaveInstanceState(Bundle bundle) {
public void onSaveInstanceState(final Bundle 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
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
protected void onRestoreInstanceState(@NonNull final Bundle 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();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
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);
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.setFooter(getListFooter());
infoListAdapter.setHeader(getListHeader());
itemsList.setAdapter(infoListAdapter);
}
protected void onItemSelected(InfoItem selectedItem) {
if (DEBUG) Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
protected void onItemSelected(final InfoItem selectedItem) {
if (DEBUG) {
Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
}
}
@Override
@ -199,19 +213,19 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
super.initListeners();
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
@Override
public void selected(StreamInfoItem selectedItem) {
public void selected(final StreamInfoItem selectedItem) {
onStreamSelected(selectedItem);
}
@Override
public void held(StreamInfoItem selectedItem) {
public void held(final StreamInfoItem selectedItem) {
showStreamDialog(selectedItem);
}
});
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
@Override
public void selected(ChannelInfoItem selectedItem) {
public void selected(final ChannelInfoItem selectedItem) {
try {
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(getFM(),
@ -226,7 +240,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
@Override
public void selected(PlaylistInfoItem selectedItem) {
public void selected(final PlaylistInfoItem selectedItem) {
try {
onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(getFM(),
@ -241,7 +255,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
@Override
public void selected(CommentsInfoItem selectedItem) {
public void selected(final CommentsInfoItem selectedItem) {
onItemSelected(selectedItem);
}
});
@ -249,13 +263,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
@Override
public void onScrolledDown(RecyclerView recyclerView) {
public void onScrolledDown(final RecyclerView recyclerView) {
onScrollToBottom();
}
});
}
private void onStreamSelected(StreamInfoItem selectedItem) {
private void onStreamSelected(final StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(getFM(),
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) {
final Context context = getContext();
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) {
StreamDialogEntry.setEnabledEntries(
@ -291,8 +305,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
StreamDialogEntry.share);
}
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
StreamDialogEntry.clickOn(which, this, item)).show();
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
(dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
}
/*//////////////////////////////////////////////////////////////////////////
@ -300,8 +314,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
@ -339,7 +356,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void showError(String message, boolean showRetryButton) {
public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton);
showListFooter(false);
animateView(itemsList, false, 200);
@ -361,25 +378,28 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void handleNextItems(N result) {
public void handleNextItems(final N result) {
isLoading.set(false);
}
@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))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
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));
if ("auto".equals(list_mode)) {
final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.list_view_mode_key),
getString(R.string.list_view_mode_value));
if ("auto".equals(listMode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} 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>
extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
@ -34,7 +33,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
protected Disposable currentWorker;
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
setTitle(name);
showListFooter(hasMoreItems());
@ -43,7 +42,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
public void onPause() {
super.onPause();
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
}
@Override
@ -73,7 +74,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/
@Override
public void writeTo(Queue<Object> objectsToSave) {
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentInfo);
objectsToSave.add(currentNextPageUrl);
@ -81,7 +82,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentInfo = (I) savedObjects.poll();
currentNextPageUrl = (String) savedObjects.poll();
@ -92,10 +93,14 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/
protected void doInitialLoadLogic() {
if (DEBUG) Log.d(TAG, "doInitialLoadLogic() called");
if (DEBUG) {
Log.d(TAG, "doInitialLoadLogic() called");
}
if (currentInfo == null) {
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}.
*
* @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);
@Override
public void startLoading(boolean forceLoad) {
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
showListFooter(false);
infoListAdapter.clearStreamItemList();
currentInfo = null;
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
currentWorker = loadResult(forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -127,19 +135,25 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
}
/**
* Implement the logic to load more items<br/>
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}
* Implement the logic to load more items.
* <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 void loadMoreItems() {
isLoading.set(true);
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
currentWorker = loadMoreItemsLogic()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
.subscribe((@io.reactivex.annotations.NonNull
ListExtractor.InfoItemsPage InfoItemsPage) -> {
isLoading.set(false);
handleNextItems(InfoItemsPage);
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
@ -149,7 +163,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
currentNextPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems());
@ -167,7 +181,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/
@Override
public void handleResult(@NonNull I result) {
public void handleResult(@NonNull final I result) {
super.handleResult(result);
name = result.getName();
@ -188,9 +202,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void setInitialData(int serviceId, String url, String name) {
this.serviceId = serviceId;
this.url = url;
this.name = !TextUtils.isEmpty(name) ? name : "";
protected void setInitialData(final int sid, final String u, final String title) {
this.serviceId = sid;
this.url = u;
this.name = !TextUtils.isEmpty(title) ? title : "";
}
}

View File

@ -4,10 +4,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
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.util.Log;
import android.view.LayoutInflater;
@ -21,6 +17,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
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 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.NewPipe;
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.stream.StreamInfoItem;
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;
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
private SubscriptionManager subscriptionManager;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private SubscriptionManager subscriptionManager;
private View headerRootLayout;
private ImageView headerChannelBanner;
private ImageView headerAvatarView;
@ -79,25 +78,24 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private TextView headerSubscribersTextView;
private Button headerSubscribeButton;
private View playlistCtrl;
private LinearLayout headerPlayAllButton;
private LinearLayout headerPopupButton;
private LinearLayout headerBackgroundButton;
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
//////////////////////////////////////////////////////////////////////////*/
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
public void setUserVisibleHint(boolean isVisibleToUser) {
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null
&& useAsFrontPage
@ -107,29 +105,40 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
subscriptionManager = new SubscriptionManager(activity);
}
@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);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) {
disposables.clear();
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
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);
headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view);
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
@ -145,12 +154,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return headerRootLayout;
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
if (useAsFrontPage && supportActionBar != null) {
@ -158,8 +163,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} else {
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu +
"], inflater = [" + inflater + "]");
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
menuRssButton = menu.findItem(R.id.menu_item_rss);
}
}
@ -172,8 +179,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
}
/*//////////////////////////////////////////////////////////////////////////
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
@ -197,22 +208,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private void monitorSubscription(final ChannelInfo info) {
final Consumer<Throwable> onError = (Throwable throwable) -> {
animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status",
0);
animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status", 0);
};
final Observable<List<SubscriptionEntity>> observable = subscriptionManager.subscriptionTable()
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
.subscriptionTable()
.getSubscriptionFlowable(info.getServiceId(), info.getUrl())
.toObservable();
@ -221,17 +226,19 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.subscribe(getSubscribeUpdateMonitor(info), onError));
disposables.add(observable
// Some updates are very rapid (when calling the updateSubscription(info), for example)
// so only update the UI for the latest emission ("sync" the subscribe button's state)
// Some updates are very rapid
// (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)
.observeOn(AndroidSchedulers.mainThread())
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
updateSubscribeButton(!subscriptionEntities.isEmpty())
, onError));
updateSubscribeButton(!subscriptionEntities.isEmpty()), 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) -> {
subscriptionManager.insertSubscription(subscription, info);
return o;
@ -246,9 +253,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
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 = () -> {
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) ->
@ -264,9 +275,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.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) -> {
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) ->
@ -287,12 +301,18 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
return (List<SubscriptionEntity> subscriptionEntities) -> {
if (DEBUG)
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
if (DEBUG) {
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: "
+ "subscriptionEntities = [" + subscriptionEntities + "]");
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
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();
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
@ -300,34 +320,45 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
info.getAvatarUrl(),
info.getDescription(),
info.getSubscriberCount());
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel, info));
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
mapOnSubscribe(channel, info));
} 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);
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
mapOnUnsubscribe(subscription));
}
};
}
private void updateSubscribeButton(boolean isSubscribed) {
if (DEBUG) Log.d(TAG, "updateSubscribeButton() called with: isSubscribed = [" + isSubscribed + "]");
private void updateSubscribeButton(final boolean isSubscribed) {
if (DEBUG) {
Log.d(TAG, "updateSubscribeButton() called with: "
+ "isSubscribed = [" + isSubscribed + "]");
}
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
int backgroundDuration = isButtonVisible ? 300 : 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 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);
if (!isSubscribed) {
headerSubscribeButton.setText(R.string.subscribe_button_title);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground, subscribeBackground);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground,
subscribeBackground);
animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText);
} else {
headerSubscribeButton.setText(R.string.subscribed_button_title);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground, subscribedBackground);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground,
subscribedBackground);
animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText);
}
@ -344,7 +375,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
@Override
protected Single<ChannelInfo> loadResult(boolean forceLoad) {
protected Single<ChannelInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
}
@ -356,47 +387,55 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
public void showLoading() {
super.showLoading();
imageLoader.cancelDisplayTask(headerChannelBanner);
imageLoader.cancelDisplayTask(headerAvatarView);
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
animateView(headerSubscribeButton, false, 100);
}
@Override
public void handleResult(@NonNull ChannelInfo result) {
public void handleResult(@NonNull final ChannelInfo result) {
super.handleResult(result);
headerRootLayout.setVisibility(View.VISIBLE);
imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner,
IMAGE_LOADER.displayImage(result.getBannerUrl(), headerChannelBanner,
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerSubscribersTextView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
headerSubscribersTextView.setText(Localization.shortSubscriberCount(activity, result.getSubscriberCount()));
headerSubscribersTextView.setText(Localization
.shortSubscriberCount(activity, result.getSubscriberCount()));
} else {
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);
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 (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
if (disposables != null) {
disposables.clear();
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
updateSubscription(result);
monitorSubscription(result);
headerPlayAllButton.setOnClickListener(
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener(
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
.playOnMainPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnClickListener(view -> NavigationHelper
.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
.playOnBackgroundPlayer(activity, getPlayQueue(), false));
}
private PlayQueue getPlayQueue() {
@ -410,17 +449,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
streamItems.add((StreamInfoItem) i);
}
}
return new ChannelPlayQueue(
currentInfo.getServiceId(),
currentInfo.getUrl(),
currentInfo.getNextPageUrl(),
streamItems,
index
);
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getNextPageUrl(), streamItems, index);
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
@ -437,8 +471,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
int errorId = exception instanceof ExtractionException
? R.string.parsing_error : R.string.general_error;
@ -454,8 +490,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
public void setTitle(final String 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.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
@ -23,17 +24,12 @@ import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private CompositeDisposable disposables = new CompositeDisposable();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
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();
instance.setInitialData(serviceId, url, name);
return instance;
@ -44,28 +40,31 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
}
@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);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
if (disposables != null) {
disposables.clear();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@ -76,7 +75,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
}
@Override
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
protected Single<CommentsInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
}
@ -90,27 +89,28 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
}
@Override
public void handleResult(@NonNull CommentsInfo result) {
public void handleResult(@NonNull final CommentsInfo result) {
super.handleResult(result);
AnimationUtils.slideUp(getView(),120, 150, 0.06f);
AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
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
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(serviceId),
"Get next page of: " + url,
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(serviceId), "Get next page of: " + url,
R.string.general_error);
}
}
@ -120,11 +120,14 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
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;
}
@ -133,14 +136,10 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
return;
}
public void setTitle(final String title) { }
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
return;
}
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { }
@Override
protected boolean isGridLayout() {

View File

@ -10,9 +10,8 @@ import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ServiceHelper;
public class DefaultKioskFragment extends KioskFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (serviceId < 0) {
@ -25,7 +24,9 @@ public class DefaultKioskFragment extends KioskFragment {
super.onResume();
if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) {
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
updateSelectedDefaultKiosk();
reloadContent();
}
@ -45,7 +46,8 @@ public class DefaultKioskFragment extends KioskFragment {
currentInfo = null;
currentNextPageUrl = null;
} 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;
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.Menu;
import android.view.MenuInflater;
import android.view.View;
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.extractor.ListExtractor;
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.
*
* <p>
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
* KioskFragment.java is part of NewPipe.
*
* </p>
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* </p>
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* </p>
* <p>
* 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> {
@State
protected String kioskId = "";
protected String kioskTranslatedName;
String kioskId = "";
String kioskTranslatedName;
@State
protected ContentCountry contentCountry;
ContentCountry contentCountry;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
public static KioskFragment getInstance(int serviceId)
throws ExtractionException {
public static KioskFragment getInstance(final int serviceId) throws ExtractionException {
return getInstance(serviceId, NewPipe.getService(serviceId)
.getKioskList()
.getDefaultKioskId());
.getKioskList().getDefaultKioskId());
}
public static KioskFragment getInstance(int serviceId, String kioskId)
public static KioskFragment getInstance(final int serviceId, final String kioskId)
throws ExtractionException {
KioskFragment instance = new KioskFragment();
StreamingService service = NewPipe.getService(serviceId);
@ -88,7 +87,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity);
@ -97,9 +96,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(useAsFrontPage && isVisibleToUser && activity != null) {
if (useAsFrontPage && isVisibleToUser && activity != null) {
try {
setTitle(kioskTranslatedName);
} catch (Exception e) {
@ -111,7 +110,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
}
@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);
}
@ -129,7 +130,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null && useAsFrontPage) {
@ -142,18 +143,14 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public Single<KioskInfo> loadResult(boolean forceReload) {
public Single<KioskInfo> loadResult(final boolean forceReload) {
contentCountry = Localization.getPreferredContentCountry(requireContext());
return ExtractorHelper.getKioskInfo(serviceId,
url,
forceReload);
return ExtractorHelper.getKioskInfo(serviceId, url, forceReload);
}
@Override
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreKioskItems(serviceId,
url,
currentNextPageUrl);
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
}
/*//////////////////////////////////////////////////////////////////////////
@ -181,13 +178,13 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(),
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
, "Get next page of: " + url, 0);
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId),
"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.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@ -17,6 +14,10 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase;
@ -57,7 +58,6 @@ import io.reactivex.disposables.Disposables;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private CompositeDisposable disposables;
private Subscription bookmarkReactor;
private AtomicBoolean isBookmarkButtonReady;
@ -82,7 +82,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
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();
instance.setInitialData(serviceId, url, name);
return instance;
@ -93,17 +94,18 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
disposables = new CompositeDisposable();
isBookmarkButtonReady = new AtomicBoolean(false);
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase.getInstance(
requireContext()));
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase
.getInstance(requireContext()));
}
@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_playlist, container, false);
}
@ -112,7 +114,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
//////////////////////////////////////////////////////////////////////////*/
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);
headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout);
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
@ -129,21 +132,23 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle 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));
}
@Override
protected void showStreamDialog(StreamInfoItem item) {
protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext();
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) {
StreamDialogEntry.setEnabledEntries(
@ -160,21 +165,25 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
StreamDialogEntry.append_playlist,
StreamDialogEntry.share);
StreamDialogEntry.start_here_on_popup.setCustomAction(
(fragment, infoItem) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(infoItem), true));
StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItem) ->
NavigationHelper.playOnPopupPlayer(context,
getPlayQueueStartingAt(infoItem), true));
}
StreamDialogEntry.start_here_on_background.setCustomAction(
(fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(infoItem), true));
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) ->
NavigationHelper.playOnBackgroundPlayer(context,
getPlayQueueStartingAt(infoItem), true));
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
StreamDialogEntry.clickOn(which, this, item)).show();
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
(dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu +
"], inflater = [" + inflater + "]");
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_playlist, menu);
@ -185,10 +194,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override
public void onDestroyView() {
super.onDestroyView();
if (isBookmarkButtonReady != null) isBookmarkButtonReady.set(false);
if (isBookmarkButtonReady != null) {
isBookmarkButtonReady.set(false);
}
if (disposables != null) disposables.clear();
if (bookmarkReactor != null) bookmarkReactor.cancel();
if (disposables != null) {
disposables.clear();
}
if (bookmarkReactor != null) {
bookmarkReactor.cancel();
}
bookmarkReactor = null;
}
@ -197,7 +212,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.dispose();
if (disposables != null) {
disposables.dispose();
}
disposables = null;
remotePlaylistManager = null;
@ -215,12 +232,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
}
@Override
protected Single<PlaylistInfo> loadResult(boolean forceLoad) {
protected Single<PlaylistInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
@ -251,7 +268,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
animateView(headerRootLayout, false, 200);
animateView(itemsList, false, 100);
imageLoader.cancelDisplayTask(headerUploaderAvatar);
IMAGE_LOADER.cancelDisplayTask(headerUploaderAvatar);
animateView(headerUploaderLayout, false, 200);
}
@ -262,7 +279,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
animateView(headerRootLayout, true, 100);
animateView(headerUploaderLayout, true, 300);
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());
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
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);
}
playlistCtrl.setVisibility(View.VISIBLE);
imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos,
(int) result.getStreamCount(), (int) result.getStreamCount()));
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)
@ -321,8 +340,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private PlayQueue getPlayQueue(final int index) {
final List<StreamInfoItem> infoItems = new ArrayList<>();
for(InfoItem i : infoListAdapter.getItemsList()) {
if(i instanceof StreamInfoItem) {
for (InfoItem i : infoListAdapter.getItemsList()) {
if (i instanceof StreamInfoItem) {
infoItems.add((StreamInfoItem) i);
}
}
@ -336,12 +355,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
, "Get next page of: " + url, 0);
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(serviceId), "Get next page of: " + url, 0);
}
}
@ -350,15 +369,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception,
UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(serviceId),
url,
errorId);
int errorId = exception instanceof ExtractionException
? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
NewPipe.getNameOfService(serviceId), url, errorId);
return true;
}
@ -366,13 +385,18 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
// Utils
//////////////////////////////////////////////////////////////////////////*/
private Flowable<Integer> getUpdateProcessor(@NonNull List<PlaylistRemoteEntity> playlists,
@NonNull PlaylistInfo result) {
private Flowable<Integer> getUpdateProcessor(
@NonNull final List<PlaylistRemoteEntity> playlists,
@NonNull final PlaylistInfo result) {
final Flowable<Integer> noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1);
if (playlists.isEmpty()) return noItemToUpdate;
if (playlists.isEmpty()) {
return noItemToUpdate;
}
final PlaylistRemoteEntity playlistEntity = playlists.get(0);
if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate;
final PlaylistRemoteEntity playlistRemoteEntity = playlists.get(0);
if (playlistRemoteEntity.isIdenticalTo(result)) {
return noItemToUpdate;
}
return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable();
}
@ -380,56 +404,59 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private Subscriber<List<PlaylistRemoteEntity>> getPlaylistBookmarkSubscriber() {
return new Subscriber<List<PlaylistRemoteEntity>>() {
@Override
public void onSubscribe(Subscription s) {
if (bookmarkReactor != null) bookmarkReactor.cancel();
public void onSubscribe(final Subscription s) {
if (bookmarkReactor != null) {
bookmarkReactor.cancel();
}
bookmarkReactor = s;
bookmarkReactor.request(1);
}
@Override
public void onNext(List<PlaylistRemoteEntity> playlist) {
public void onNext(final List<PlaylistRemoteEntity> playlist) {
playlistEntity = playlist.isEmpty() ? null : playlist.get(0);
updateBookmarkButtons();
isBookmarkButtonReady.set(true);
if (bookmarkReactor != null) bookmarkReactor.request(1);
if (bookmarkReactor != null) {
bookmarkReactor.request(1);
}
}
@Override
public void onError(Throwable t) {
public void onError(final Throwable t) {
PlaylistFragment.this.onError(t);
}
@Override
public void onComplete() {
}
public void onComplete() { }
};
}
@Override
public void setTitle(String title) {
public void setTitle(final String title) {
super.setTitle(title);
headerTitleView.setText(title);
}
private void onBookmarkClicked() {
if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get() ||
remotePlaylistManager == null)
if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get()
|| remotePlaylistManager == null) {
return;
}
final Disposable action;
if (currentInfo != null && playlistEntity == null) {
action = remotePlaylistManager.onBookmark(currentInfo)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> {/* Do nothing */}, this::onError);
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
} else if (playlistEntity != null) {
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> playlistEntity = null)
.subscribe(ignored -> {/* Do nothing */}, this::onError);
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
} else {
action = Disposables.empty();
}
@ -438,13 +465,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
}
private void updateBookmarkButtons() {
if (playlistBookmarkButton == null || activity == null) return;
if (playlistBookmarkButton == null || activity == null) {
return;
}
final int iconAttr = playlistEntity == null ?
R.attr.ic_playlist_add : R.attr.ic_playlist_check;
final int iconAttr = playlistEntity == null
? R.attr.ic_playlist_add : R.attr.ic_playlist_check;
final int titleRes = playlistEntity == null ?
R.string.bookmark_playlist : R.string.unbookmark_playlist;
final int titleRes = playlistEntity == null
? R.string.bookmark_playlist : R.string.unbookmark_playlist;
playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
playlistBookmarkButton.setTitle(titleRes);

View File

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

View File

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

View File

@ -2,36 +2,32 @@ package org.schabi.newpipe.fragments.list.search;
import android.content.Context;
import android.content.res.TypedArray;
import androidx.annotation.AttrRes;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import java.util.ArrayList;
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 Context context;
private OnSuggestionItemSelected listener;
private boolean showSuggestionHistory = true;
public interface OnSuggestionItemSelected {
void onSuggestionItemSelected(SuggestionItem item);
void onSuggestionItemInserted(SuggestionItem item);
void onSuggestionItemLongClick(SuggestionItem item);
}
public SuggestionListAdapter(Context context) {
public SuggestionListAdapter(final Context context) {
this.context = context;
}
public void setItems(List<SuggestionItem> items) {
public void setItems(final List<SuggestionItem> items) {
this.items.clear();
if (showSuggestionHistory) {
this.items.addAll(items);
@ -46,36 +42,43 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
notifyDataSetChanged();
}
public void setListener(OnSuggestionItemSelected listener) {
public void setListener(final OnSuggestionItemSelected listener) {
this.listener = listener;
}
public void setShowSuggestionHistory(boolean v) {
public void setShowSuggestionHistory(final boolean v) {
showSuggestionHistory = v;
}
@Override
public SuggestionItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new SuggestionItemHolder(LayoutInflater.from(context).inflate(R.layout.item_search_suggestion, parent, false));
public SuggestionItemHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new SuggestionItemHolder(LayoutInflater.from(context)
.inflate(R.layout.item_search_suggestion, parent, false));
}
@Override
public void onBindViewHolder(SuggestionItemHolder holder, int position) {
public void onBindViewHolder(final SuggestionItemHolder holder, final int position) {
final SuggestionItem currentItem = getItem(position);
holder.updateFrom(currentItem);
holder.queryView.setOnClickListener(v -> {
if (listener != null) listener.onSuggestionItemSelected(currentItem);
if (listener != null) {
listener.onSuggestionItemSelected(currentItem);
}
});
holder.queryView.setOnLongClickListener(v -> {
if (listener != null) listener.onSuggestionItemLongClick(currentItem);
return true;
if (listener != null) {
listener.onSuggestionItemLongClick(currentItem);
}
return true;
});
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);
}
@ -88,7 +91,15 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
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 ImageView suggestionIcon;
private final View queryView;
@ -98,7 +109,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
private final int historyResId;
private final int searchResId;
private SuggestionItemHolder(View rootView) {
private SuggestionItemHolder(final View rootView) {
super(rootView);
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
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);
}
private void updateFrom(SuggestionItem item) {
suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId);
itemSuggestionQuery.setText(item.query);
}
private static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) {
private static int resolveResourceIdFromAttr(final Context context,
@AttrRes final int attr) {
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
int attributeResourceId = a.getResourceId(0, 0);
a.recycle();
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.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -14,6 +12,9 @@ import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
@ -28,8 +29,9 @@ import java.io.Serializable;
import io.reactivex.Single;
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 RelatedStreamInfo relatedStreamInfo;
/*//////////////////////////////////////////////////////////////////////////
@ -37,44 +39,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
//////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout;
private Switch aSwitch;
private boolean mIsVisibleToUser = false;
public static RelatedVideosFragment getInstance(StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
public static RelatedVideosFragment getInstance(final StreamInfo info) {
RelatedVideosFragment instance = new RelatedVideosFragment();
instance.setInitialData(info);
return instance;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
}
@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);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
if (disposables != null) {
disposables.clear();
}
}
protected View getListHeader(){
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
protected View getListHeader() {
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.related_streams_header, itemsList, false);
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
@ -82,14 +88,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
aSwitch.setChecked(autoplay);
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
prefEdit.apply();
public void onCheckedChanged(final CompoundButton compoundButton,
final boolean b) {
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
.putBoolean(getString(R.string.auto_queue_key), b).apply();
}
});
return headerRootLayout;
}else{
} else {
return null;
}
}
@ -99,38 +105,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
}
@Override
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<RelatedStreamInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
}
@Override
public void showLoading() {
super.showLoading();
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
if (headerRootLayout != null) {
headerRootLayout.setVisibility(View.INVISIBLE);
}
}
@Override
public void handleResult(@NonNull RelatedStreamInfo result) {
public void handleResult(@NonNull final RelatedStreamInfo result) {
super.handleResult(result);
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
if (headerRootLayout != null) {
headerRootLayout.setVisibility(View.VISIBLE);
}
AnimationUtils.slideUp(getView(), 120, 96, 0.06f);
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
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
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
//////////////////////////////////////////////////////////////////////////*/
@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;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
return;
}
private void setInitialData(StreamInfo info) {
private void setInitialData(final StreamInfo info) {
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
public void onSaveInstanceState(Bundle outState) {
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedStreamInfo);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
if (savedState != null) {
Serializable serializable = savedState.getSerializable(INFO_KEY);
if(serializable instanceof RelatedStreamInfo){
if (serializable instanceof RelatedStreamInfo) {
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String s) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if(null != aSwitch) aSwitch.setChecked(autoplay);
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if (null != aSwitch) {
aSwitch.setChecked(autoplay);
}
}
@Override

View File

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

View File

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

View File

@ -1,13 +1,14 @@
package org.schabi.newpipe.info_list;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
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.extractor.InfoItem;
@ -83,42 +84,33 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private View header = null;
private View footer = null;
public class HFHolder extends RecyclerView.ViewHolder {
public View view;
public HFHolder(View v) {
super(v);
view = v;
}
}
public InfoListAdapter(Context context) {
public InfoListAdapter(final Context context) {
this.recordManager = new HistoryRecordManager(context);
infoItemBuilder = new InfoItemBuilder(context);
infoItemList = new ArrayList<>();
}
public void setOnStreamSelectedListener(OnClickGesture<StreamInfoItem> listener) {
public void setOnStreamSelectedListener(final OnClickGesture<StreamInfoItem> listener) {
infoItemBuilder.setOnStreamSelectedListener(listener);
}
public void setOnChannelSelectedListener(OnClickGesture<ChannelInfoItem> listener) {
public void setOnChannelSelectedListener(final OnClickGesture<ChannelInfoItem> listener) {
infoItemBuilder.setOnChannelSelectedListener(listener);
}
public void setOnPlaylistSelectedListener(OnClickGesture<PlaylistInfoItem> listener) {
public void setOnPlaylistSelectedListener(final OnClickGesture<PlaylistInfoItem> listener) {
infoItemBuilder.setOnPlaylistSelectedListener(listener);
}
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
public void setOnCommentsSelectedListener(final OnClickGesture<CommentsInfoItem> listener) {
infoItemBuilder.setOnCommentsSelectedListener(listener);
}
public void useMiniItemVariants(boolean useMiniVariant) {
public void setUseMiniVariant(final boolean useMiniVariant) {
this.useMiniVariant = useMiniVariant;
}
public void setGridItemVariants(boolean useGridVariant) {
public void setUseGridVariant(final boolean useGridVariant) {
this.useGridVariant = useGridVariant;
}
@ -126,55 +118,67 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
if (data == null) {
return;
}
if (DEBUG) Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " +
infoItemList.size() + ", data.size() = " + data.size());
if (DEBUG) {
Log.d(TAG, "addInfoItemList() before > infoItemList.size() = "
+ infoItemList.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeaderOffset();
infoItemList.addAll(data);
if (DEBUG) Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart +
", infoItemList.size() = " + infoItemList.size() +
", header = " + header + ", footer = " + footer +
", showFooter = " + showFooter);
if (DEBUG) {
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", "
+ "infoItemList.size() = " + infoItemList.size() + ", "
+ "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart +
" to " + footerNow);
if (DEBUG) {
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.addAll(data);
notifyDataSetChanged();
}
public void addInfoItem(@Nullable InfoItem data) {
public void addInfoItem(@Nullable final InfoItem data) {
if (data == null) {
return;
}
if (DEBUG) Log.d(TAG, "addInfoItem() before > infoItemList.size() = " +
infoItemList.size() + ", thread = " + Thread.currentThread());
if (DEBUG) {
Log.d(TAG, "addInfoItem() before > infoItemList.size() = "
+ infoItemList.size() + ", thread = " + Thread.currentThread());
}
int positionInserted = sizeConsideringHeaderOffset();
infoItemList.add(data);
if (DEBUG) Log.d(TAG, "addInfoItem() after > position = " + positionInserted +
", infoItemList.size() = " + infoItemList.size() +
", header = " + header + ", footer = " + footer +
", showFooter = " + showFooter);
if (DEBUG) {
Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", "
+ "infoItemList.size() = " + infoItemList.size() + ", "
+ "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
notifyItemInserted(positionInserted);
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeaderOffset();
notifyItemMoved(positionInserted, footerNow);
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted +
" to " + footerNow);
if (DEBUG) {
Log.d(TAG, "addInfoItem() footer from " + positionInserted
+ " to " + footerNow);
}
}
}
@ -186,29 +190,39 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyDataSetChanged();
}
public void setHeader(View header) {
public void setHeader(final View header) {
boolean changed = header != this.header;
this.header = header;
if (changed) notifyDataSetChanged();
if (changed) {
notifyDataSetChanged();
}
}
public void setFooter(View view) {
public void setFooter(final View view) {
this.footer = view;
}
public void showFooter(boolean show) {
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]");
if (show == showFooter) return;
public void showFooter(final boolean show) {
if (DEBUG) {
Log.d(TAG, "showFooter() called with: show = [" + show + "]");
}
if (show == showFooter) {
return;
}
showFooter = show;
if (show) notifyItemInserted(sizeConsideringHeaderOffset());
else notifyItemRemoved(sizeConsideringHeaderOffset());
if (show) {
notifyItemInserted(sizeConsideringHeaderOffset());
} else {
notifyItemRemoved(sizeConsideringHeaderOffset());
}
}
private int sizeConsideringHeaderOffset() {
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;
}
@ -219,18 +233,27 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@Override
public int getItemCount() {
int count = infoItemList.size();
if (header != null) count++;
if (footer != null && showFooter) count++;
if (header != null) {
count++;
}
if (footer != null && showFooter) {
count++;
}
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;
}
@Override
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) {
return HEADER_TYPE;
@ -243,11 +266,14 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
final InfoItem item = infoItemList.get(position);
switch (item.getInfoType()) {
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:
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:
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:
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
default:
@ -257,9 +283,12 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
if (DEBUG)
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
final int type) {
if (DEBUG) {
Log.d(TAG, "onCreateViewHolder() called with: "
+ "parent = [" + parent + "], type = [" + type + "]");
}
switch (type) {
case HEADER_TYPE:
return new HFHolder(header);
@ -293,28 +322,38 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + 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 (holder instanceof InfoItemHolder) {
// 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);
} else if (holder instanceof HFHolder && position == 0 && header != null) {
((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;
}
}
@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) {
for (Object payload : payloads) {
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) {
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager);
((InfoItemHolder) holder).updateState(infoItemList
.get(header == null ? position : position - 1), recordManager);
}
}
} else {
@ -325,10 +364,19 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
public int getSpanSize(final int position) {
final int type = getItemViewType(position);
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;
public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder {
public ChannelGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_grid_item, parent);
}
public ChannelGridInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup 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 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);
itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView);
}
@Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof ChannelInfoItem)) return;
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
if (!(infoItem instanceof ChannelInfoItem)) {
return;
}
final ChannelInfoItem item;
item = (ChannelInfoItem) infoItem;
itemChannelDescriptionView.setText(item.getDescription());
}

View File

@ -16,9 +16,10 @@ import de.hdodenhof.circleimageview.CircleImageView;
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
public final CircleImageView itemThumbnailView;
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);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -26,13 +27,17 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
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);
}
@Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof ChannelInfoItem)) return;
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof ChannelInfoItem)) {
return;
}
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
itemTitleView.setText(item.getName());

View File

@ -30,23 +30,24 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
*/
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
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);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
}
@Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof CommentsInfoItem)) return;
if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getAuthorName());
}
}

View File

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

View File

@ -1,9 +1,10 @@
package org.schabi.newpipe.info_list.holder;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
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 {
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));
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;
public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder {
public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
}
public PlaylistGridInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup 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;
public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder {
public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
public PlaylistInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup 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 final ImageView itemThumbnailView;
public final TextView itemStreamCountView;
private final TextView itemStreamCountView;
public final TextView itemTitleView;
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);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
@ -26,13 +27,17 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
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);
}
@Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof PlaylistInfoItem)) return;
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof PlaylistInfoItem)) {
return;
}
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
itemTitleView.setText(item.getName());
@ -41,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
itemBuilder.getImageLoader()
.displayImage(item.getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnPlaylistSelectedListener() != null) {

View File

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

View File

@ -20,39 +20,46 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamInfoItemHolder.java is part of NewPipe.
* </p>
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* </p>
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* </p?
* <p>
* 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 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);
}
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
}
@Override
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof StreamInfoItem)) return;
if (!(infoItem instanceof StreamInfoItem)) {
return;
}
final StreamInfoItem item = (StreamInfoItem) infoItem;
itemAdditionalDetails.setText(getStreamInfoDetailLine(item));
@ -62,11 +69,14 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
String viewsAndDate = "";
if (infoItem.getViewCount() >= 0) {
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)) {
viewsAndDate = Localization.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount());
viewsAndDate = Localization
.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount());
} 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) {
if (infoItem.getUploadDate() != null) {
String formattedRelativeTime = Localization.relativeTime(infoItem.getUploadDate().date());
String formattedRelativeTime = Localization
.relativeTime(infoItem.getUploadDate().date());
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() + ")";
}
return formattedRelativeTime;

View File

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

View File

@ -5,16 +5,17 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
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.view.Menu;
import android.view.MenuInflater;
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.fragments.BaseStateFragment;
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
* {@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.
*
* <p>
* This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is
* 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>
implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
@ -36,21 +41,19 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
// Views
//////////////////////////////////////////////////////////////////////////*/
protected View headerRootView;
protected View footerRootView;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
private View headerRootView;
private View footerRootView;
protected LocalItemListAdapter itemListAdapter;
protected RecyclerView itemsList;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
/*//////////////////////////////////////////////////////////////////////////
// Lifecycle - Creation
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
@ -70,8 +73,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid);
itemsList.setLayoutManager(
useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setUseGridVariant(useGrid);
itemListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
@ -94,7 +98,8 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
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);
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
return lm;
@ -105,7 +110,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
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.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid);
itemListAdapter.setHeader(headerRootView = getListHeader());
itemListAdapter.setFooter(footerRootView = getListFooter());
itemListAdapter.setUseGridVariant(useGrid);
headerRootView = getListHeader();
itemListAdapter.setHeader(headerRootView);
footerRootView = getListFooter();
itemListAdapter.setFooter(footerRootView);
itemsList.setAdapter(itemListAdapter);
}
@ -131,13 +138,17 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater 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 + "]");
}
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar == null) return;
if (supportActionBar == null) {
return;
}
supportActionBar.setDisplayShowTitleEnabled(true);
}
@ -158,7 +169,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/
@Override
public void startLoading(boolean forceLoad) {
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
resetFragment();
}
@ -166,24 +177,36 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
@Override
public void showLoading() {
super.showLoading();
if (itemsList != null) animateView(itemsList, false, 200);
if (headerRootView != null) animateView(headerRootView, false, 200);
if (itemsList != null) {
animateView(itemsList, false, 200);
}
if (headerRootView != null) {
animateView(headerRootView, false, 200);
}
}
@Override
public void hideLoading() {
super.hideLoading();
if (itemsList != null) animateView(itemsList, true, 200);
if (headerRootView != null) animateView(headerRootView, true, 200);
if (itemsList != null) {
animateView(itemsList, true, 200);
}
if (headerRootView != null) {
animateView(headerRootView, true, 200);
}
}
@Override
public void showError(String message, boolean showRetryButton) {
public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton);
showListFooter(false);
if (itemsList != null) animateView(itemsList, false, 200);
if (headerRootView != null) animateView(headerRootView, false, 200);
if (itemsList != null) {
animateView(itemsList, false, 200);
}
if (headerRootView != null) {
animateView(headerRootView, false, 200);
}
}
@Override
@ -194,14 +217,18 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
@Override
public void showListFooter(final boolean show) {
if (itemsList == null) return;
if (itemsList == null) {
return;
}
itemsList.post(() -> {
if (itemListAdapter != null) itemListAdapter.showFooter(show);
if (itemListAdapter != null) {
itemListAdapter.showFooter(show);
}
});
}
@Override
public void handleNextItems(N result) {
public void handleNextItems(final N result) {
isLoading.set(false);
}
@ -210,30 +237,35 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
//////////////////////////////////////////////////////////////////////////*/
protected void resetFragment() {
if (itemListAdapter != null) itemListAdapter.clearStreamItemList();
if (itemListAdapter != null) {
itemListAdapter.clearStreamItemList();
}
}
@Override
protected boolean onError(Throwable exception) {
protected boolean onError(final Throwable exception) {
resetFragment();
return super.onError(exception);
}
@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))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
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));
if ("auto".equals(list_mode)) {
final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.list_view_mode_key),
getString(R.string.list_view_mode_value));
if ("auto".equals(listMode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else {
return "grid".equals(list_mode);
return "grid".equals(listMode);
}
}
}

View File

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

View File

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

View File

@ -1,13 +1,14 @@
package org.schabi.newpipe.local;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
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.stream.model.StreamStateEntity;
@ -50,7 +51,6 @@ import java.util.List;
*/
public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = LocalItemListAdapter.class.getSimpleName();
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 LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
private final LocalItemBuilder localItemBuilder;
private final ArrayList<LocalItem> localItems;
@ -76,7 +76,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private View header = null;
private View footer = null;
public LocalItemListAdapter(Context context) {
public LocalItemListAdapter(final Context context) {
recordManager = new HistoryRecordManager(context);
localItemBuilder = new LocalItemBuilder(context);
localItems = new ArrayList<>();
@ -84,7 +84,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
Localization.getPreferredLocale(context));
}
public void setSelectedListener(OnClickGesture<LocalItem> listener) {
public void setSelectedListener(final OnClickGesture<LocalItem> listener) {
localItemBuilder.setOnItemSelectedListener(listener);
}
@ -92,28 +92,34 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
localItemBuilder.setOnItemSelectedListener(null);
}
public void addItems(@Nullable List<? extends LocalItem> data) {
public void addItems(@Nullable final List<? extends LocalItem> data) {
if (data == null) {
return;
}
if (DEBUG) Log.d(TAG, "addItems() before > localItems.size() = " +
localItems.size() + ", data.size() = " + data.size());
if (DEBUG) {
Log.d(TAG, "addItems() before > localItems.size() = "
+ localItems.size() + ", data.size() = " + data.size());
}
int offsetStart = sizeConsideringHeader();
localItems.addAll(data);
if (DEBUG) Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
", localItems.size() = " + localItems.size() +
", header = " + header + ", footer = " + footer +
", showFooter = " + showFooter);
if (DEBUG) {
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", "
+ "localItems.size() = " + localItems.size() + ", "
+ "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
notifyItemRangeInserted(offsetStart, data.size());
if (footer != null && showFooter) {
int footerNow = sizeConsideringHeader();
notifyItemMoved(offsetStart, footerNow);
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
" to " + footerNow);
if (DEBUG) {
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));
}
public boolean swapItems(int fromAdapterPosition, int toAdapterPosition) {
public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) {
final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition);
final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition);
if (actualFrom < 0 || actualTo < 0) return false;
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) return false;
if (actualFrom < 0 || actualTo < 0) {
return false;
}
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) {
return false;
}
localItems.add(actualTo, localItems.remove(actualFrom));
notifyItemMoved(fromAdapterPosition, toAdapterPosition);
@ -143,27 +153,36 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
notifyDataSetChanged();
}
public void setGridItemVariants(boolean useGridVariant) {
public void setUseGridVariant(final boolean useGridVariant) {
this.useGridVariant = useGridVariant;
}
public void setHeader(View header) {
public void setHeader(final View header) {
boolean changed = header != this.header;
this.header = header;
if (changed) notifyDataSetChanged();
if (changed) {
notifyDataSetChanged();
}
}
public void setFooter(View view) {
public void setFooter(final View view) {
this.footer = view;
}
public void showFooter(boolean show) {
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]");
if (show == showFooter) return;
public void showFooter(final boolean show) {
if (DEBUG) {
Log.d(TAG, "showFooter() called with: show = [" + show + "]");
}
if (show == showFooter) {
return;
}
showFooter = show;
if (show) notifyItemInserted(sizeConsideringHeader());
else notifyItemRemoved(sizeConsideringHeader());
if (show) {
notifyItemInserted(sizeConsideringHeader());
} else {
notifyItemRemoved(sizeConsideringHeader());
}
}
private int adapterOffsetWithoutHeader(final int offset) {
@ -181,21 +200,27 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
@Override
public int getItemCount() {
int count = localItems.size();
if (header != null) count++;
if (footer != null && showFooter) count++;
if (header != null) {
count++;
}
if (footer != null && showFooter) {
count++;
}
if (DEBUG) {
Log.d(TAG, "getItemCount() called, count = " + count +
", localItems.size() = " + localItems.size() +
", header = " + header + ", footer = " + footer +
", showFooter = " + showFooter);
Log.d(TAG, "getItemCount() called, count = " + count + ", "
+ "localItems.size() = " + localItems.size() + ", "
+ "header = " + header + ", footer = " + footer + ", "
+ "showFooter = " + showFooter);
}
return count;
}
@Override
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) {
return HEADER_TYPE;
@ -208,23 +233,34 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
final LocalItem item = localItems.get(position);
switch (item.getLocalItemType()) {
case PLAYLIST_LOCAL_ITEM: 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_LOCAL_ITEM:
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 STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
case PLAYLIST_STREAM_ITEM:
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:
Log.e(TAG, "No holder type has been considered for item: [" +
item.getLocalItemType() + "]");
Log.e(TAG, "No holder type has been considered for item: ["
+ item.getLocalItemType() + "]");
return -1;
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" +
parent + "], type = [" + type + "]");
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
final int type) {
if (DEBUG) {
Log.d(TAG, "onCreateViewHolder() called with: "
+ "parent = [" + parent + "], type = [" + type + "]");
}
switch (type) {
case HEADER_TYPE:
return new HeaderFooterHolder(header);
@ -253,15 +289,21 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" +
holder.getClass().getSimpleName() + "], position = [" + 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 (holder instanceof LocalItemHolder) {
// 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) {
((HeaderFooterHolder) holder).view = header;
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
@ -271,13 +313,16 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
}
@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) {
for (Object payload : payloads) {
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) {
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager);
((LocalItemHolder) holder).updateState(localItems
.get(header == null ? position : position - 1), recordManager);
}
}
} else {
@ -288,7 +333,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
public int getSpanSize(final int position) {
final int type = getItemViewType(position);
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.Parcelable;
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.View;
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.Subscription;
import org.schabi.newpipe.NewPipeDatabase;
@ -39,10 +39,9 @@ import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public final class BookmarkFragment
extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
@State
protected Parcelable itemsListState;
@ -55,10 +54,26 @@ public final class BookmarkFragment
// 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
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (activity == null) return;
if (activity == null) {
return;
}
final AppDatabase database = NewPipeDatabase.getInstance(activity);
localPlaylistManager = new LocalPlaylistManager(database);
remotePlaylistManager = new RemotePlaylistManager(database);
@ -67,41 +82,44 @@ public final class BookmarkFragment
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
final Bundle savedInstanceState) {
if(!useAsFrontPage) {
if (!useAsFrontPage) {
setTitle(activity.getString(R.string.tab_bookmarks));
}
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
///////////////////////////////////////////////////////////////////////////
@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);
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
///////////////////////////////////////////////////////////////////////////
@Override
protected void initListeners() {
super.initListeners();
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override
public void selected(LocalItem selectedItem) {
public void selected(final LocalItem selectedItem) {
final FragmentManager fragmentManager = getFM();
if (selectedItem instanceof PlaylistMetadataEntry) {
@ -120,7 +138,7 @@ public final class BookmarkFragment
}
@Override
public void held(LocalItem selectedItem) {
public void held(final LocalItem selectedItem) {
if (selectedItem instanceof PlaylistMetadataEntry) {
showLocalDialog((PlaylistMetadataEntry) selectedItem);
} else if (selectedItem instanceof PlaylistRemoteEntity) {
@ -131,26 +149,20 @@ public final class BookmarkFragment
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
// Fragment LifeCycle - Destruction
///////////////////////////////////////////////////////////////////////////
@Override
public void startLoading(boolean forceLoad) {
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
Flowable.combineLatest(
localPlaylistManager.getPlaylists(),
remotePlaylistManager.getPlaylists(),
BookmarkFragment::merge
).onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistsSubscriber());
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistsSubscriber());
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Destruction
///////////////////////////////////////////////////////////////////////////
@Override
public void onPause() {
super.onPause();
@ -161,16 +173,26 @@ public final class BookmarkFragment
public void onDestroyView() {
super.onDestroyView();
if (disposables != null) disposables.clear();
if (databaseSubscription != null) databaseSubscription.cancel();
if (disposables != null) {
disposables.clear();
}
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = null;
}
///////////////////////////////////////////////////////////////////////////
// Subscriptions Loader
///////////////////////////////////////////////////////////////////////////
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.dispose();
if (disposables != null) {
disposables.dispose();
}
disposables = null;
localPlaylistManager = null;
@ -178,39 +200,41 @@ public final class BookmarkFragment
itemsListState = null;
}
///////////////////////////////////////////////////////////////////////////
// Subscriptions Loader
///////////////////////////////////////////////////////////////////////////
private Subscriber<List<PlaylistLocalItem>> getPlaylistsSubscriber() {
return new Subscriber<List<PlaylistLocalItem>>() {
@Override
public void onSubscribe(Subscription s) {
public void onSubscribe(final Subscription s) {
showLoading();
if (databaseSubscription != null) databaseSubscription.cancel();
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = s;
databaseSubscription.request(1);
}
@Override
public void onNext(List<PlaylistLocalItem> subscriptions) {
public void onNext(final List<PlaylistLocalItem> subscriptions) {
handleResult(subscriptions);
if (databaseSubscription != null) databaseSubscription.request(1);
if (databaseSubscription != null) {
databaseSubscription.request(1);
}
}
@Override
public void onError(Throwable exception) {
public void onError(final Throwable exception) {
BookmarkFragment.this.onError(exception);
}
@Override
public void onComplete() {
}
public void onComplete() { }
};
}
///////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
@Override
public void handleResult(@NonNull List<PlaylistLocalItem> result) {
public void handleResult(@NonNull final List<PlaylistLocalItem> result) {
super.handleResult(result);
itemListAdapter.clearStreamItemList();
@ -227,55 +251,58 @@ public final class BookmarkFragment
}
hideLoading();
}
///////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
"none", "Bookmark", R.string.general_error);
return true;
}
@Override
protected void resetFragment() {
super.resetFragment();
if (disposables != null) disposables.clear();
}
///////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////
@Override
protected void resetFragment() {
super.resetFragment();
if (disposables != null) {
disposables.clear();
}
}
private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
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);
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
editText.setText(selectedItem.name);
Builder builder = new AlertDialog.Builder(activity);
builder.setView(dialogView)
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
})
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.delete, (dialog, which) -> {
showDeleteDialog(selectedItem.name,
localPlaylistManager.deletePlaylist(selectedItem.uid));
dialog.dismiss();
})
.create()
.show();
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
})
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.delete, (dialog, which) -> {
showDeleteDialog(selectedItem.name,
localPlaylistManager.deletePlaylist(selectedItem.uid));
dialog.dismiss();
})
.create()
.show();
}
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)
.setTitle(name)
@ -284,40 +311,27 @@ public final class BookmarkFragment
.setPositiveButton(R.string.delete, (dialog, i) ->
disposables.add(deleteReactor
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> {/*Do nothing on success*/}, this::onError))
.subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
)
.setNegativeButton(R.string.cancel, null)
.show();
}
private void changeLocalPlaylistName(long id, String name) {
private void changeLocalPlaylistName(final long id, final String name) {
if (localPlaylistManager == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "Updating playlist id=[" + id +
"] with new name=[" + name + "] items");
Log.d(TAG, "Updating playlist id=[" + id + "] "
+ "with new name=[" + name + "] items");
}
localPlaylistManager.renamePlaylist(id, name);
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(longs -> {/*Do nothing on success*/}, this::onError);
.observeOn(AndroidSchedulers.mainThread())
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
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
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_playlists, container);
}
@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);
final LocalPlaylistManager playlistManager =
@ -84,9 +84,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
playlistAdapter = new LocalItemListAdapter(getActivity());
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override
public void selected(LocalItem selectedItem) {
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null)
public void selected(final LocalItem selectedItem) {
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) {
return;
}
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem,
getStreams());
}
@ -126,7 +127,9 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
//////////////////////////////////////////////////////////////////////////*/
public void openCreatePlaylistDialog() {
if (getStreams() == null || getFragmentManager() == null) return;
if (getStreams() == null || getFragmentManager() == null) {
return;
}
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG);
getDialog().dismiss();
@ -145,16 +148,19 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
}
}
private void onPlaylistSelected(@NonNull LocalPlaylistManager manager,
@NonNull PlaylistMetadataEntry playlist,
@NonNull List<StreamEntity> streams) {
if (getStreams() == null) return;
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
@NonNull final PlaylistMetadataEntry playlist,
@NonNull final List<StreamEntity> streams) {
if (getStreams() == null) {
return;
}
final Toast successToast = Toast.makeText(getContext(),
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
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())
.subscribe(ignored -> successToast.show()));
}

View File

@ -3,12 +3,13 @@ package org.schabi.newpipe.local.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
@ -19,8 +20,6 @@ import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
public final class PlaylistCreationDialog extends PlaylistDialog {
private static final String TAG = PlaylistCreationDialog.class.getCanonicalName();
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
dialog.setInfo(streams);
@ -33,8 +32,10 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getStreams() == null) return super.onCreateDialog(savedInstanceState);
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
if (getStreams() == null) {
return super.onCreateDialog(savedInstanceState);
}
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
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.os.Bundle;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import android.view.Window;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.util.StateSaver;
@ -14,7 +15,6 @@ import java.util.List;
import java.util.Queue;
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
private List<StreamEntity> streamEntities;
private StateSaver.SavedState savedState;
@ -32,7 +32,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
savedState = StateSaver.tryToRestore(savedInstanceState, this);
}
@ -45,7 +45,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
//remove title
final Window window = dialog.getWindow();
@ -66,18 +66,18 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
}
@Override
public void writeTo(Queue<Object> objectsToSave) {
public void writeTo(final Queue<Object> objectsToSave) {
objectsToSave.add(streamEntities);
}
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) {
public void readFrom(@NonNull final Queue<Object> savedObjects) {
streamEntities = (List<StreamEntity>) savedObjects.poll();
}
@Override
public void onSaveInstanceState(Bundle outState) {
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
if (getActivity() != null) {
savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(),

View File

@ -41,7 +41,9 @@ import java.util.*
class FeedFragment : BaseListFragment<FeedState, Unit>() {
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 groupName = ""
@ -49,13 +51,14 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
init {
setHasOptionsMenu(true)
useDefaultStateSaving(false)
setUseDefaultStateSaving(false)
}
override fun onCreate(savedInstanceState: Bundle?) {
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) ?: ""
}
@ -107,7 +110,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
inflater.inflate(R.menu.menu_feed_fragment, menu)
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)
}
}
@ -324,4 +327,4 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
return feedFragment
}
}
}
}

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.history;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@ -14,19 +15,19 @@ import java.util.Date;
/**
* Adapter for history entries
* @param <E> the type of the entries
* This is an adapter for history entries.
*
* @param <E> the type of the entries
* @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 DateFormat mDateFormat;
private final Context mContext;
private OnHistoryItemClickListener<E> onHistoryItemClickListener = null;
public HistoryEntryAdapter(Context context) {
public HistoryEntryAdapter(final Context context) {
super();
mContext = context;
mEntries = new ArrayList<>();
@ -34,7 +35,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
Localization.getPreferredLocale(context));
}
public void setEntries(@NonNull Collection<E> historyEntries) {
public void setEntries(@NonNull final Collection<E> historyEntries) {
mEntries.clear();
mEntries.addAll(historyEntries);
notifyDataSetChanged();
@ -49,7 +50,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
notifyDataSetChanged();
}
protected String getFormattedDate(Date date) {
protected String getFormattedDate(final Date date) {
return mDateFormat.format(date);
}
@ -63,10 +64,10 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
}
@Override
public void onBindViewHolder(VH holder, int position) {
public void onBindViewHolder(final VH holder, final int position) {
final E entry = mEntries.get(position);
holder.itemView.setOnClickListener(v -> {
if(onHistoryItemClickListener != null) {
if (onHistoryItemClickListener != null) {
onHistoryItemClickListener.onHistoryItemClick(entry);
}
});
@ -83,14 +84,15 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
}
@Override
public void onViewRecycled(VH holder) {
public void onViewRecycled(final VH holder) {
super.onViewRecycled(holder);
holder.itemView.setOnClickListener(null);
}
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;
}
@ -100,6 +102,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
public interface OnHistoryItemClickListener<E> {
void onHistoryItemClick(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.SharedPreferences;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import org.schabi.newpipe.NewPipeDatabase;
@ -55,7 +56,6 @@ import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
public class HistoryRecordManager {
private final AppDatabase database;
private final StreamDAO streamTable;
private final StreamHistoryDAO streamHistoryTable;
@ -81,7 +81,9 @@ public class HistoryRecordManager {
///////////////////////////////////////////////////////
public Maybe<Long> onViewed(final StreamInfo info) {
if (!isStreamHistoryEnabled()) return Maybe.empty();
if (!isStreamHistoryEnabled()) {
return Maybe.empty();
}
final Date currentTime = new Date();
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
@ -149,7 +151,9 @@ public class HistoryRecordManager {
///////////////////////////////////////////////////////
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 SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
@ -231,11 +235,13 @@ public class HistoryRecordManager {
public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) {
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()) {
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()) {
return new StreamStateEntity[]{null};
}
@ -247,12 +253,14 @@ public class HistoryRecordManager {
return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
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()) {
result.add(null);
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()) {
result.add(null);
continue;
@ -263,7 +271,8 @@ public class HistoryRecordManager {
}).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(() -> {
final List<StreamStateEntity> result = new ArrayList<>(items.size());
for (LocalItem item : items) {
@ -278,7 +287,8 @@ public class HistoryRecordManager {
result.add(null);
continue;
}
final List<StreamStateEntity> states = streamStateTable.getState(streamId).blockingFirst();
final List<StreamStateEntity> states = streamStateTable.getState(streamId)
.blockingFirst();
if (states.isEmpty()) {
result.add(null);
continue;

View File

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

View File

@ -1,9 +1,10 @@
package org.schabi.newpipe.local.holder;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -33,14 +34,15 @@ import java.text.DateFormat;
public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
protected final LocalItemBuilder itemBuilder;
public LocalItemHolder(LocalItemBuilder itemBuilder, int layoutId, ViewGroup parent) {
super(LayoutInflater.from(itemBuilder.getContext())
.inflate(layoutId, parent, false));
public LocalItemHolder(final LocalItemBuilder itemBuilder, final int layoutId,
final ViewGroup parent) {
super(LayoutInflater.from(itemBuilder.getContext()).inflate(layoutId, parent, false));
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;
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
public LocalPlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder,
final ViewGroup 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;
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
public LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup 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);
}
@Override
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistMetadataEntry)) return;
public void updateFromItem(final LocalItem localItem,
final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistMetadataEntry)) {
return;
}
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
itemTitleView.setText(item.name);

View File

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

View File

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

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