Improve code style to be more consistent
This commit is contained in:
parent
819e52cab3
commit
fda5405e48
|
@ -30,8 +30,9 @@ class AppDatabaseTest {
|
||||||
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
|
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Rule val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
|
@get:Rule
|
||||||
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory());
|
val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
|
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun migrateDatabaseFrom2to3() {
|
fun migrateDatabaseFrom2to3() {
|
||||||
|
@ -72,7 +73,7 @@ class AppDatabaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
|
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
|
||||||
true, Migrations.MIGRATION_2_3);
|
true, Migrations.MIGRATION_2_3)
|
||||||
|
|
||||||
val migratedDatabaseV3 = getMigratedDatabase()
|
val migratedDatabaseV3 = getMigratedDatabase()
|
||||||
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -12,15 +13,16 @@ import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented tests for {@link ErrorInfo}
|
* Instrumented tests for {@link ErrorInfo}.
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public class ErrorInfoTest {
|
public class ErrorInfoTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void errorInfo_testParcelable() {
|
public void errorInfoTestParcelable() {
|
||||||
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", R.string.general_error);
|
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
||||||
|
R.string.general_error);
|
||||||
// Obtain a Parcel object and write the parcelable object to it:
|
// Obtain a Parcel object and write the parcelable object to it:
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
info.writeToParcel(parcel, 0);
|
info.writeToParcel(parcel, 0);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.schabi.newpipe;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.multidex.MultiDex;
|
import androidx.multidex.MultiDex;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ public class DebugApp extends App {
|
||||||
private static final String TAG = DebugApp.class.toString();
|
private static final String TAG = DebugApp.class.toString();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(final Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(base);
|
||||||
MultiDex.install(this);
|
MultiDex.install(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,15 @@ import java.util.ArrayList;
|
||||||
* This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}.
|
* This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}.
|
||||||
* <p>
|
* <p>
|
||||||
* It includes a workaround to fix the menu visibility when the adapter is restored.
|
* It includes a workaround to fix the menu visibility when the adapter is restored.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* When restoring the state of this adapter, all the fragments' menu visibility were set to false,
|
* When restoring the state of this adapter, all the fragments' menu visibility were set to false,
|
||||||
* effectively disabling the menu from the user until he switched pages or another event that triggered the
|
* effectively disabling the menu from the user until he switched pages or another event
|
||||||
* menu to be visible again happened.
|
* that triggered the menu to be visible again happened.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* <br><b>Check out the changes in:</b>
|
* <b>Check out the changes in:</b>
|
||||||
|
* </p>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #saveState()}</li>
|
* <li>{@link #saveState()}</li>
|
||||||
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
||||||
|
@ -88,8 +91,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
private Fragment mCurrentPrimaryItem = null;
|
private Fragment mCurrentPrimaryItem = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround} that sets the fragment manager for the
|
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
|
||||||
* adapter. This is the equivalent of calling
|
* that sets the fragment manager for the adapter. This is the equivalent of calling
|
||||||
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
|
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
|
||||||
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
||||||
*
|
*
|
||||||
|
@ -101,7 +104,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm) {
|
public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm) {
|
||||||
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,20 +120,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
* @param fm fragment manager that will interact with this adapter
|
* @param fm fragment manager that will interact with this adapter
|
||||||
* @param behavior determines if only current fragments are in a resumed state
|
* @param behavior determines if only current fragments are in a resumed state
|
||||||
*/
|
*/
|
||||||
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm,
|
public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm,
|
||||||
@Behavior int behavior) {
|
@Behavior final int behavior) {
|
||||||
mFragmentManager = fm;
|
mFragmentManager = fm;
|
||||||
mBehavior = behavior;
|
mBehavior = behavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Fragment associated with a specified position.
|
* @param position the position of the item you want
|
||||||
|
* @return the {@link Fragment} associated with a specified position
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract Fragment getItem(int position);
|
public abstract Fragment getItem(int position);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startUpdate(@NonNull ViewGroup container) {
|
public void startUpdate(@NonNull final ViewGroup container) {
|
||||||
if (container.getId() == View.NO_ID) {
|
if (container.getId() == View.NO_ID) {
|
||||||
throw new IllegalStateException("ViewPager with adapter " + this
|
throw new IllegalStateException("ViewPager with adapter " + this
|
||||||
+ " requires a view id");
|
+ " requires a view id");
|
||||||
|
@ -140,7 +144,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
|
||||||
// If we already have this item instantiated, there is nothing
|
// If we already have this item instantiated, there is nothing
|
||||||
// to do. This can happen when we are restoring the entire pager
|
// to do. This can happen when we are restoring the entire pager
|
||||||
// from its saved state, where the fragment manager has already
|
// from its saved state, where the fragment manager has already
|
||||||
|
@ -157,7 +161,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
}
|
}
|
||||||
|
|
||||||
Fragment fragment = getItem(position);
|
Fragment fragment = getItem(position);
|
||||||
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
if (DEBUG) {
|
||||||
|
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||||
|
}
|
||||||
if (mSavedState.size() > position) {
|
if (mSavedState.size() > position) {
|
||||||
Fragment.SavedState fss = mSavedState.get(position);
|
Fragment.SavedState fss = mSavedState.get(position);
|
||||||
if (fss != null) {
|
if (fss != null) {
|
||||||
|
@ -183,14 +189,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
public void destroyItem(@NonNull final ViewGroup container, final int position,
|
||||||
|
@NonNull final Object object) {
|
||||||
Fragment fragment = (Fragment) object;
|
Fragment fragment = (Fragment) object;
|
||||||
|
|
||||||
if (mCurTransaction == null) {
|
if (mCurTransaction == null) {
|
||||||
mCurTransaction = mFragmentManager.beginTransaction();
|
mCurTransaction = mFragmentManager.beginTransaction();
|
||||||
}
|
}
|
||||||
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
|
if (DEBUG) {
|
||||||
+ " v=" + ((Fragment)object).getView());
|
Log.v(TAG, "Removing item #" + position + ": f=" + object
|
||||||
|
+ " v=" + ((Fragment) object).getView());
|
||||||
|
}
|
||||||
while (mSavedState.size() <= position) {
|
while (mSavedState.size() <= position) {
|
||||||
mSavedState.add(null);
|
mSavedState.add(null);
|
||||||
}
|
}
|
||||||
|
@ -206,8 +215,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
||||||
Fragment fragment = (Fragment)object;
|
@NonNull final Object object) {
|
||||||
|
Fragment fragment = (Fragment) object;
|
||||||
if (fragment != mCurrentPrimaryItem) {
|
if (fragment != mCurrentPrimaryItem) {
|
||||||
if (mCurrentPrimaryItem != null) {
|
if (mCurrentPrimaryItem != null) {
|
||||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||||
|
@ -235,7 +245,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finishUpdate(@NonNull ViewGroup container) {
|
public void finishUpdate(@NonNull final ViewGroup container) {
|
||||||
if (mCurTransaction != null) {
|
if (mCurTransaction != null) {
|
||||||
mCurTransaction.commitNowAllowingStateLoss();
|
mCurTransaction.commitNowAllowingStateLoss();
|
||||||
mCurTransaction = null;
|
mCurTransaction = null;
|
||||||
|
@ -243,12 +253,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
|
||||||
return ((Fragment)object).getView() == view;
|
return ((Fragment) object).getView() == view;
|
||||||
}
|
}
|
||||||
|
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
private final String SELECTED_FRAGMENT = "selected_fragment";
|
private final String selectedFragment = "selected_fragment";
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -261,7 +271,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
mSavedState.toArray(fss);
|
mSavedState.toArray(fss);
|
||||||
state.putParcelableArray("states", fss);
|
state.putParcelableArray("states", fss);
|
||||||
}
|
}
|
||||||
for (int i=0; i<mFragments.size(); i++) {
|
for (int i = 0; i < mFragments.size(); i++) {
|
||||||
Fragment f = mFragments.get(i);
|
Fragment f = mFragments.get(i);
|
||||||
if (f != null && f.isAdded()) {
|
if (f != null && f.isAdded()) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
|
@ -273,7 +283,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
// Check if it's the same fragment instance
|
// Check if it's the same fragment instance
|
||||||
if (f == mCurrentPrimaryItem) {
|
if (f == mCurrentPrimaryItem) {
|
||||||
state.putString(SELECTED_FRAGMENT, key);
|
state.putString(selectedFragment, key);
|
||||||
}
|
}
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
}
|
}
|
||||||
|
@ -282,16 +292,16 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
|
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
Bundle bundle = (Bundle)state;
|
Bundle bundle = (Bundle) state;
|
||||||
bundle.setClassLoader(loader);
|
bundle.setClassLoader(loader);
|
||||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||||
mSavedState.clear();
|
mSavedState.clear();
|
||||||
mFragments.clear();
|
mFragments.clear();
|
||||||
if (fss != null) {
|
if (fss != null) {
|
||||||
for (int i=0; i<fss.length; i++) {
|
for (int i = 0; i < fss.length; i++) {
|
||||||
mSavedState.add((Fragment.SavedState)fss[i]);
|
mSavedState.add((Fragment.SavedState) fss[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Iterable<String> keys = bundle.keySet();
|
Iterable<String> keys = bundle.keySet();
|
||||||
|
@ -304,7 +314,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||||
mFragments.add(null);
|
mFragments.add(null);
|
||||||
}
|
}
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
final boolean wasSelected = bundle.getString(SELECTED_FRAGMENT, "").equals(key);
|
final boolean wasSelected = bundle.getString(selectedFragment, "")
|
||||||
|
.equals(key);
|
||||||
f.setMenuVisibility(wasSelected);
|
f.setMenuVisibility(wasSelected);
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
mFragments.set(index, f);
|
mFragments.set(index, f);
|
||||||
|
|
|
@ -10,15 +10,15 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
// check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489
|
// See https://stackoverflow.com/questions/56849221#57997489
|
||||||
public final class FlingBehavior extends AppBarLayout.Behavior {
|
public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||||
|
public FlingBehavior(final Context context, final AttributeSet attrs) {
|
||||||
public FlingBehavior(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
|
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
|
||||||
|
final MotionEvent ev) {
|
||||||
switch (ev.getActionMasked()) {
|
switch (ev.getActionMasked()) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
// remove reference to old nested scrolling child
|
// remove reference to old nested scrolling child
|
||||||
|
@ -35,7 +35,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||||
@Nullable
|
@Nullable
|
||||||
private OverScroller getScrollerField() {
|
private OverScroller getScrollerField() {
|
||||||
try {
|
try {
|
||||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass();
|
Class<?> headerBehaviorType = this.getClass()
|
||||||
|
.getSuperclass().getSuperclass().getSuperclass();
|
||||||
if (headerBehaviorType != null) {
|
if (headerBehaviorType != null) {
|
||||||
Field field = headerBehaviorType.getDeclaredField("scroller");
|
Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
|
@ -62,12 +63,14 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetNestedScrollingChild(){
|
private void resetNestedScrollingChild() {
|
||||||
Field field = getLastNestedScrollingChildRefField();
|
Field field = getLastNestedScrollingChildRefField();
|
||||||
if(field != null){
|
if (field != null) {
|
||||||
try {
|
try {
|
||||||
Object value = field.get(this);
|
Object value = field.get(this);
|
||||||
if(value != null) field.set(this, null);
|
if (value != null) {
|
||||||
|
field.set(this, null);
|
||||||
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
// ?
|
// ?
|
||||||
}
|
}
|
||||||
|
@ -76,7 +79,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||||
|
|
||||||
private void stopAppBarLayoutFling() {
|
private void stopAppBarLayoutFling() {
|
||||||
OverScroller scroller = getScrollerField();
|
OverScroller scroller = getScrollerField();
|
||||||
if (scroller != null) scroller.forceFinished(true);
|
if (scroller != null) {
|
||||||
|
scroller.forceFinished(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,17 +23,25 @@ package org.schabi.newpipe;
|
||||||
/**
|
/**
|
||||||
* Singleton:
|
* Singleton:
|
||||||
* Used to send data between certain Activity/Services within the same process.
|
* Used to send data between certain Activity/Services within the same process.
|
||||||
* This can be considered as an ugly hack inside the Android universe. **/
|
* This can be considered as an ugly hack inside the Android universe.
|
||||||
|
**/
|
||||||
public class ActivityCommunicator {
|
public class ActivityCommunicator {
|
||||||
|
|
||||||
private static ActivityCommunicator activityCommunicator;
|
private static ActivityCommunicator activityCommunicator;
|
||||||
|
private volatile Class returnActivity;
|
||||||
|
|
||||||
public static ActivityCommunicator getCommunicator() {
|
public static ActivityCommunicator getCommunicator() {
|
||||||
if(activityCommunicator == null) {
|
if (activityCommunicator == null) {
|
||||||
activityCommunicator = new ActivityCommunicator();
|
activityCommunicator = new ActivityCommunicator();
|
||||||
}
|
}
|
||||||
return activityCommunicator;
|
return activityCommunicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public volatile Class returnActivity;
|
public Class getReturnActivity() {
|
||||||
|
return returnActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReturnActivity(final Class returnActivity) {
|
||||||
|
this.returnActivity = returnActivity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,15 +66,24 @@ import io.reactivex.plugins.RxJavaPlugins;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
protected static final String TAG = App.class.toString();
|
protected static final String TAG = App.class.toString();
|
||||||
private RefWatcher refWatcher;
|
|
||||||
private static App app;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static final Class<? extends ReportSenderFactory>[]
|
private static final Class<? extends ReportSenderFactory>[]
|
||||||
reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
|
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
||||||
|
private static App app;
|
||||||
|
private RefWatcher refWatcher;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static RefWatcher getRefWatcher(final Context context) {
|
||||||
|
final App application = (App) context.getApplicationContext();
|
||||||
|
return application.refWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static App getApp() {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(final Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(base);
|
||||||
|
|
||||||
initACRA();
|
initACRA();
|
||||||
|
@ -123,24 +132,30 @@ public class App extends Application {
|
||||||
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
||||||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(@NonNull Throwable throwable) {
|
public void accept(@NonNull final Throwable throwable) {
|
||||||
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
|
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
|
||||||
"throwable = [" + throwable.getClass().getName() + "]");
|
+ "throwable = [" + throwable.getClass().getName() + "]");
|
||||||
|
|
||||||
|
final Throwable actualThrowable;
|
||||||
if (throwable instanceof UndeliverableException) {
|
if (throwable instanceof UndeliverableException) {
|
||||||
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
|
// As UndeliverableException is a wrapper,
|
||||||
throwable = throwable.getCause();
|
// get the cause of it to get the "real" exception
|
||||||
|
actualThrowable = throwable.getCause();
|
||||||
|
} else {
|
||||||
|
actualThrowable = throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Throwable> errors;
|
final List<Throwable> errors;
|
||||||
if (throwable instanceof CompositeException) {
|
if (actualThrowable instanceof CompositeException) {
|
||||||
errors = ((CompositeException) throwable).getExceptions();
|
errors = ((CompositeException) actualThrowable).getExceptions();
|
||||||
} else {
|
} else {
|
||||||
errors = Collections.singletonList(throwable);
|
errors = Collections.singletonList(actualThrowable);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Throwable error : errors) {
|
for (final Throwable error : errors) {
|
||||||
if (isThrowableIgnored(error)) return;
|
if (isThrowableIgnored(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isThrowableCritical(error)) {
|
if (isThrowableCritical(error)) {
|
||||||
reportException(error);
|
reportException(error);
|
||||||
return;
|
return;
|
||||||
|
@ -150,17 +165,19 @@ public class App extends Application {
|
||||||
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
|
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
|
||||||
// When exception is not reported, log it
|
// When exception is not reported, log it
|
||||||
if (isDisposedRxExceptionsReported()) {
|
if (isDisposedRxExceptionsReported()) {
|
||||||
reportException(throwable);
|
reportException(actualThrowable);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable);
|
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
|
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
|
||||||
// Don't crash the application over a simple network problem
|
// Don't crash the application over a simple network problem
|
||||||
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
||||||
IOException.class, SocketException.class, // network api cancellation
|
// network api cancellation
|
||||||
InterruptedException.class, InterruptedIOException.class); // blocking code disposed
|
IOException.class, SocketException.class,
|
||||||
|
// blocking code disposed
|
||||||
|
InterruptedException.class, InterruptedIOException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
|
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
|
||||||
|
@ -191,7 +208,7 @@ public class App extends Application {
|
||||||
private void initACRA() {
|
private void initACRA() {
|
||||||
try {
|
try {
|
||||||
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
||||||
.setReportSenderFactoryClasses(reportSenderFactoryClasses)
|
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
||||||
.setBuildConfigClass(BuildConfig.class)
|
.setBuildConfigClass(BuildConfig.class)
|
||||||
.build();
|
.build();
|
||||||
ACRA.init(this, acraConfig);
|
ACRA.init(this, acraConfig);
|
||||||
|
@ -202,7 +219,7 @@ public class App extends Application {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,11 +247,11 @@ public class App extends Application {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up notification channel for app update.
|
* Set up notification channel for app update.
|
||||||
|
*
|
||||||
* @param importance
|
* @param importance
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
private void setUpUpdateNotificationChannel(int importance) {
|
private void setUpUpdateNotificationChannel(final int importance) {
|
||||||
|
|
||||||
final String appUpdateId
|
final String appUpdateId
|
||||||
= getString(R.string.app_update_notification_channel_id);
|
= getString(R.string.app_update_notification_channel_id);
|
||||||
final CharSequence appUpdateName
|
final CharSequence appUpdateName
|
||||||
|
@ -251,12 +268,6 @@ public class App extends Application {
|
||||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static RefWatcher getRefWatcher(Context context) {
|
|
||||||
final App application = (App) context.getApplicationContext();
|
|
||||||
return application.refWatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected RefWatcher installLeakCanary() {
|
protected RefWatcher installLeakCanary() {
|
||||||
return RefWatcher.DISABLED;
|
return RefWatcher.DISABLED;
|
||||||
}
|
}
|
||||||
|
@ -264,8 +275,4 @@ public class App extends Application {
|
||||||
protected boolean isDisposedRxExceptionsReported() {
|
protected boolean isDisposedRxExceptionsReported() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static App getApp() {
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@ package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
import com.squareup.leakcanary.RefWatcher;
|
||||||
|
|
||||||
|
@ -16,18 +17,16 @@ import icepick.Icepick;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
|
||||||
public abstract class BaseFragment extends Fragment {
|
public abstract class BaseFragment extends Fragment {
|
||||||
|
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
|
||||||
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||||
protected final boolean DEBUG = MainActivity.DEBUG;
|
protected final boolean DEBUG = MainActivity.DEBUG;
|
||||||
|
|
||||||
protected AppCompatActivity activity;
|
protected AppCompatActivity activity;
|
||||||
public static final ImageLoader imageLoader = ImageLoader.getInstance();
|
//These values are used for controlling fragments when they are part of the frontpage
|
||||||
|
|
||||||
//These values are used for controlling framgents when they are part of the frontpage
|
|
||||||
@State
|
@State
|
||||||
protected boolean useAsFrontPage = false;
|
protected boolean useAsFrontPage = false;
|
||||||
protected boolean mIsVisibleToUser = false;
|
private boolean mIsVisibleToUser = false;
|
||||||
|
|
||||||
public void useAsFrontPage(boolean value) {
|
public void useAsFrontPage(final boolean value) {
|
||||||
useAsFrontPage = value;
|
useAsFrontPage = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ public abstract class BaseFragment extends Fragment {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
activity = (AppCompatActivity) context;
|
activity = (AppCompatActivity) context;
|
||||||
}
|
}
|
||||||
|
@ -48,43 +47,51 @@ public abstract class BaseFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreate() called with: "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||||
if (savedInstanceState != null) onRestoreInstanceState(savedInstanceState);
|
if (savedInstanceState != null) {
|
||||||
|
onRestoreInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View rootView, Bundle savedInstanceState) {
|
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.onViewCreated(rootView, savedInstanceState);
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
|
Log.d(TAG, "onViewCreated() called with: "
|
||||||
|
+ "rootView = [" + rootView + "], "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
}
|
}
|
||||||
initViews(rootView, savedInstanceState);
|
initViews(rootView, savedInstanceState);
|
||||||
initListeners();
|
initListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
||||||
if (refWatcher != null) refWatcher.watch(this);
|
if (refWatcher != null) {
|
||||||
|
refWatcher.watch(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
mIsVisibleToUser = isVisibleToUser;
|
||||||
}
|
}
|
||||||
|
@ -93,20 +100,20 @@ public abstract class BaseFragment extends Fragment {
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) { }
|
||||||
}
|
|
||||||
|
|
||||||
protected void initListeners() {
|
protected void initListeners() { }
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public void setTitle(String title) {
|
public void setTitle(final String title) {
|
||||||
if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
if (DEBUG) {
|
||||||
if((!useAsFrontPage || mIsVisibleToUser)
|
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
||||||
&& (activity != null && activity.getSupportActionBar() != null)) {
|
}
|
||||||
|
if ((!useAsFrontPage || mIsVisibleToUser)
|
||||||
|
&& (activity != null && activity.getSupportActionBar() != null)) {
|
||||||
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||||
activity.getSupportActionBar().setTitle(title);
|
activity.getSupportActionBar().setTitle(title);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ import android.net.ConnectivityManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
@ -42,62 +43,137 @@ import okhttp3.Response;
|
||||||
* the notification, the user will be directed to the download link.
|
* the notification, the user will be directed to the download link.
|
||||||
*/
|
*/
|
||||||
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
|
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
|
||||||
private static final Application app = App.getApp();
|
private static final Application APP = App.getApp();
|
||||||
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
private static final String GITHUB_APK_SHA1
|
||||||
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
|
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||||
private static final int timeoutPeriod = 30;
|
private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
|
||||||
|
private static final int TIMEOUT_PERIOD = 30;
|
||||||
|
|
||||||
private SharedPreferences mPrefs;
|
private SharedPreferences mPrefs;
|
||||||
private OkHttpClient client;
|
private OkHttpClient client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
||||||
|
*
|
||||||
|
* @return String with the apk's SHA1 fingeprint in hexadecimal
|
||||||
|
*/
|
||||||
|
private static String getCertificateSHA1Fingerprint() {
|
||||||
|
PackageManager pm = APP.getPackageManager();
|
||||||
|
String packageName = APP.getPackageName();
|
||||||
|
int flags = PackageManager.GET_SIGNATURES;
|
||||||
|
PackageInfo packageInfo = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||||
|
} catch (PackageManager.NameNotFoundException ex) {
|
||||||
|
ErrorActivity.reportError(APP, ex, null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
|
"Could not find package info", R.string.app_ui_crash));
|
||||||
|
}
|
||||||
|
|
||||||
|
Signature[] signatures = packageInfo.signatures;
|
||||||
|
byte[] cert = signatures[0].toByteArray();
|
||||||
|
InputStream input = new ByteArrayInputStream(cert);
|
||||||
|
|
||||||
|
CertificateFactory cf = null;
|
||||||
|
X509Certificate c = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cf = CertificateFactory.getInstance("X509");
|
||||||
|
c = (X509Certificate) cf.generateCertificate(input);
|
||||||
|
} catch (CertificateException ex) {
|
||||||
|
ErrorActivity.reportError(APP, ex, null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
|
"Certificate error", R.string.app_ui_crash));
|
||||||
|
}
|
||||||
|
|
||||||
|
String hexString = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
|
byte[] publicKey = md.digest(c.getEncoded());
|
||||||
|
hexString = byte2HexFormatted(publicKey);
|
||||||
|
} catch (NoSuchAlgorithmException ex1) {
|
||||||
|
ErrorActivity.reportError(APP, ex1, null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
|
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||||
|
} catch (CertificateEncodingException ex2) {
|
||||||
|
ErrorActivity.reportError(APP, ex2, null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
|
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String byte2HexFormatted(final byte[] arr) {
|
||||||
|
StringBuilder str = new StringBuilder(arr.length * 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < arr.length; i++) {
|
||||||
|
String h = Integer.toHexString(arr[i]);
|
||||||
|
int l = h.length();
|
||||||
|
if (l == 1) {
|
||||||
|
h = "0" + h;
|
||||||
|
}
|
||||||
|
if (l > 2) {
|
||||||
|
h = h.substring(l - 2, l);
|
||||||
|
}
|
||||||
|
str.append(h.toUpperCase());
|
||||||
|
if (i < (arr.length - 1)) {
|
||||||
|
str.append(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isGithubApk() {
|
||||||
|
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
|
mPrefs = PreferenceManager.getDefaultSharedPreferences(APP);
|
||||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
|
|
||||||
|
|
||||||
// Check if user has enabled/ disabled update checking
|
// Check if user has enabled/ disabled update checking
|
||||||
// and if the current apk is a github one or not.
|
// and if the current apk is a github one or not.
|
||||||
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|
if (!mPrefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
|
||||||
|| !isGithubApk()) {
|
|
||||||
this.cancel(true);
|
this.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String doInBackground(Void... voids) {
|
protected String doInBackground(final Void... voids) {
|
||||||
|
if (isCancelled() || !isConnected()) {
|
||||||
if(isCancelled() || !isConnected()) return null;
|
return null;
|
||||||
|
|
||||||
// Make a network request to get latest NewPipe data.
|
|
||||||
if (client == null) {
|
|
||||||
|
|
||||||
client = new OkHttpClient
|
|
||||||
.Builder()
|
|
||||||
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
// Make a network request to get latest NewPipe data.
|
||||||
.url(newPipeApiUrl)
|
// FIXME: Use DownloaderImp
|
||||||
.build();
|
if (client == null) {
|
||||||
|
|
||||||
|
client = new OkHttpClient.Builder()
|
||||||
|
.readTimeout(TIMEOUT_PERIOD, TimeUnit.SECONDS).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder().url(NEWPIPE_API_URL).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Response response = client.newCall(request).execute();
|
Response response = client.newCall(request).execute();
|
||||||
return response.body().string();
|
return response.body().string();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
// connectivity problems, do not alarm user and fail silently
|
// connectivity problems, do not alarm user and fail silently
|
||||||
if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex));
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, Log.getStackTraceString(ex));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String response) {
|
protected void onPostExecute(final String response) {
|
||||||
|
|
||||||
// Parse the json from the response.
|
// Parse the json from the response.
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
|
||||||
|
@ -115,7 +191,9 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
} catch (JSONException ex) {
|
} catch (JSONException ex) {
|
||||||
// connectivity problems, do not alarm user and fail silently
|
// connectivity problems, do not alarm user and fail silently
|
||||||
if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex));
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, Log.getStackTraceString(ex));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,116 +201,42 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
/**
|
/**
|
||||||
* Method to compare the current and latest available app version.
|
* Method to compare the current and latest available app version.
|
||||||
* If a newer version is available, we show the update notification.
|
* If a newer version is available, we show the update notification.
|
||||||
* @param versionName
|
*
|
||||||
* @param apkLocationUrl
|
* @param versionName Name of new version
|
||||||
|
* @param apkLocationUrl Url with the new apk
|
||||||
|
* @param versionCode V
|
||||||
*/
|
*/
|
||||||
private void compareAppVersionAndShowNotification(String versionName,
|
private void compareAppVersionAndShowNotification(final String versionName,
|
||||||
String apkLocationUrl,
|
final String apkLocationUrl,
|
||||||
String versionCode) {
|
final String versionCode) {
|
||||||
|
int notificationId = 2000;
|
||||||
int NOTIFICATION_ID = 2000;
|
|
||||||
|
|
||||||
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
||||||
|
|
||||||
// A pending intent to open the apk location url in the browser.
|
// A pending intent to open the apk location url in the browser.
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||||
PendingIntent pendingIntent
|
PendingIntent pendingIntent
|
||||||
= PendingIntent.getActivity(app, 0, intent, 0);
|
= PendingIntent.getActivity(APP, 0, intent, 0);
|
||||||
|
|
||||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
||||||
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
|
.Builder(APP, APP.getString(R.string.app_update_notification_channel_id))
|
||||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
.setContentTitle(APP.getString(R.string.app_update_notification_content_title))
|
||||||
.setContentText(app.getString(R.string.app_update_notification_content_text)
|
.setContentText(APP.getString(R.string.app_update_notification_content_text)
|
||||||
+ " " + versionName);
|
+ " " + versionName);
|
||||||
|
|
||||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(APP);
|
||||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
notificationManager.notify(notificationId, notificationBuilder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to get the apk's SHA1 key.
|
|
||||||
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
|
|
||||||
*/
|
|
||||||
private static String getCertificateSHA1Fingerprint() {
|
|
||||||
|
|
||||||
PackageManager pm = app.getPackageManager();
|
|
||||||
String packageName = app.getPackageName();
|
|
||||||
int flags = PackageManager.GET_SIGNATURES;
|
|
||||||
PackageInfo packageInfo = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
|
||||||
} catch (PackageManager.NameNotFoundException ex) {
|
|
||||||
ErrorActivity.reportError(app, ex, null, null,
|
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
|
||||||
"Could not find package info", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
Signature[] signatures = packageInfo.signatures;
|
|
||||||
byte[] cert = signatures[0].toByteArray();
|
|
||||||
InputStream input = new ByteArrayInputStream(cert);
|
|
||||||
|
|
||||||
CertificateFactory cf = null;
|
|
||||||
X509Certificate c = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
cf = CertificateFactory.getInstance("X509");
|
|
||||||
c = (X509Certificate) cf.generateCertificate(input);
|
|
||||||
} catch (CertificateException ex) {
|
|
||||||
ErrorActivity.reportError(app, ex, null, null,
|
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
|
||||||
"Certificate error", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
String hexString = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
|
||||||
byte[] publicKey = md.digest(c.getEncoded());
|
|
||||||
hexString = byte2HexFormatted(publicKey);
|
|
||||||
} catch (NoSuchAlgorithmException ex1) {
|
|
||||||
ErrorActivity.reportError(app, ex1, null, null,
|
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
|
||||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
|
||||||
} catch (CertificateEncodingException ex2) {
|
|
||||||
ErrorActivity.reportError(app, ex2, null, null,
|
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
|
||||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
return hexString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String byte2HexFormatted(byte[] arr) {
|
|
||||||
|
|
||||||
StringBuilder str = new StringBuilder(arr.length * 2);
|
|
||||||
|
|
||||||
for (int i = 0; i < arr.length; i++) {
|
|
||||||
String h = Integer.toHexString(arr[i]);
|
|
||||||
int l = h.length();
|
|
||||||
if (l == 1) h = "0" + h;
|
|
||||||
if (l > 2) h = h.substring(l - 2, l);
|
|
||||||
str.append(h.toUpperCase());
|
|
||||||
if (i < (arr.length - 1)) str.append(':');
|
|
||||||
}
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isGithubApk() {
|
|
||||||
|
|
||||||
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isConnected() {
|
private boolean isConnected() {
|
||||||
|
|
||||||
ConnectivityManager cm =
|
ConnectivityManager cm =
|
||||||
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
|
(ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
return cm.getActiveNetworkInfo() != null
|
return cm.getActiveNetworkInfo() != null
|
||||||
&& cm.getActiveNetworkInfo().isConnected();
|
&& cm.getActiveNetworkInfo().isConnected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package org.schabi.newpipe;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.downloader.Request;
|
import org.schabi.newpipe.extractor.downloader.Request;
|
||||||
import org.schabi.newpipe.extractor.downloader.Response;
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
|
@ -26,9 +29,6 @@ import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import okhttp3.CipherSuite;
|
import okhttp3.CipherSuite;
|
||||||
import okhttp3.ConnectionSpec;
|
import okhttp3.ConnectionSpec;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
@ -37,20 +37,22 @@ import okhttp3.ResponseBody;
|
||||||
|
|
||||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
|
|
||||||
public class DownloaderImpl extends Downloader {
|
public final class DownloaderImpl extends Downloader {
|
||||||
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
public static final String USER_AGENT
|
||||||
|
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
||||||
|
|
||||||
private static DownloaderImpl instance;
|
private static DownloaderImpl instance;
|
||||||
private String mCookies;
|
private String mCookies;
|
||||||
private OkHttpClient client;
|
private OkHttpClient client;
|
||||||
|
|
||||||
private DownloaderImpl(OkHttpClient.Builder builder) {
|
private DownloaderImpl(final OkHttpClient.Builder builder) {
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
||||||
enableModernTLS(builder);
|
enableModernTLS(builder);
|
||||||
}
|
}
|
||||||
this.client = builder
|
this.client = builder
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
//.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
|
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
|
||||||
|
// 16 * 1024 * 1024))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,20 +60,72 @@ public class DownloaderImpl extends Downloader {
|
||||||
* It's recommended to call exactly once in the entire lifetime of the application.
|
* It's recommended to call exactly once in the entire lifetime of the application.
|
||||||
*
|
*
|
||||||
* @param builder if null, default builder will be used
|
* @param builder if null, default builder will be used
|
||||||
|
* @return a new instance of {@link DownloaderImpl}
|
||||||
*/
|
*/
|
||||||
public static DownloaderImpl init(@Nullable OkHttpClient.Builder builder) {
|
public static DownloaderImpl init(@Nullable final OkHttpClient.Builder builder) {
|
||||||
return instance = new DownloaderImpl(builder != null ? builder : new OkHttpClient.Builder());
|
instance = new DownloaderImpl(
|
||||||
|
builder != null ? builder : new OkHttpClient.Builder());
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DownloaderImpl getInstance() {
|
public static DownloaderImpl getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken
|
||||||
|
* from the documentation of OkHttpClient.Builder.sslSocketFactory(_,_).
|
||||||
|
* <p>
|
||||||
|
* If there is an error, the function will safely fall back to doing nothing
|
||||||
|
* and printing the error to the console.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place)
|
||||||
|
*/
|
||||||
|
private static void enableModernTLS(final OkHttpClient.Builder builder) {
|
||||||
|
try {
|
||||||
|
// get the default TrustManager
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||||
|
TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init((KeyStore) null);
|
||||||
|
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||||
|
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||||
|
throw new IllegalStateException("Unexpected default trust managers:"
|
||||||
|
+ Arrays.toString(trustManagers));
|
||||||
|
}
|
||||||
|
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||||
|
|
||||||
|
// insert our own TLSSocketFactory
|
||||||
|
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
||||||
|
|
||||||
|
builder.sslSocketFactory(sslSocketFactory, trustManager);
|
||||||
|
|
||||||
|
// This will try to enable all modern CipherSuites(+2 more)
|
||||||
|
// that are supported on the device.
|
||||||
|
// Necessary because some servers (e.g. Framatube.org)
|
||||||
|
// don't support the old cipher suites.
|
||||||
|
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
|
||||||
|
List<CipherSuite> cipherSuites = new ArrayList<>();
|
||||||
|
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
|
||||||
|
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
|
||||||
|
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
|
||||||
|
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
|
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getCookies() {
|
public String getCookies() {
|
||||||
return mCookies;
|
return mCookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCookies(String cookies) {
|
public void setCookies(final String cookies) {
|
||||||
mCookies = cookies;
|
mCookies = cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +135,7 @@ public class DownloaderImpl extends Downloader {
|
||||||
* @param url an url pointing to the content
|
* @param url an url pointing to the content
|
||||||
* @return the size of the content, in bytes
|
* @return the size of the content, in bytes
|
||||||
*/
|
*/
|
||||||
public long getContentLength(String url) throws IOException {
|
public long getContentLength(final String url) throws IOException {
|
||||||
try {
|
try {
|
||||||
final Response response = head(url);
|
final Response response = head(url);
|
||||||
return Long.parseLong(response.getHeader("Content-Length"));
|
return Long.parseLong(response.getHeader("Content-Length"));
|
||||||
|
@ -92,7 +146,7 @@ public class DownloaderImpl extends Downloader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream stream(String siteUrl) throws IOException {
|
public InputStream stream(final String siteUrl) throws IOException {
|
||||||
try {
|
try {
|
||||||
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
|
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
|
||||||
.method("GET", null).url(siteUrl)
|
.method("GET", null).url(siteUrl)
|
||||||
|
@ -122,7 +176,8 @@ public class DownloaderImpl extends Downloader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response execute(@NonNull Request request) throws IOException, ReCaptchaException {
|
public Response execute(@NonNull final Request request)
|
||||||
|
throws IOException, ReCaptchaException {
|
||||||
final String httpMethod = request.httpMethod();
|
final String httpMethod = request.httpMethod();
|
||||||
final String url = request.url();
|
final String url = request.url();
|
||||||
final Map<String, List<String>> headers = request.headers();
|
final Map<String, List<String>> headers = request.headers();
|
||||||
|
@ -172,49 +227,7 @@ public class DownloaderImpl extends Downloader {
|
||||||
}
|
}
|
||||||
|
|
||||||
final String latestUrl = response.request().url().toString();
|
final String latestUrl = response.request().url().toString();
|
||||||
return new Response(response.code(), response.message(), response.headers().toMultimap(), responseBodyToReturn, latestUrl);
|
return new Response(response.code(), response.message(), response.headers().toMultimap(),
|
||||||
}
|
responseBodyToReturn, latestUrl);
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken from the documentation of
|
|
||||||
* OkHttpClient.Builder.sslSocketFactory(_,_)
|
|
||||||
* <p>
|
|
||||||
* If there is an error, the function will safely fall back to doing nothing and printing the error to the console.
|
|
||||||
*
|
|
||||||
* @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place)
|
|
||||||
*/
|
|
||||||
private static void enableModernTLS(OkHttpClient.Builder builder) {
|
|
||||||
try {
|
|
||||||
// get the default TrustManager
|
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
|
||||||
TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
trustManagerFactory.init((KeyStore) null);
|
|
||||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
|
||||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
|
||||||
throw new IllegalStateException("Unexpected default trust managers:"
|
|
||||||
+ Arrays.toString(trustManagers));
|
|
||||||
}
|
|
||||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
|
||||||
|
|
||||||
// insert our own TLSSocketFactory
|
|
||||||
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
|
||||||
|
|
||||||
builder.sslSocketFactory(sslSocketFactory, trustManager);
|
|
||||||
|
|
||||||
// This will try to enable all modern CipherSuites(+2 more) that are supported on the device.
|
|
||||||
// Necessary because some servers (e.g. Framatube.org) don't support the old cipher suites.
|
|
||||||
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
|
|
||||||
List<CipherSuite> cipherSuites = new ArrayList<>();
|
|
||||||
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
|
|
||||||
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
|
|
||||||
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
|
|
||||||
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
|
||||||
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
|
|
||||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
|
||||||
if (DEBUG) e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
@ -27,9 +26,20 @@ import android.os.Bundle;
|
||||||
|
|
||||||
public class ExitActivity extends Activity {
|
public class ExitActivity extends Activity {
|
||||||
|
|
||||||
|
public static void exitAndRemoveFromRecentApps(final Activity activity) {
|
||||||
|
Intent intent = new Intent(activity, ExitActivity.class);
|
||||||
|
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||||
|
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
@ -40,15 +50,4 @@ public class ExitActivity extends Activity {
|
||||||
|
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void exitAndRemoveFromRecentApps(Activity activity) {
|
|
||||||
Intent intent = new Intent(activity, ExitActivity.class);
|
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
|
||||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
|
||||||
|
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ public class ImageDownloader extends BaseImageDownloader {
|
||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
private final String downloadThumbnailKey;
|
private final String downloadThumbnailKey;
|
||||||
|
|
||||||
public ImageDownloader(Context context) {
|
public ImageDownloader(final Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
this.resources = context.getResources();
|
this.resources = context.getResources();
|
||||||
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
@ -31,7 +31,7 @@ public class ImageDownloader extends BaseImageDownloader {
|
||||||
|
|
||||||
@SuppressLint("ResourceType")
|
@SuppressLint("ResourceType")
|
||||||
@Override
|
@Override
|
||||||
public InputStream getStream(String imageUri, Object extra) throws IOException {
|
public InputStream getStream(final String imageUri, final Object extra) throws IOException {
|
||||||
if (isDownloadingThumbnail()) {
|
if (isDownloadingThumbnail()) {
|
||||||
return super.getStream(imageUri, extra);
|
return super.getStream(imageUri, extra);
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,7 +39,8 @@ public class ImageDownloader extends BaseImageDownloader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
|
protected InputStream getStreamFromNetwork(final String imageUri, final Object extra)
|
||||||
|
throws IOException {
|
||||||
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
|
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
|
||||||
return downloader.stream(imageUri);
|
return downloader.stream(imageUri);
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,11 +93,11 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private boolean servicesShown = false;
|
private boolean servicesShown = false;
|
||||||
private ImageView serviceArrow;
|
private ImageView serviceArrow;
|
||||||
|
|
||||||
private static final int ITEM_ID_SUBSCRIPTIONS = - 1;
|
private static final int ITEM_ID_SUBSCRIPTIONS = -1;
|
||||||
private static final int ITEM_ID_FEED = - 2;
|
private static final int ITEM_ID_FEED = -2;
|
||||||
private static final int ITEM_ID_BOOKMARKS = - 3;
|
private static final int ITEM_ID_BOOKMARKS = -3;
|
||||||
private static final int ITEM_ID_DOWNLOADS = - 4;
|
private static final int ITEM_ID_DOWNLOADS = -4;
|
||||||
private static final int ITEM_ID_HISTORY = - 5;
|
private static final int ITEM_ID_HISTORY = -5;
|
||||||
private static final int ITEM_ID_SETTINGS = 0;
|
private static final int ITEM_ID_SETTINGS = 0;
|
||||||
private static final int ITEM_ID_ABOUT = 1;
|
private static final int ITEM_ID_ABOUT = 1;
|
||||||
|
|
||||||
|
@ -108,8 +108,11 @@ public class MainActivity extends AppCompatActivity {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreate() called with: "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
}
|
||||||
|
|
||||||
// enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources
|
// enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
||||||
|
@ -123,10 +126,12 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
Window w = getWindow();
|
Window w = getWindow();
|
||||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
||||||
|
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
if (getSupportFragmentManager() != null
|
||||||
|
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||||
initFragments();
|
initFragments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,13 +156,15 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator.getTranslatedKioskName(ks, this))
|
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
|
||||||
|
.getTranslatedKioskName(ks, this))
|
||||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||||
kioskId ++;
|
kioskId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
|
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
|
||||||
|
R.string.tab_subscriptions)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||||
|
@ -180,20 +187,21 @@ public class MainActivity extends AppCompatActivity {
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||||
|
|
||||||
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
|
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
|
||||||
|
R.string.drawer_close);
|
||||||
toggle.syncState();
|
toggle.syncState();
|
||||||
drawer.addDrawerListener(toggle);
|
drawer.addDrawerListener(toggle);
|
||||||
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
||||||
private int lastService;
|
private int lastService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrawerOpened(View drawerView) {
|
public void onDrawerOpened(final View drawerView) {
|
||||||
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
|
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrawerClosed(View drawerView) {
|
public void onDrawerClosed(final View drawerView) {
|
||||||
if(servicesShown) {
|
if (servicesShown) {
|
||||||
toggleServices();
|
toggleServices();
|
||||||
}
|
}
|
||||||
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
||||||
|
@ -206,7 +214,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
setupDrawerHeader();
|
setupDrawerHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean drawerItemSelected(MenuItem item) {
|
private boolean drawerItemSelected(final MenuItem item) {
|
||||||
switch (item.getGroupId()) {
|
switch (item.getGroupId()) {
|
||||||
case R.id.menu_services_group:
|
case R.id.menu_services_group:
|
||||||
changeService(item);
|
changeService(item);
|
||||||
|
@ -229,14 +237,16 @@ public class MainActivity extends AppCompatActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeService(MenuItem item) {
|
private void changeService(final MenuItem item) {
|
||||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
|
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
|
||||||
|
.setChecked(false);
|
||||||
ServiceHelper.setSelectedServiceId(this, item.getItemId());
|
ServiceHelper.setSelectedServiceId(this, item.getItemId());
|
||||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
|
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
|
||||||
|
.setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tabSelected(MenuItem item) throws ExtractionException {
|
private void tabSelected(final MenuItem item) throws ExtractionException {
|
||||||
switch(item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case ITEM_ID_SUBSCRIPTIONS:
|
case ITEM_ID_SUBSCRIPTIONS:
|
||||||
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
|
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
|
||||||
break;
|
break;
|
||||||
|
@ -259,19 +269,20 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
int kioskId = 0;
|
int kioskId = 0;
|
||||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||||
if(kioskId == item.getItemId()) {
|
if (kioskId == item.getItemId()) {
|
||||||
serviceName = ks;
|
serviceName = ks;
|
||||||
}
|
}
|
||||||
kioskId ++;
|
kioskId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName);
|
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId,
|
||||||
|
serviceName);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void optionsAboutSelected(MenuItem item) {
|
private void optionsAboutSelected(final MenuItem item) {
|
||||||
switch(item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case ITEM_ID_SETTINGS:
|
case ITEM_ID_SETTINGS:
|
||||||
NavigationHelper.openSettings(this);
|
NavigationHelper.openSettings(this);
|
||||||
break;
|
break;
|
||||||
|
@ -283,7 +294,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private void setupDrawerHeader() {
|
private void setupDrawerHeader() {
|
||||||
NavigationView navigationView = findViewById(R.id.navigation);
|
NavigationView navigationView = findViewById(R.id.navigation);
|
||||||
View hView = navigationView.getHeaderView(0);
|
View hView = navigationView.getHeaderView(0);
|
||||||
|
|
||||||
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
||||||
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
|
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
|
||||||
|
@ -299,7 +310,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
|
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
|
||||||
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
|
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
|
||||||
|
|
||||||
if(servicesShown) {
|
if (servicesShown) {
|
||||||
showServices();
|
showServices();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -313,55 +324,62 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private void showServices() {
|
private void showServices() {
|
||||||
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
|
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
|
||||||
|
|
||||||
for(StreamingService s : NewPipe.getServices()) {
|
for (StreamingService s : NewPipe.getServices()) {
|
||||||
final String title = s.getServiceInfo().getName() +
|
final String title = s.getServiceInfo().getName()
|
||||||
(ServiceHelper.isBeta(s) ? " (beta)" : "");
|
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||||
|
|
||||||
MenuItem menuItem = drawerItems.getMenu()
|
MenuItem menuItem = drawerItems.getMenu()
|
||||||
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
||||||
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||||
|
|
||||||
// peertube specifics
|
// peertube specifics
|
||||||
if(s.getServiceId() == 3){
|
if (s.getServiceId() == 3) {
|
||||||
enhancePeertubeMenu(s, menuItem);
|
enhancePeertubeMenu(s, menuItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
|
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
|
||||||
|
.setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enhancePeertubeMenu(StreamingService s, MenuItem menuItem) {
|
private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
|
||||||
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
|
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
|
||||||
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
|
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
|
||||||
Spinner spinner = (Spinner) LayoutInflater.from(this).inflate(R.layout.instance_spinner_layout, null);
|
Spinner spinner = (Spinner) LayoutInflater.from(this)
|
||||||
|
.inflate(R.layout.instance_spinner_layout, null);
|
||||||
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
||||||
List<String> items = new ArrayList<>();
|
List<String> items = new ArrayList<>();
|
||||||
int defaultSelect = 0;
|
int defaultSelect = 0;
|
||||||
for(PeertubeInstance instance: instances){
|
for (PeertubeInstance instance : instances) {
|
||||||
items.add(instance.getName());
|
items.add(instance.getName());
|
||||||
if(instance.getUrl().equals(currentInstace.getUrl())){
|
if (instance.getUrl().equals(currentInstace.getUrl())) {
|
||||||
defaultSelect = items.size()-1;
|
defaultSelect = items.size() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.instance_spinner_item, items);
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||||
|
R.layout.instance_spinner_item, items);
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
spinner.setAdapter(adapter);
|
spinner.setAdapter(adapter);
|
||||||
spinner.setSelection(defaultSelect, false);
|
spinner.setSelection(defaultSelect, false);
|
||||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(final AdapterView<?> parent, final View view,
|
||||||
|
final int position, final long id) {
|
||||||
PeertubeInstance newInstance = instances.get(position);
|
PeertubeInstance newInstance = instances.get(position);
|
||||||
if(newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) return;
|
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
PeertubeHelper.selectInstance(newInstance, getApplicationContext());
|
PeertubeHelper.selectInstance(newInstance, getApplicationContext());
|
||||||
changeService(menuItem);
|
changeService(menuItem);
|
||||||
drawer.closeDrawers();
|
drawer.closeDrawers();
|
||||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
getSupportFragmentManager().popBackStack(null,
|
||||||
|
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
recreate();
|
recreate();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
public void onNothingSelected(final AdapterView<?> parent) {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -379,9 +397,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, kioskId, ORDER, KioskTranslator.getTranslatedKioskName(ks, this))
|
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
||||||
|
KioskTranslator.getTranslatedKioskName(ks, this))
|
||||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||||
kioskId ++;
|
kioskId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
|
@ -420,15 +439,17 @@ public class MainActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
assureCorrectAppLanguage(this);
|
assureCorrectAppLanguage(this);
|
||||||
Localization.init(getApplicationContext()); //change the date format to match the selected language on resume
|
// Change the date format to match the selected language on resume
|
||||||
|
Localization.init(getApplicationContext());
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
// close drawer on return, and don't show animation, so its looks like the drawer isn't open
|
// Close drawer on return, and don't show animation,
|
||||||
// when the user returns to MainActivity
|
// so it looks like the drawer isn't open when the user returns to MainActivity
|
||||||
drawer.closeDrawer(GravityCompat.START, false);
|
drawer.closeDrawer(GravityCompat.START, false);
|
||||||
try {
|
try {
|
||||||
final int selectedServiceId = ServiceHelper.getSelectedServiceId(this);
|
final int selectedServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||||
final String selectedServiceName = NewPipe.getService(selectedServiceId).getServiceInfo().getName();
|
final String selectedServiceName = NewPipe.getService(selectedServiceId)
|
||||||
|
.getServiceInfo().getName();
|
||||||
headerServiceView.setText(selectedServiceName);
|
headerServiceView.setText(selectedServiceName);
|
||||||
headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
|
headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
|
||||||
|
|
||||||
|
@ -441,15 +462,20 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
||||||
if (DEBUG) Log.d(TAG, "Theme has changed, recreating activity...");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Theme has changed, recreating activity...");
|
||||||
|
}
|
||||||
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
|
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
|
||||||
// https://stackoverflow.com/questions/10844112/runtimeexception-performing-pause-of-activity-that-is-not-resumed
|
// https://stackoverflow.com/questions/10844112/
|
||||||
// Briefly, let the activity resume properly posting the recreate call to end of the message queue
|
// Briefly, let the activity resume
|
||||||
|
// properly posting the recreate call to end of the message queue
|
||||||
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
|
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
|
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
|
||||||
if (DEBUG) Log.d(TAG, "main page has changed, recreating main fragment...");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "main page has changed, recreating main fragment...");
|
||||||
|
}
|
||||||
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
|
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
|
||||||
NavigationHelper.openMainActivity(this);
|
NavigationHelper.openMainActivity(this);
|
||||||
}
|
}
|
||||||
|
@ -460,13 +486,18 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(final Intent intent) {
|
||||||
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
|
||||||
|
}
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||||
// to not destroy the already created backstack
|
// to not destroy the already created backstack
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return;
|
if ((action != null && action.equals(Intent.ACTION_MAIN))
|
||||||
|
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
|
@ -476,24 +507,32 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (DEBUG) Log.d(TAG, "onBackPressed() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onBackPressed() called");
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
|
||||||
// If current fragment implements BackPressable (i.e. can/wanna handle back press) delegate the back press to it
|
|
||||||
if (fragment instanceof BackPressable) {
|
|
||||||
if (((BackPressable) fragment).onBackPressed()) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||||
|
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
||||||
|
// delegate the back press to it
|
||||||
|
if (fragment instanceof BackPressable) {
|
||||||
|
if (((BackPressable) fragment).onBackPressed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||||
finish();
|
finish();
|
||||||
} else super.onBackPressed();
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
for (int i: grantResults){
|
@NonNull final String[] permissions,
|
||||||
if (i == PackageManager.PERMISSION_DENIED){
|
@NonNull final int[] grantResults) {
|
||||||
|
for (int i : grantResults) {
|
||||||
|
if (i == PackageManager.PERMISSION_DENIED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,7 +541,8 @@ public class MainActivity extends AppCompatActivity {
|
||||||
NavigationHelper.openDownloads(this);
|
NavigationHelper.openDownloads(this);
|
||||||
break;
|
break;
|
||||||
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
|
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
Fragment fragment = getSupportFragmentManager()
|
||||||
|
.findFragmentById(R.id.fragment_holder);
|
||||||
if (fragment instanceof VideoDetailFragment) {
|
if (fragment instanceof VideoDetailFragment) {
|
||||||
((VideoDetailFragment) fragment).openDownloadDialog();
|
((VideoDetailFragment) fragment).openDownloadDialog();
|
||||||
}
|
}
|
||||||
|
@ -547,8 +587,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
|
||||||
|
}
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
|
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||||
|
@ -557,8 +599,8 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(fragment instanceof SearchFragment)) {
|
if (!(fragment instanceof SearchFragment)) {
|
||||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE);
|
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
|
||||||
|
.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
@ -572,8 +614,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||||
|
}
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
@ -590,11 +634,15 @@ public class MainActivity extends AppCompatActivity {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void initFragments() {
|
private void initFragments() {
|
||||||
if (DEBUG) Log.d(TAG, "initFragments() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "initFragments() called");
|
||||||
|
}
|
||||||
StateSaver.clearStateFiles();
|
StateSaver.clearStateFiles();
|
||||||
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
|
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
} else NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
} else {
|
||||||
|
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -602,12 +650,14 @@ public class MainActivity extends AppCompatActivity {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void updateDrawerNavigation() {
|
private void updateDrawerNavigation() {
|
||||||
if (getSupportActionBar() == null) return;
|
if (getSupportActionBar() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
final DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
|
||||||
|
|
||||||
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
final Fragment fragment = getSupportFragmentManager()
|
||||||
|
.findFragmentById(R.id.fragment_holder);
|
||||||
if (fragment instanceof MainFragment) {
|
if (fragment instanceof MainFragment) {
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
if (toggle != null) {
|
if (toggle != null) {
|
||||||
|
@ -622,26 +672,23 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDrawerHeaderString(String content) {
|
private void handleIntent(final Intent intent) {
|
||||||
NavigationView navigationView = findViewById(R.id.navigation);
|
|
||||||
View hView = navigationView.getHeaderView(0);
|
|
||||||
Button action = hView.findViewById(R.id.drawer_header_action_button);
|
|
||||||
|
|
||||||
action.setContentDescription(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleIntent(Intent intent) {
|
|
||||||
try {
|
try {
|
||||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||||
|
}
|
||||||
|
|
||||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
String url = intent.getStringExtra(Constants.KEY_URL);
|
||||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||||
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
switch (((StreamingService.LinkType) intent
|
||||||
|
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||||
case STREAM:
|
case STREAM:
|
||||||
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
boolean autoPlay = intent
|
||||||
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
|
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
||||||
|
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
|
||||||
|
serviceId, url, title, autoPlay);
|
||||||
break;
|
break;
|
||||||
case CHANNEL:
|
case CHANNEL:
|
||||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
||||||
|
@ -658,7 +705,9 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
|
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
|
||||||
String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING);
|
String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING);
|
||||||
if (searchString == null) searchString = "";
|
if (searchString == null) {
|
||||||
|
searchString = "";
|
||||||
|
}
|
||||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||||
NavigationHelper.openSearchFragment(
|
NavigationHelper.openSearchFragment(
|
||||||
getSupportFragmentManager(),
|
getSupportFragmentManager(),
|
||||||
|
|
|
@ -13,14 +13,13 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
|
||||||
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
|
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
|
||||||
|
|
||||||
public final class NewPipeDatabase {
|
public final class NewPipeDatabase {
|
||||||
|
|
||||||
private static volatile AppDatabase databaseInstance;
|
private static volatile AppDatabase databaseInstance;
|
||||||
|
|
||||||
private NewPipeDatabase() {
|
private NewPipeDatabase() {
|
||||||
//no instance
|
//no instance
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AppDatabase getDatabase(Context context) {
|
private static AppDatabase getDatabase(final Context context) {
|
||||||
return Room
|
return Room
|
||||||
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
||||||
|
@ -28,13 +27,14 @@ public final class NewPipeDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static AppDatabase getInstance(@NonNull Context context) {
|
public static AppDatabase getInstance(@NonNull final Context context) {
|
||||||
AppDatabase result = databaseInstance;
|
AppDatabase result = databaseInstance;
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
synchronized (NewPipeDatabase.class) {
|
synchronized (NewPipeDatabase.class) {
|
||||||
result = databaseInstance;
|
result = databaseInstance;
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
databaseInstance = (result = getDatabase(context));
|
databaseInstance = getDatabase(context);
|
||||||
|
result = databaseInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
@ -26,17 +25,18 @@ import android.os.Bundle;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class PanicResponderActivity extends Activity {
|
public class PanicResponderActivity extends Activity {
|
||||||
|
|
||||||
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
|
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||||
// TODO explicitly clear the search results once they are restored when the app restarts
|
// TODO: Explicitly clear the search results
|
||||||
// or if the app reloads the current video after being killed, that should be cleared also
|
// once they are restored when the app restarts
|
||||||
|
// or if the app reloads the current video after being killed,
|
||||||
|
// that should be cleared also
|
||||||
ExitActivity.exitAndRemoveFromRecentApps(this);
|
ExitActivity.exitAndRemoveFromRecentApps(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,6 @@ package org.schabi.newpipe;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.core.app.NavUtils;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
@ -16,9 +11,13 @@ import android.webkit.WebSettings;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.NavUtils;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
|
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
|
||||||
|
@ -49,7 +48,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
private String foundCookies = "";
|
private String foundCookies = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_recaptcha);
|
setContentView(R.layout.activity_recaptcha);
|
||||||
|
@ -73,7 +72,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
|
|
||||||
webView.setWebViewClient(new WebViewClient() {
|
webView.setWebViewClient(new WebViewClient() {
|
||||||
@Override
|
@Override
|
||||||
public void onPageFinished(WebView view, String url) {
|
public void onPageFinished(final WebView view, final String url) {
|
||||||
super.onPageFinished(view, url);
|
super.onPageFinished(view, url);
|
||||||
handleCookies(url);
|
handleCookies(url);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +83,8 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
webView.clearHistory();
|
webView.clearHistory();
|
||||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
cookieManager.removeAllCookies(aBoolean -> {});
|
cookieManager.removeAllCookies(aBoolean -> {
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
cookieManager.removeAllCookie();
|
cookieManager.removeAllCookie();
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
@ -112,7 +112,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.id.menu_item_done:
|
case R.id.menu_item_done:
|
||||||
|
@ -137,24 +137,29 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleCookies(final String url) {
|
||||||
private void handleCookies(String url) {
|
|
||||||
String cookies = CookieManager.getInstance().getCookie(url);
|
String cookies = CookieManager.getInstance().getCookie(url);
|
||||||
if (MainActivity.DEBUG) Log.d(TAG, "handleCookies: url=" + url + "; cookies=" + (cookies == null ? "null" : cookies));
|
if (MainActivity.DEBUG) {
|
||||||
if (cookies == null) return;
|
Log.d(TAG, "handleCookies: "
|
||||||
|
+ "url=" + url + "; cookies=" + (cookies == null ? "null" : cookies));
|
||||||
|
}
|
||||||
|
if (cookies == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addYoutubeCookies(cookies);
|
addYoutubeCookies(cookies);
|
||||||
// add other methods to extract cookies here
|
// add other methods to extract cookies here
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addYoutubeCookies(@NonNull String cookies) {
|
private void addYoutubeCookies(@NonNull final String cookies) {
|
||||||
if (cookies.contains("s_gl=") || cookies.contains("goojf=") || cookies.contains("VISITOR_INFO1_LIVE=")) {
|
if (cookies.contains("s_gl=") || cookies.contains("goojf=")
|
||||||
|
|| cookies.contains("VISITOR_INFO1_LIVE=")) {
|
||||||
// youtube seems to also need the other cookies:
|
// youtube seems to also need the other cookies:
|
||||||
addCookie(cookies);
|
addCookie(cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCookie(String cookie) {
|
private void addCookie(final String cookie) {
|
||||||
if (foundCookies.contains(cookie)) {
|
if (foundCookies.contains(cookie)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
|
@ -71,29 +73,31 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCap
|
||||||
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the url from the intent and open it in the chosen preferred player
|
* Get the url from the intent and open it in the chosen preferred player.
|
||||||
*/
|
*/
|
||||||
public class RouterActivity extends AppCompatActivity {
|
public class RouterActivity extends AppCompatActivity {
|
||||||
|
public static final String INTERNAL_ROUTE_KEY = "internalRoute";
|
||||||
|
/**
|
||||||
|
* Removes invisible separators (\p{Z}) and punctuation characters including
|
||||||
|
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
||||||
|
* more details.
|
||||||
|
*/
|
||||||
|
private static final String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
||||||
|
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
@State
|
@State
|
||||||
protected int currentServiceId = -1;
|
protected int currentServiceId = -1;
|
||||||
private StreamingService currentService;
|
|
||||||
@State
|
@State
|
||||||
protected LinkType currentLinkType;
|
protected LinkType currentLinkType;
|
||||||
@State
|
@State
|
||||||
protected int selectedRadioPosition = -1;
|
protected int selectedRadioPosition = -1;
|
||||||
protected int selectedPreviously = -1;
|
protected int selectedPreviously = -1;
|
||||||
|
|
||||||
protected String currentUrl;
|
protected String currentUrl;
|
||||||
protected boolean internalRoute = false;
|
protected boolean internalRoute = false;
|
||||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
private StreamingService currentService;
|
||||||
|
|
||||||
private boolean selectionIsDownload = false;
|
private boolean selectionIsDownload = false;
|
||||||
|
|
||||||
public static final String internalRouteKey = "internalRoute";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||||
|
|
||||||
|
@ -106,14 +110,14 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
|
internalRoute = getIntent().getBooleanExtra(INTERNAL_ROUTE_KEY, false);
|
||||||
|
|
||||||
setTheme(ThemeHelper.isLightThemeSelected(this)
|
setTheme(ThemeHelper.isLightThemeSelected(this)
|
||||||
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
@ -132,7 +136,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUrl(String url) {
|
private void handleUrl(final String url) {
|
||||||
disposables.add(Observable
|
disposables.add(Observable
|
||||||
.fromCallable(() -> {
|
.fromCallable(() -> {
|
||||||
if (currentServiceId == -1) {
|
if (currentServiceId == -1) {
|
||||||
|
@ -157,13 +161,14 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}, this::handleError));
|
}, this::handleError));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleError(Throwable error) {
|
private void handleError(final Throwable error) {
|
||||||
error.printStackTrace();
|
error.printStackTrace();
|
||||||
|
|
||||||
if (error instanceof ExtractionException) {
|
if (error instanceof ExtractionException) {
|
||||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null);
|
ExtractorHelper.handleGeneralException(this, -1, null, error,
|
||||||
|
UserAction.SOMETHING_ELSE, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
|
@ -175,8 +180,11 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onSuccess() {
|
protected void onSuccess() {
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
final SharedPreferences preferences = PreferenceManager
|
||||||
final String selectedChoiceKey = preferences.getString(getString(R.string.preferred_open_action_key), getString(R.string.preferred_open_action_default));
|
.getDefaultSharedPreferences(this);
|
||||||
|
final String selectedChoiceKey = preferences
|
||||||
|
.getString(getString(R.string.preferred_open_action_key),
|
||||||
|
getString(R.string.preferred_open_action_default));
|
||||||
|
|
||||||
final String showInfoKey = getString(R.string.show_info_key);
|
final String showInfoKey = getString(R.string.show_info_key);
|
||||||
final String videoPlayerKey = getString(R.string.video_player_key);
|
final String videoPlayerKey = getString(R.string.video_player_key);
|
||||||
|
@ -186,7 +194,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
final String alwaysAskKey = getString(R.string.always_ask_open_action_key);
|
final String alwaysAskKey = getString(R.string.always_ask_open_action_key);
|
||||||
|
|
||||||
if (selectedChoiceKey.equals(alwaysAskKey)) {
|
if (selectedChoiceKey.equals(alwaysAskKey)) {
|
||||||
final List<AdapterChoiceItem> choices = getChoicesForService(currentService, currentLinkType);
|
final List<AdapterChoiceItem> choices
|
||||||
|
= getChoicesForService(currentService, currentLinkType);
|
||||||
|
|
||||||
switch (choices.size()) {
|
switch (choices.size()) {
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -204,20 +213,26 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
} else if (selectedChoiceKey.equals(downloadKey)) {
|
} else if (selectedChoiceKey.equals(downloadKey)) {
|
||||||
handleChoice(downloadKey);
|
handleChoice(downloadKey);
|
||||||
} else {
|
} else {
|
||||||
final boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
|
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||||
final boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
|
getString(R.string.use_external_video_player_key), false);
|
||||||
final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey) || selectedChoiceKey.equals(popupPlayerKey);
|
final boolean isExtAudioEnabled = preferences.getBoolean(
|
||||||
|
getString(R.string.use_external_audio_player_key), false);
|
||||||
|
final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey)
|
||||||
|
|| selectedChoiceKey.equals(popupPlayerKey);
|
||||||
final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey);
|
final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey);
|
||||||
|
|
||||||
if (currentLinkType != LinkType.STREAM) {
|
if (currentLinkType != LinkType.STREAM) {
|
||||||
if (isExtAudioEnabled && isAudioPlayerSelected || isExtVideoEnabled && isVideoPlayerSelected) {
|
if (isExtAudioEnabled && isAudioPlayerSelected
|
||||||
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
|
|| isExtVideoEnabled && isVideoPlayerSelected) {
|
||||||
|
Toast.makeText(this, R.string.external_player_unsupported_link_type,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
handleChoice(showInfoKey);
|
handleChoice(showInfoKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = currentService.getServiceInfo().getMediaCapabilities();
|
final List<StreamingService.ServiceInfo.MediaCapability> capabilities
|
||||||
|
= currentService.getServiceInfo().getMediaCapabilities();
|
||||||
|
|
||||||
boolean serviceSupportsChoice = false;
|
boolean serviceSupportsChoice = false;
|
||||||
if (isVideoPlayerSelected) {
|
if (isVideoPlayerSelected) {
|
||||||
|
@ -239,7 +254,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
final Context themeWrapperContext = getThemeWrapperContext();
|
final Context themeWrapperContext = getThemeWrapperContext();
|
||||||
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
|
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
|
||||||
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
|
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
|
||||||
|
R.layout.preferred_player_dialog_view, null, false);
|
||||||
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
|
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
|
||||||
|
|
||||||
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
|
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
|
||||||
|
@ -250,7 +266,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
handleChoice(choice.key);
|
handleChoice(choice.key);
|
||||||
|
|
||||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||||
preferences.edit().putString(getString(R.string.preferred_open_action_key), choice.key).apply();
|
preferences.edit()
|
||||||
|
.putString(getString(R.string.preferred_open_action_key), choice.key)
|
||||||
|
.apply();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -261,7 +279,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
||||||
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
||||||
.setOnDismissListener((dialog) -> {
|
.setOnDismissListener((dialog) -> {
|
||||||
if (!selectionIsDownload) finish();
|
if (!selectionIsDownload) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
|
@ -270,10 +290,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
|
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
|
radioGroup.setOnCheckedChangeListener((group, checkedId) ->
|
||||||
|
setDialogButtonsState(alertDialog, true));
|
||||||
final View.OnClickListener radioButtonsClickListener = v -> {
|
final View.OnClickListener radioButtonsClickListener = v -> {
|
||||||
final int indexOfChild = radioGroup.indexOfChild(v);
|
final int indexOfChild = radioGroup.indexOfChild(v);
|
||||||
if (indexOfChild == -1) return;
|
if (indexOfChild == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
selectedPreviously = selectedRadioPosition;
|
selectedPreviously = selectedRadioPosition;
|
||||||
selectedRadioPosition = indexOfChild;
|
selectedRadioPosition = indexOfChild;
|
||||||
|
@ -285,18 +308,21 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
|
|
||||||
int id = 12345;
|
int id = 12345;
|
||||||
for (AdapterChoiceItem item : choices) {
|
for (AdapterChoiceItem item : choices) {
|
||||||
final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
final RadioButton radioButton
|
||||||
|
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||||
radioButton.setText(item.description);
|
radioButton.setText(item.description);
|
||||||
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
|
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
|
||||||
radioButton.setChecked(false);
|
radioButton.setChecked(false);
|
||||||
radioButton.setId(id++);
|
radioButton.setId(id++);
|
||||||
radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
radioButton.setOnClickListener(radioButtonsClickListener);
|
radioButton.setOnClickListener(radioButtonsClickListener);
|
||||||
radioGroup.addView(radioButton);
|
radioGroup.addView(radioButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedRadioPosition == -1) {
|
if (selectedRadioPosition == -1) {
|
||||||
final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_open_action_last_selected_key), null);
|
final String lastSelectedPlayer = preferences.getString(
|
||||||
|
getString(R.string.preferred_open_action_last_selected_key), null);
|
||||||
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
||||||
for (int i = 0; i < choices.size(); i++) {
|
for (int i = 0; i < choices.size(); i++) {
|
||||||
AdapterChoiceItem c = choices.get(i);
|
AdapterChoiceItem c = choices.get(i);
|
||||||
|
@ -317,46 +343,58 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
alertDialog.show();
|
alertDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AdapterChoiceItem> getChoicesForService(StreamingService service, LinkType linkType) {
|
private List<AdapterChoiceItem> getChoicesForService(final StreamingService service,
|
||||||
|
final LinkType linkType) {
|
||||||
final Context context = getThemeWrapperContext();
|
final Context context = getThemeWrapperContext();
|
||||||
|
|
||||||
final List<AdapterChoiceItem> returnList = new ArrayList<>();
|
final List<AdapterChoiceItem> returnList = new ArrayList<>();
|
||||||
final List<StreamingService.ServiceInfo.MediaCapability> capabilities = service.getServiceInfo().getMediaCapabilities();
|
final List<StreamingService.ServiceInfo.MediaCapability> capabilities
|
||||||
|
= service.getServiceInfo().getMediaCapabilities();
|
||||||
|
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
final SharedPreferences preferences = PreferenceManager
|
||||||
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
|
.getDefaultSharedPreferences(this);
|
||||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
|
boolean isExtVideoEnabled = preferences.getBoolean(
|
||||||
|
getString(R.string.use_external_video_player_key), false);
|
||||||
|
boolean isExtAudioEnabled = preferences.getBoolean(
|
||||||
|
getString(R.string.use_external_audio_player_key), false);
|
||||||
|
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key), getString(R.string.show_info),
|
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
||||||
|
getString(R.string.show_info),
|
||||||
resolveResourceIdFromAttr(context, R.attr.info)));
|
resolveResourceIdFromAttr(context, R.attr.info)));
|
||||||
|
|
||||||
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
|
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
|
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
|
||||||
|
getString(R.string.video_player),
|
||||||
resolveResourceIdFromAttr(context, R.attr.play)));
|
resolveResourceIdFromAttr(context, R.attr.play)));
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
|
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
|
||||||
|
getString(R.string.popup_player),
|
||||||
resolveResourceIdFromAttr(context, R.attr.popup)));
|
resolveResourceIdFromAttr(context, R.attr.popup)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
|
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
|
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
|
||||||
|
getString(R.string.background_player),
|
||||||
resolveResourceIdFromAttr(context, R.attr.audio)));
|
resolveResourceIdFromAttr(context, R.attr.audio)));
|
||||||
}
|
}
|
||||||
|
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.download_key), getString(R.string.download),
|
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
||||||
|
getString(R.string.download),
|
||||||
resolveResourceIdFromAttr(context, R.attr.download)));
|
resolveResourceIdFromAttr(context, R.attr.download)));
|
||||||
|
|
||||||
return returnList;
|
return returnList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context getThemeWrapperContext() {
|
private Context getThemeWrapperContext() {
|
||||||
return new ContextThemeWrapper(this,
|
return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
|
||||||
ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
|
? R.style.LightTheme : R.style.DarkTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDialogButtonsState(AlertDialog dialog, boolean state) {
|
private void setDialogButtonsState(final AlertDialog dialog, final boolean state) {
|
||||||
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
|
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
|
||||||
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||||
if (negativeButton == null || positiveButton == null) return;
|
if (negativeButton == null || positiveButton == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
negativeButton.setEnabled(state);
|
negativeButton.setEnabled(state);
|
||||||
positiveButton.setEnabled(state);
|
positiveButton.setEnabled(state);
|
||||||
|
@ -372,21 +410,25 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleChoice(final String selectedChoiceKey) {
|
private void handleChoice(final String selectedChoiceKey) {
|
||||||
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
|
final List<String> validChoicesList = Arrays.asList(getResources()
|
||||||
|
.getStringArray(R.array.preferred_open_action_values_list));
|
||||||
if (validChoicesList.contains(selectedChoiceKey)) {
|
if (validChoicesList.contains(selectedChoiceKey)) {
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).edit()
|
PreferenceManager.getDefaultSharedPreferences(this).edit()
|
||||||
.putString(getString(R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
|
.putString(getString(
|
||||||
|
R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
|
if (selectedChoiceKey.equals(getString(R.string.popup_player_key))
|
||||||
|
&& !PermissionHelper.isPopupEnabled(this)) {
|
||||||
PermissionHelper.showPopupEnablementToast(this);
|
PermissionHelper.showPopupEnablementToast(this);
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedChoiceKey.equals(getString(R.string.download_key))) {
|
if (selectedChoiceKey.equals(getString(R.string.download_key))) {
|
||||||
if (PermissionHelper.checkStoragePermissions(this, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
if (PermissionHelper.checkStoragePermissions(this,
|
||||||
|
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
||||||
selectionIsDownload = true;
|
selectionIsDownload = true;
|
||||||
openDownloadDialog();
|
openDownloadDialog();
|
||||||
}
|
}
|
||||||
|
@ -414,7 +456,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Intent intent = new Intent(this, FetcherService.class);
|
final Intent intent = new Intent(this, FetcherService.class);
|
||||||
final Choice choice = new Choice(currentService.getServiceId(), currentLinkType, currentUrl, selectedChoiceKey);
|
final Choice choice = new Choice(currentService.getServiceId(), currentLinkType,
|
||||||
|
currentUrl, selectedChoiceKey);
|
||||||
intent.putExtra(FetcherService.KEY_CHOICE, choice);
|
intent.putExtra(FetcherService.KEY_CHOICE, choice);
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
|
||||||
|
@ -427,12 +470,11 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe((@NonNull StreamInfo result) -> {
|
.subscribe((@NonNull StreamInfo result) -> {
|
||||||
List<VideoStream> sortedVideoStreams = ListHelper.getSortedStreamVideosList(this,
|
List<VideoStream> sortedVideoStreams = ListHelper
|
||||||
result.getVideoStreams(),
|
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
||||||
result.getVideoOnlyStreams(),
|
result.getVideoOnlyStreams(), false);
|
||||||
false);
|
int selectedVideoStreamIndex = ListHelper
|
||||||
int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this,
|
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
||||||
sortedVideoStreams);
|
|
||||||
|
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||||
|
@ -450,7 +492,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
|
@NonNull final String[] permissions,
|
||||||
|
@NonNull final int[] grantResults) {
|
||||||
for (int i : grantResults) {
|
for (int i : grantResults) {
|
||||||
if (i == PackageManager.PERMISSION_DENIED) {
|
if (i == PackageManager.PERMISSION_DENIED) {
|
||||||
finish();
|
finish();
|
||||||
|
@ -462,12 +506,73 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Service Fetcher
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private String removeHeadingGibberish(final String input) {
|
||||||
|
int start = 0;
|
||||||
|
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
|
||||||
|
if (!input.substring(i, i + 1).matches("\\p{L}")) {
|
||||||
|
start = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return input.substring(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private String trim(final String input) {
|
||||||
|
if (input == null || input.length() < 1) {
|
||||||
|
return input;
|
||||||
|
} else {
|
||||||
|
String output = input;
|
||||||
|
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
|
||||||
|
output = output.substring(1);
|
||||||
|
}
|
||||||
|
while (output.length() > 0
|
||||||
|
&& output.substring(output.length() - 1).matches(REGEX_REMOVE_FROM_URL)) {
|
||||||
|
output = output.substring(0, output.length() - 1);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all Strings which look remotely like URLs from a text.
|
||||||
|
* Used if NewPipe was called through share menu.
|
||||||
|
*
|
||||||
|
* @param sharedText text to scan for URLs.
|
||||||
|
* @return potential URLs
|
||||||
|
*/
|
||||||
|
protected String[] getUris(final String sharedText) {
|
||||||
|
final Collection<String> result = new HashSet<>();
|
||||||
|
if (sharedText != null) {
|
||||||
|
final String[] array = sharedText.split("\\p{Space}");
|
||||||
|
for (String s : array) {
|
||||||
|
s = trim(s);
|
||||||
|
if (s.length() != 0) {
|
||||||
|
if (s.matches(".+://.+")) {
|
||||||
|
result.add(removeHeadingGibberish(s));
|
||||||
|
} else if (s.matches(".+\\..+")) {
|
||||||
|
result.add("http://" + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toArray(new String[result.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
private static class AdapterChoiceItem {
|
private static class AdapterChoiceItem {
|
||||||
final String description, key;
|
final String description;
|
||||||
|
final String key;
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
final int icon;
|
final int icon;
|
||||||
|
|
||||||
AdapterChoiceItem(String key, String description, int icon) {
|
AdapterChoiceItem(final String key, final String description, final int icon) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
|
@ -476,10 +581,12 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static class Choice implements Serializable {
|
private static class Choice implements Serializable {
|
||||||
final int serviceId;
|
final int serviceId;
|
||||||
final String url, playerChoice;
|
final String url;
|
||||||
|
final String playerChoice;
|
||||||
final LinkType linkType;
|
final LinkType linkType;
|
||||||
|
|
||||||
Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
|
Choice(final int serviceId, final LinkType linkType,
|
||||||
|
final String url, final String playerChoice) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
this.linkType = linkType;
|
this.linkType = linkType;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
@ -492,14 +599,10 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Service Fetcher
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
public static class FetcherService extends IntentService {
|
public static class FetcherService extends IntentService {
|
||||||
|
|
||||||
private static final int ID = 456;
|
|
||||||
public static final String KEY_CHOICE = "key_choice";
|
public static final String KEY_CHOICE = "key_choice";
|
||||||
|
private static final int ID = 456;
|
||||||
private Disposable fetcher;
|
private Disposable fetcher;
|
||||||
|
|
||||||
public FetcherService() {
|
public FetcherService() {
|
||||||
|
@ -513,16 +616,20 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onHandleIntent(@Nullable Intent intent) {
|
protected void onHandleIntent(@Nullable final Intent intent) {
|
||||||
if (intent == null) return;
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
|
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
|
||||||
if (!(serializable instanceof Choice)) return;
|
if (!(serializable instanceof Choice)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Choice playerChoice = (Choice) serializable;
|
Choice playerChoice = (Choice) serializable;
|
||||||
handleChoice(playerChoice);
|
handleChoice(playerChoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleChoice(Choice choice) {
|
public void handleChoice(final Choice choice) {
|
||||||
Single<? extends Info> single = null;
|
Single<? extends Info> single = null;
|
||||||
UserAction userAction = UserAction.SOMETHING_ELSE;
|
UserAction userAction = UserAction.SOMETHING_ELSE;
|
||||||
|
|
||||||
|
@ -549,22 +656,27 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(info -> {
|
.subscribe(info -> {
|
||||||
resultHandler.accept(info);
|
resultHandler.accept(info);
|
||||||
if (fetcher != null) fetcher.dispose();
|
if (fetcher != null) {
|
||||||
|
fetcher.dispose();
|
||||||
|
}
|
||||||
}, throwable -> ExtractorHelper.handleGeneralException(this,
|
}, throwable -> ExtractorHelper.handleGeneralException(this,
|
||||||
choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice));
|
choice.serviceId, choice.url, throwable, finalUserAction,
|
||||||
|
", opened with " + choice.playerChoice));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<Info> getResultHandler(Choice choice) {
|
public Consumer<Info> getResultHandler(final Choice choice) {
|
||||||
return info -> {
|
return info -> {
|
||||||
final String videoPlayerKey = getString(R.string.video_player_key);
|
final String videoPlayerKey = getString(R.string.video_player_key);
|
||||||
final String backgroundPlayerKey = getString(R.string.background_player_key);
|
final String backgroundPlayerKey = getString(R.string.background_player_key);
|
||||||
final String popupPlayerKey = getString(R.string.popup_player_key);
|
final String popupPlayerKey = getString(R.string.popup_player_key);
|
||||||
|
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
final SharedPreferences preferences = PreferenceManager
|
||||||
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
|
.getDefaultSharedPreferences(this);
|
||||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
|
boolean isExtVideoEnabled = preferences.getBoolean(
|
||||||
;
|
getString(R.string.use_external_video_player_key), false);
|
||||||
|
boolean isExtAudioEnabled = preferences.getBoolean(
|
||||||
|
getString(R.string.use_external_audio_player_key), false);
|
||||||
|
|
||||||
PlayQueue playQueue;
|
PlayQueue playQueue;
|
||||||
String playerChoice = choice.playerChoice;
|
String playerChoice = choice.playerChoice;
|
||||||
|
@ -590,7 +702,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
|
if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
|
||||||
playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
|
playQueue = info instanceof ChannelInfo
|
||||||
|
? new ChannelPlayQueue((ChannelInfo) info)
|
||||||
|
: new PlaylistPlayQueue((PlaylistInfo) info);
|
||||||
|
|
||||||
if (playerChoice.equals(videoPlayerKey)) {
|
if (playerChoice.equals(videoPlayerKey)) {
|
||||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||||
|
@ -607,7 +721,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
if (fetcher != null) fetcher.dispose();
|
if (fetcher != null) {
|
||||||
|
fetcher.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationCompat.Builder createNotification() {
|
private NotificationCompat.Builder createNotification() {
|
||||||
|
@ -615,8 +731,10 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.setContentTitle(getString(R.string.preferred_player_fetcher_notification_title))
|
.setContentTitle(
|
||||||
.setContentText(getString(R.string.preferred_player_fetcher_notification_message));
|
getString(R.string.preferred_player_fetcher_notification_title))
|
||||||
|
.setContentText(
|
||||||
|
getString(R.string.preferred_player_fetcher_notification_message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,7 +743,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getUrl(Intent intent) {
|
private String getUrl(final Intent intent) {
|
||||||
String foundUrl = null;
|
String foundUrl = null;
|
||||||
if (intent.getData() != null) {
|
if (intent.getData() != null) {
|
||||||
// Called from another app
|
// Called from another app
|
||||||
|
|
|
@ -4,21 +4,22 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -27,26 +28,41 @@ import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
public class AboutActivity extends AppCompatActivity {
|
public class AboutActivity extends AppCompatActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all software components
|
* List of all software components.
|
||||||
*/
|
*/
|
||||||
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
|
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
|
||||||
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
|
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
|
||||||
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
|
||||||
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT),
|
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
|
||||||
new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
|
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
||||||
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
|
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
|
||||||
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
|
"https://github.com/jhy/jsoup", StandardLicenses.MIT),
|
||||||
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
new SoftwareComponent("Rhino", "2015", "Mozilla",
|
||||||
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
|
"https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
|
||||||
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
|
||||||
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
"http://www.acra.ch", StandardLicenses.APACHE2),
|
||||||
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
|
||||||
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
|
"https://github.com/nostra13/Android-Universal-Image-Loader",
|
||||||
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
|
StandardLicenses.APACHE2),
|
||||||
new SoftwareComponent("Markwon", "2017 - 2020", "Noties", "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
|
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
|
||||||
new SoftwareComponent("Groupie", "2016", "Lisa Wray", "https://github.com/lisawray/groupie", StandardLicenses.MIT)
|
"https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
|
||||||
|
"https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
|
||||||
|
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc",
|
||||||
|
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors",
|
||||||
|
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
|
||||||
|
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton",
|
||||||
|
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
|
||||||
|
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("Markwon", "2017 - 2020", "Noties",
|
||||||
|
"https://github.com/noties/Markwon", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("Groupie", "2016", "Lisa Wray",
|
||||||
|
"https://github.com/lisawray/groupie", StandardLicenses.MIT)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,7 +81,7 @@ public class AboutActivity extends AppCompatActivity {
|
||||||
private ViewPager mViewPager;
|
private ViewPager mViewPager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(this);
|
assureCorrectAppLanguage(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
|
@ -88,10 +104,8 @@ public class AboutActivity extends AppCompatActivity {
|
||||||
tabLayout.setupWithViewPager(mViewPager);
|
tabLayout.setupWithViewPager(mViewPager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
|
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
@ -107,21 +121,20 @@ public class AboutActivity extends AppCompatActivity {
|
||||||
* A placeholder fragment containing a simple view.
|
* A placeholder fragment containing a simple view.
|
||||||
*/
|
*/
|
||||||
public static class AboutFragment extends Fragment {
|
public static class AboutFragment extends Fragment {
|
||||||
|
public AboutFragment() { }
|
||||||
public AboutFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new instance of this fragment for the given section
|
* Created a new instance of this fragment for the given section number.
|
||||||
* number.
|
*
|
||||||
|
* @return New instance of {@link AboutFragment}
|
||||||
*/
|
*/
|
||||||
public static AboutFragment newInstance() {
|
public static AboutFragment newInstance() {
|
||||||
return new AboutFragment();
|
return new AboutFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||||
Context context = this.getContext();
|
Context context = this.getContext();
|
||||||
|
|
||||||
|
@ -129,40 +142,42 @@ public class AboutActivity extends AppCompatActivity {
|
||||||
version.setText(BuildConfig.VERSION_NAME);
|
version.setText(BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
View githubLink = rootView.findViewById(R.id.github_link);
|
View githubLink = rootView.findViewById(R.id.github_link);
|
||||||
githubLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.github_url), context));
|
githubLink.setOnClickListener(nv ->
|
||||||
|
openWebsite(context.getString(R.string.github_url), context));
|
||||||
|
|
||||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
View donationLink = rootView.findViewById(R.id.donation_link);
|
||||||
donationLink.setOnClickListener(v -> openWebsite(context.getString(R.string.donation_url), context));
|
donationLink.setOnClickListener(v ->
|
||||||
|
openWebsite(context.getString(R.string.donation_url), context));
|
||||||
|
|
||||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
View websiteLink = rootView.findViewById(R.id.website_link);
|
||||||
websiteLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.website_url), context));
|
websiteLink.setOnClickListener(nv ->
|
||||||
|
openWebsite(context.getString(R.string.website_url), context));
|
||||||
|
|
||||||
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||||
privacyPolicyLink.setOnClickListener(v -> openWebsite(context.getString(R.string.privacy_policy_url), context));
|
privacyPolicyLink.setOnClickListener(v ->
|
||||||
|
openWebsite(context.getString(R.string.privacy_policy_url), context));
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWebsite(String url, Context context) {
|
private void openWebsite(final String url, final Context context) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
||||||
* one of the sections/tabs/pages.
|
* one of the sections/tabs/pages.
|
||||||
*/
|
*/
|
||||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
public SectionsPagerAdapter(final FragmentManager fm) {
|
||||||
public SectionsPagerAdapter(FragmentManager fm) {
|
|
||||||
super(fm);
|
super(fm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(final int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
return AboutFragment.newInstance();
|
return AboutFragment.newInstance();
|
||||||
|
@ -179,7 +194,7 @@ public class AboutActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(final int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
return getString(R.string.tab_about);
|
return getString(R.string.tab_about);
|
||||||
|
|
|
@ -5,18 +5,17 @@ import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A software license
|
* Class for storing information about a software license.
|
||||||
*/
|
*/
|
||||||
public class License implements Parcelable {
|
public class License implements Parcelable {
|
||||||
|
|
||||||
public static final Creator<License> CREATOR = new Creator<License>() {
|
public static final Creator<License> CREATOR = new Creator<License>() {
|
||||||
@Override
|
@Override
|
||||||
public License createFromParcel(Parcel source) {
|
public License createFromParcel(final Parcel source) {
|
||||||
return new License(source);
|
return new License(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public License[] newArray(int size) {
|
public License[] newArray(final int size) {
|
||||||
return new License[size];
|
return new License[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,16 +23,22 @@ public class License implements Parcelable {
|
||||||
private final String name;
|
private final String name;
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
public License(String name, String abbreviation, String filename) {
|
public License(final String name, final String abbreviation, final String filename) {
|
||||||
if(name == null) throw new NullPointerException("name is null");
|
if (name == null) {
|
||||||
if(abbreviation == null) throw new NullPointerException("abbreviation is null");
|
throw new NullPointerException("name is null");
|
||||||
if(filename == null) throw new NullPointerException("filename is null");
|
}
|
||||||
|
if (abbreviation == null) {
|
||||||
|
throw new NullPointerException("abbreviation is null");
|
||||||
|
}
|
||||||
|
if (filename == null) {
|
||||||
|
throw new NullPointerException("filename is null");
|
||||||
|
}
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.abbreviation = abbreviation;
|
this.abbreviation = abbreviation;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected License(Parcel in) {
|
protected License(final Parcel in) {
|
||||||
this.filename = in.readString();
|
this.filename = in.readString();
|
||||||
this.abbreviation = in.readString();
|
this.abbreviation = in.readString();
|
||||||
this.name = in.readString();
|
this.name = in.readString();
|
||||||
|
@ -61,7 +66,7 @@ public class License implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
dest.writeString(this.filename);
|
dest.writeString(this.filename);
|
||||||
dest.writeString(this.abbreviation);
|
dest.writeString(this.abbreviation);
|
||||||
dest.writeString(this.name);
|
dest.writeString(this.name);
|
||||||
|
|
|
@ -5,26 +5,32 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.view.*;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment containing the software licenses
|
* Fragment containing the software licenses.
|
||||||
*/
|
*/
|
||||||
public class LicenseFragment extends Fragment {
|
public class LicenseFragment extends Fragment {
|
||||||
|
|
||||||
private static final String ARG_COMPONENTS = "components";
|
private static final String ARG_COMPONENTS = "components";
|
||||||
private SoftwareComponent[] softwareComponents;
|
private SoftwareComponent[] softwareComponents;
|
||||||
private SoftwareComponent mComponentForContextMenu;
|
private SoftwareComponent mComponentForContextMenu;
|
||||||
|
|
||||||
public static LicenseFragment newInstance(SoftwareComponent[] softwareComponents) {
|
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||||
if(softwareComponents == null) {
|
if (softwareComponents == null) {
|
||||||
throw new NullPointerException("softwareComponents is null");
|
throw new NullPointerException("softwareComponents is null");
|
||||||
}
|
}
|
||||||
LicenseFragment fragment = new LicenseFragment();
|
LicenseFragment fragment = new LicenseFragment();
|
||||||
|
@ -35,23 +41,25 @@ public class LicenseFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a popup containing the license
|
* Shows a popup containing the license.
|
||||||
|
*
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param license the license to show
|
* @param license the license to show
|
||||||
*/
|
*/
|
||||||
public static void showLicense(Context context, License license) {
|
public static void showLicense(final Context context, final License license) {
|
||||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
new LicenseFragmentHelper((Activity) context).execute(license);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
softwareComponents = (SoftwareComponent[]) getArguments().getParcelableArray(ARG_COMPONENTS);
|
softwareComponents = (SoftwareComponent[]) getArguments()
|
||||||
|
.getParcelableArray(ARG_COMPONENTS);
|
||||||
|
|
||||||
// Sort components by name
|
// Sort components by name
|
||||||
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
|
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(SoftwareComponent o1, SoftwareComponent o2) {
|
public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
|
||||||
return o1.getName().compareTo(o2.getName());
|
return o1.getName().compareTo(o2.getName());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -59,7 +67,8 @@ public class LicenseFragment extends Fragment {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||||
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||||
|
|
||||||
|
@ -67,7 +76,8 @@ public class LicenseFragment extends Fragment {
|
||||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
||||||
|
|
||||||
for (final SoftwareComponent component : softwareComponents) {
|
for (final SoftwareComponent component : softwareComponents) {
|
||||||
View componentView = inflater.inflate(R.layout.item_software_component, container, false);
|
View componentView = inflater
|
||||||
|
.inflate(R.layout.item_software_component, container, false);
|
||||||
TextView softwareName = componentView.findViewById(R.id.name);
|
TextView softwareName = componentView.findViewById(R.id.name);
|
||||||
TextView copyright = componentView.findViewById(R.id.copyright);
|
TextView copyright = componentView.findViewById(R.id.copyright);
|
||||||
softwareName.setText(component.getName());
|
softwareName.setText(component.getName());
|
||||||
|
@ -79,7 +89,7 @@ public class LicenseFragment extends Fragment {
|
||||||
componentView.setTag(component);
|
componentView.setTag(component);
|
||||||
componentView.setOnClickListener(new View.OnClickListener() {
|
componentView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(final View v) {
|
||||||
Context context = v.getContext();
|
Context context = v.getContext();
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
showLicense(context, component.getLicense());
|
showLicense(context, component.getLicense());
|
||||||
|
@ -93,7 +103,8 @@ public class LicenseFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
||||||
|
final ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
MenuInflater inflater = getActivity().getMenuInflater();
|
MenuInflater inflater = getActivity().getMenuInflater();
|
||||||
SoftwareComponent component = (SoftwareComponent) v.getTag();
|
SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||||
menu.setHeaderTitle(component.getName());
|
menu.setHeaderTitle(component.getName());
|
||||||
|
@ -103,7 +114,7 @@ public class LicenseFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
public boolean onContextItemSelected(final MenuItem item) {
|
||||||
// item.getMenuInfo() is null so we use the tag of the view
|
// item.getMenuInfo() is null so we use the tag of the view
|
||||||
final SoftwareComponent component = mComponentForContextMenu;
|
final SoftwareComponent component = mComponentForContextMenu;
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
|
@ -119,14 +130,14 @@ public class LicenseFragment extends Fragment {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWebsite(String componentLink) {
|
private void openWebsite(final String componentLink) {
|
||||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
|
||||||
startActivity(browserIntent);
|
startActivity(browserIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(final View v) {
|
||||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,103 @@ package org.schabi.newpipe.about;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import android.webkit.WebView;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
|
private final WeakReference<Activity> weakReference;
|
||||||
final WeakReference<Activity> weakReference;
|
|
||||||
private License license;
|
private License license;
|
||||||
|
|
||||||
public LicenseFragmentHelper(@Nullable Activity activity) {
|
public LicenseFragmentHelper(@Nullable final Activity activity) {
|
||||||
weakReference = new WeakReference<>(activity);
|
weakReference = new WeakReference<>(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getFinishString(final Activity activity) {
|
||||||
|
return activity.getApplicationContext().getResources().getString(R.string.finish);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context the context to use
|
||||||
|
* @param license the license
|
||||||
|
* @return String which contains a HTML formatted license page
|
||||||
|
* styled according to the context's theme
|
||||||
|
*/
|
||||||
|
public static String getFormattedLicense(final Context context, final License license) {
|
||||||
|
if (context == null) {
|
||||||
|
throw new NullPointerException("context is null");
|
||||||
|
}
|
||||||
|
if (license == null) {
|
||||||
|
throw new NullPointerException("license is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder licenseContent = new StringBuilder();
|
||||||
|
String webViewData;
|
||||||
|
try {
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||||
|
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
|
||||||
|
String str;
|
||||||
|
while ((str = in.readLine()) != null) {
|
||||||
|
licenseContent.append(str);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||||
|
String[] insert = licenseContent.toString().split("</head>");
|
||||||
|
webViewData = insert[0] + "<style type=\"text/css\">"
|
||||||
|
+ getLicenseStylesheet(context) + "</style></head>"
|
||||||
|
+ insert[1];
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new NullPointerException("could not get license file:"
|
||||||
|
+ getLicenseStylesheet(context));
|
||||||
|
}
|
||||||
|
return webViewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context
|
||||||
|
* @return String which is a CSS stylesheet according to the context's theme
|
||||||
|
*/
|
||||||
|
public static String getLicenseStylesheet(final Context context) {
|
||||||
|
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||||
|
return "body{padding:12px 15px;margin:0;background:#"
|
||||||
|
+ getHexRGBColor(context, isLightTheme
|
||||||
|
? R.color.light_license_background_color
|
||||||
|
: R.color.dark_license_background_color)
|
||||||
|
+ ";color:#"
|
||||||
|
+ getHexRGBColor(context, isLightTheme
|
||||||
|
? R.color.light_license_text_color
|
||||||
|
: R.color.dark_license_text_color) + ";}"
|
||||||
|
+ "a[href]{color:#"
|
||||||
|
+ getHexRGBColor(context, isLightTheme
|
||||||
|
? R.color.light_youtube_primary_color
|
||||||
|
: R.color.dark_youtube_primary_color) + ";}"
|
||||||
|
+ "pre{white-space: pre-wrap;}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast R.color to a hexadecimal color value.
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @param color the color number from R.color
|
||||||
|
* @return a six characters long String with hexadecimal RGB values
|
||||||
|
*/
|
||||||
|
public static String getHexRGBColor(final Context context, final int color) {
|
||||||
|
return context.getResources().getString(color).substring(3);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Activity getActivity() {
|
private Activity getActivity() {
|
||||||
Activity activity = weakReference.get();
|
Activity activity = weakReference.get();
|
||||||
|
@ -38,13 +111,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer doInBackground(Object... objects) {
|
protected Integer doInBackground(final Object... objects) {
|
||||||
license = (License) objects[0];
|
license = (License) objects[0];
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Integer result) {
|
protected void onPostExecute(final Integer result) {
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -63,74 +136,4 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
alert.show();
|
alert.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getFinishString(Activity activity) {
|
|
||||||
return activity.getApplicationContext().getResources().getString(R.string.finish);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context the context to use
|
|
||||||
* @param license the license
|
|
||||||
* @return String which contains a HTML formatted license page styled according to the context's theme
|
|
||||||
*/
|
|
||||||
public static String getFormattedLicense(Context context, License license) {
|
|
||||||
if(context == null) {
|
|
||||||
throw new NullPointerException("context is null");
|
|
||||||
}
|
|
||||||
if(license == null) {
|
|
||||||
throw new NullPointerException("license is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder licenseContent = new StringBuilder();
|
|
||||||
String webViewData;
|
|
||||||
try {
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(context.getAssets().open(license.getFilename()), "UTF-8"));
|
|
||||||
String str;
|
|
||||||
while ((str = in.readLine()) != null) {
|
|
||||||
licenseContent.append(str);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
|
|
||||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
|
||||||
String[] insert = licenseContent.toString().split("</head>");
|
|
||||||
webViewData = insert[0] + "<style type=\"text/css\">"
|
|
||||||
+ getLicenseStylesheet(context) + "</style></head>"
|
|
||||||
+ insert[1];
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new NullPointerException("could not get license file:" + getLicenseStylesheet(context));
|
|
||||||
}
|
|
||||||
return webViewData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @return String which is a CSS stylesheet according to the context's theme
|
|
||||||
*/
|
|
||||||
public static String getLicenseStylesheet(Context context) {
|
|
||||||
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
|
||||||
return "body{padding:12px 15px;margin:0;background:#"
|
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_license_background_color
|
|
||||||
: R.color.dark_license_background_color)
|
|
||||||
+ ";color:#"
|
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_license_text_color
|
|
||||||
: R.color.dark_license_text_color) + ";}"
|
|
||||||
+ "a[href]{color:#"
|
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_youtube_primary_color
|
|
||||||
: R.color.dark_youtube_primary_color) + ";}"
|
|
||||||
+ "pre{white-space: pre-wrap;}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cast R.color to a hexadecimal color value
|
|
||||||
* @param context the context to use
|
|
||||||
* @param color the color number from R.color
|
|
||||||
* @return a six characters long String with hexadecimal RGB values
|
|
||||||
*/
|
|
||||||
public static String getHexRGBColor(Context context, int color) {
|
|
||||||
return context.getResources().getString(color).substring(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,44 @@ import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
public class SoftwareComponent implements Parcelable {
|
public class SoftwareComponent implements Parcelable {
|
||||||
|
|
||||||
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
|
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
|
||||||
@Override
|
@Override
|
||||||
public SoftwareComponent createFromParcel(Parcel source) {
|
public SoftwareComponent createFromParcel(final Parcel source) {
|
||||||
return new SoftwareComponent(source);
|
return new SoftwareComponent(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SoftwareComponent[] newArray(int size) {
|
public SoftwareComponent[] newArray(final int size) {
|
||||||
return new SoftwareComponent[size];
|
return new SoftwareComponent[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final License license;
|
||||||
|
private final String name;
|
||||||
|
private final String years;
|
||||||
|
private final String copyrightOwner;
|
||||||
|
private final String link;
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public SoftwareComponent(final String name, final String years, final String copyrightOwner,
|
||||||
|
final String link, final License license) {
|
||||||
|
this.name = name;
|
||||||
|
this.years = years;
|
||||||
|
this.copyrightOwner = copyrightOwner;
|
||||||
|
this.link = link;
|
||||||
|
this.license = license;
|
||||||
|
this.version = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SoftwareComponent(final Parcel in) {
|
||||||
|
this.name = in.readString();
|
||||||
|
this.license = in.readParcelable(License.class.getClassLoader());
|
||||||
|
this.copyrightOwner = in.readString();
|
||||||
|
this.link = in.readString();
|
||||||
|
this.years = in.readString();
|
||||||
|
this.version = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -37,31 +62,6 @@ public class SoftwareComponent implements Parcelable {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final License license;
|
|
||||||
private final String name;
|
|
||||||
private final String years;
|
|
||||||
private final String copyrightOwner;
|
|
||||||
private final String link;
|
|
||||||
private final String version;
|
|
||||||
|
|
||||||
public SoftwareComponent(String name, String years, String copyrightOwner, String link, License license) {
|
|
||||||
this.name = name;
|
|
||||||
this.years = years;
|
|
||||||
this.copyrightOwner = copyrightOwner;
|
|
||||||
this.link = link;
|
|
||||||
this.license = license;
|
|
||||||
this.version = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SoftwareComponent(Parcel in) {
|
|
||||||
this.name = in.readString();
|
|
||||||
this.license = in.readParcelable(License.class.getClassLoader());
|
|
||||||
this.copyrightOwner = in.readString();
|
|
||||||
this.link = in.readString();
|
|
||||||
this.years = in.readString();
|
|
||||||
this.version = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public License getLicense() {
|
public License getLicense() {
|
||||||
return license;
|
return license;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ public class SoftwareComponent implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
dest.writeString(name);
|
dest.writeString(name);
|
||||||
dest.writeParcelable(license, flags);
|
dest.writeParcelable(license, flags);
|
||||||
dest.writeString(copyrightOwner);
|
dest.writeString(copyrightOwner);
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
package org.schabi.newpipe.about;
|
package org.schabi.newpipe.about;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard software licenses
|
* Class containing information about standard software licenses.
|
||||||
*/
|
*/
|
||||||
public final class StandardLicenses {
|
public final class StandardLicenses {
|
||||||
public static final License GPL2 = new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html");
|
public static final License GPL2
|
||||||
public static final License GPL3 = new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
|
= new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html");
|
||||||
public static final License APACHE2 = new License("Apache License, Version 2.0", "ALv2", "apache2.html");
|
public static final License GPL3
|
||||||
public static final License MPL2 = new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
|
= new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
|
||||||
public static final License MIT = new License("MIT License", "MIT", "mit.html");
|
public static final License APACHE2
|
||||||
|
= new License("Apache License, Version 2.0", "ALv2", "apache2.html");
|
||||||
|
public static final License MPL2
|
||||||
|
= new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
|
||||||
|
public static final License MIT
|
||||||
|
= new License("MIT License", "MIT", "mit.html");
|
||||||
|
|
||||||
|
private StandardLicenses() { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,14 +46,20 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||||
public abstract SearchHistoryDAO searchHistoryDAO();
|
public abstract SearchHistoryDAO searchHistoryDAO();
|
||||||
|
|
||||||
public abstract StreamDAO streamDAO();
|
public abstract StreamDAO streamDAO();
|
||||||
|
|
||||||
public abstract StreamHistoryDAO streamHistoryDAO();
|
public abstract StreamHistoryDAO streamHistoryDAO();
|
||||||
|
|
||||||
public abstract StreamStateDAO streamStateDAO();
|
public abstract StreamStateDAO streamStateDAO();
|
||||||
|
|
||||||
public abstract PlaylistDAO playlistDAO();
|
public abstract PlaylistDAO playlistDAO();
|
||||||
|
|
||||||
public abstract PlaylistStreamDAO playlistStreamDAO();
|
public abstract PlaylistStreamDAO playlistStreamDAO();
|
||||||
|
|
||||||
public abstract PlaylistRemoteDAO playlistRemoteDAO();
|
public abstract PlaylistRemoteDAO playlistRemoteDAO();
|
||||||
|
|
||||||
public abstract FeedDAO feedDAO();
|
public abstract FeedDAO feedDAO();
|
||||||
|
|
||||||
public abstract FeedGroupDAO feedGroupDAO();
|
public abstract FeedGroupDAO feedGroupDAO();
|
||||||
|
|
||||||
public abstract SubscriptionDAO subscriptionDAO();
|
public abstract SubscriptionDAO subscriptionDAO();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@ import io.reactivex.Flowable;
|
||||||
public interface BasicDAO<Entity> {
|
public interface BasicDAO<Entity> {
|
||||||
/* Inserts */
|
/* Inserts */
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||||
long insert(final Entity entity);
|
long insert(Entity entity);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||||
List<Long> insertAll(final Entity... entities);
|
List<Long> insertAll(Entity... entities);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||||
List<Long> insertAll(final Collection<Entity> entities);
|
List<Long> insertAll(Collection<Entity> entities);
|
||||||
|
|
||||||
/* Searches */
|
/* Searches */
|
||||||
Flowable<List<Entity>> getAll();
|
Flowable<List<Entity>> getAll();
|
||||||
|
@ -30,17 +30,17 @@ public interface BasicDAO<Entity> {
|
||||||
|
|
||||||
/* Deletes */
|
/* Deletes */
|
||||||
@Delete
|
@Delete
|
||||||
void delete(final Entity entity);
|
void delete(Entity entity);
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
int delete(final Collection<Entity> entities);
|
int delete(Collection<Entity> entities);
|
||||||
|
|
||||||
int deleteAll();
|
int deleteAll();
|
||||||
|
|
||||||
/* Updates */
|
/* Updates */
|
||||||
@Update
|
@Update
|
||||||
int update(final Entity entity);
|
int update(Entity entity);
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
void update(final Collection<Entity> entities);
|
void update(Collection<Entity> entities);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,47 +7,52 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class Converters {
|
public final class Converters {
|
||||||
|
private Converters() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a long value to a date
|
* Convert a long value to a date.
|
||||||
|
*
|
||||||
* @param value the long value
|
* @param value the long value
|
||||||
* @return the date
|
* @return the date
|
||||||
*/
|
*/
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static Date fromTimestamp(Long value) {
|
public static Date fromTimestamp(final Long value) {
|
||||||
return value == null ? null : new Date(value);
|
return value == null ? null : new Date(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a date to a long value
|
* Convert a date to a long value.
|
||||||
|
*
|
||||||
* @param date the date
|
* @param date the date
|
||||||
* @return the long value
|
* @return the long value
|
||||||
*/
|
*/
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static Long dateToTimestamp(Date date) {
|
public static Long dateToTimestamp(final Date date) {
|
||||||
return date == null ? null : date.getTime();
|
return date == null ? null : date.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static StreamType streamTypeOf(String value) {
|
public static StreamType streamTypeOf(final String value) {
|
||||||
return StreamType.valueOf(value);
|
return StreamType.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static String stringOf(StreamType streamType) {
|
public static String stringOf(final StreamType streamType) {
|
||||||
return streamType.name();
|
return streamType.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static Integer integerOf(FeedGroupIcon feedGroupIcon) {
|
public static Integer integerOf(final FeedGroupIcon feedGroupIcon) {
|
||||||
return feedGroupIcon.getId();
|
return feedGroupIcon.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static FeedGroupIcon feedGroupIconOf(Integer id) {
|
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
|
||||||
for (FeedGroupIcon icon : FeedGroupIcon.values()) {
|
for (FeedGroupIcon icon : FeedGroupIcon.values()) {
|
||||||
if (icon.getId() == id) return icon;
|
if (icon.getId() == id) {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("There's no feed group icon with the id \"" + id + "\"");
|
throw new IllegalArgumentException("There's no feed group icon with the id \"" + id + "\"");
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.schabi.newpipe.database;
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
public interface LocalItem {
|
public interface LocalItem {
|
||||||
|
LocalItemType getLocalItemType();
|
||||||
|
|
||||||
enum LocalItemType {
|
enum LocalItemType {
|
||||||
PLAYLIST_LOCAL_ITEM,
|
PLAYLIST_LOCAL_ITEM,
|
||||||
PLAYLIST_REMOTE_ITEM,
|
PLAYLIST_REMOTE_ITEM,
|
||||||
|
@ -8,6 +10,4 @@ public interface LocalItem {
|
||||||
PLAYLIST_STREAM_ITEM,
|
PLAYLIST_STREAM_ITEM,
|
||||||
STATISTIC_STREAM_ITEM,
|
STATISTIC_STREAM_ITEM,
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalItemType getLocalItemType();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,103 @@
|
||||||
package org.schabi.newpipe.database;
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
|
||||||
import androidx.room.migration.Migration;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.migration.Migration;
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
|
|
||||||
public class Migrations {
|
public final class Migrations {
|
||||||
public static final int DB_VER_1 = 1;
|
public static final int DB_VER_1 = 1;
|
||||||
public static final int DB_VER_2 = 2;
|
public static final int DB_VER_2 = 2;
|
||||||
public static final int DB_VER_3 = 3;
|
public static final int DB_VER_3 = 3;
|
||||||
|
|
||||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
|
||||||
private static final String TAG = Migrations.class.getName();
|
private static final String TAG = Migrations.class.getName();
|
||||||
|
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||||
|
|
||||||
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
|
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
|
||||||
@Override
|
@Override
|
||||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
public void migrate(@NonNull final SupportSQLiteDatabase database) {
|
||||||
if(DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Start migrating database");
|
Log.d(TAG, "Start migrating database");
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Unfortunately these queries must be hardcoded due to the possibility of
|
* Unfortunately these queries must be hardcoded due to the possibility of
|
||||||
* schema and names changing at a later date, thus invalidating the older migration
|
* schema and names changing at a later date, thus invalidating the older migration
|
||||||
* scripts if they are not hardcoded.
|
* scripts if they are not hardcoded.
|
||||||
* */
|
* */
|
||||||
|
|
||||||
// Not much we can do about this, since room doesn't create tables before migration.
|
// Not much we can do about this, since room doesn't create tables before migration.
|
||||||
// It's either this or blasting the entire database anew.
|
// It's either this or blasting the entire database anew.
|
||||||
database.execSQL("CREATE INDEX `index_search_history_search` ON `search_history` (`search`)");
|
database.execSQL("CREATE INDEX `index_search_history_search` "
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `streams` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, `stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, `thumbnail_url` TEXT)");
|
+ "ON `search_history` (`search`)");
|
||||||
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` ON `streams` (`service_id`, `url`)");
|
database.execSQL("CREATE TABLE IF NOT EXISTS `streams` "
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
|
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
|
||||||
database.execSQL("CREATE INDEX `index_stream_history_stream_id` ON `stream_history` (`stream_id`)");
|
+ "`service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, "
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
|
+ "`stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, "
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)");
|
+ "`thumbnail_url` TEXT)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` "
|
||||||
|
+ "ON `streams` (`service_id`, `url`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` "
|
||||||
|
+ "(`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, "
|
||||||
|
+ "`repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), "
|
||||||
|
+ "FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||||
|
database.execSQL("CREATE INDEX `index_stream_history_stream_id` "
|
||||||
|
+ "ON `stream_history` (`stream_id`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` "
|
||||||
|
+ "(`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, "
|
||||||
|
+ "PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) "
|
||||||
|
+ "REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` "
|
||||||
|
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
|
||||||
|
+ "`name` TEXT, `thumbnail_url` TEXT)");
|
||||||
database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
|
database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` "
|
||||||
database.execSQL("CREATE UNIQUE INDEX `index_playlist_stream_join_playlist_id_join_index` ON `playlist_stream_join` (`playlist_id`, `join_index`)");
|
+ "(`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, "
|
||||||
database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` ON `playlist_stream_join` (`stream_id`)");
|
+ "`join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), "
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)");
|
+ "FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) "
|
||||||
database.execSQL("CREATE INDEX `index_remote_playlists_name` ON `remote_playlists` (`name`)");
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
|
||||||
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` ON `remote_playlists` (`service_id`, `url`)");
|
+ "FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX "
|
||||||
|
+ "`index_playlist_stream_join_playlist_id_join_index` "
|
||||||
|
+ "ON `playlist_stream_join` (`playlist_id`, `join_index`)");
|
||||||
|
database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` "
|
||||||
|
+ "ON `playlist_stream_join` (`stream_id`)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` "
|
||||||
|
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
|
||||||
|
+ "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
|
||||||
|
+ "`thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)");
|
||||||
|
database.execSQL("CREATE INDEX `index_remote_playlists_name` "
|
||||||
|
+ "ON `remote_playlists` (`name`)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
|
||||||
|
+ "ON `remote_playlists` (`service_id`, `url`)");
|
||||||
|
|
||||||
// Populate streams table with existing entries in watch history
|
// Populate streams table with existing entries in watch history
|
||||||
// Latest data first, thus ignoring older entries with the same indices
|
// Latest data first, thus ignoring older entries with the same indices
|
||||||
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, " +
|
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, "
|
||||||
"stream_type, duration, uploader, thumbnail_url) " +
|
+ "stream_type, duration, uploader, thumbnail_url) "
|
||||||
|
|
||||||
"SELECT service_id, url, title, 'VIDEO_STREAM', duration, " +
|
+ "SELECT service_id, url, title, 'VIDEO_STREAM', duration, "
|
||||||
"uploader, thumbnail_url " +
|
+ "uploader, thumbnail_url "
|
||||||
|
|
||||||
"FROM watch_history " +
|
+ "FROM watch_history "
|
||||||
"ORDER BY creation_date DESC");
|
+ "ORDER BY creation_date DESC");
|
||||||
|
|
||||||
// Once the streams have PKs, join them with the normalized history table
|
// Once the streams have PKs, join them with the normalized history table
|
||||||
// and populate it with the remaining data from watch history
|
// and populate it with the remaining data from watch history
|
||||||
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" +
|
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)"
|
||||||
"SELECT uid, creation_date, 1 " +
|
+ "SELECT uid, creation_date, 1 "
|
||||||
"FROM watch_history INNER JOIN streams " +
|
+ "FROM watch_history INNER JOIN streams "
|
||||||
"ON watch_history.service_id == streams.service_id " +
|
+ "ON watch_history.service_id == streams.service_id "
|
||||||
"AND watch_history.url == streams.url " +
|
+ "AND watch_history.url == streams.url "
|
||||||
"ORDER BY creation_date DESC");
|
+ "ORDER BY creation_date DESC");
|
||||||
|
|
||||||
database.execSQL("DROP TABLE IF EXISTS watch_history");
|
database.execSQL("DROP TABLE IF EXISTS watch_history");
|
||||||
|
|
||||||
if(DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Stop migrating database");
|
Log.d(TAG, "Stop migrating database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,37 +105,60 @@ public class Migrations {
|
||||||
|
|
||||||
public static final Migration MIGRATION_2_3 = new Migration(DB_VER_2, DB_VER_3) {
|
public static final Migration MIGRATION_2_3 = new Migration(DB_VER_2, DB_VER_3) {
|
||||||
@Override
|
@Override
|
||||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
public void migrate(@NonNull final SupportSQLiteDatabase database) {
|
||||||
// Add NOT NULLs and new fields
|
// Add NOT NULLs and new fields
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS streams_new " +
|
database.execSQL("CREATE TABLE IF NOT EXISTS streams_new "
|
||||||
"(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, service_id INTEGER NOT NULL, url TEXT NOT NULL, title TEXT NOT NULL, stream_type TEXT NOT NULL," +
|
+ "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
|
||||||
" duration INTEGER NOT NULL, uploader TEXT NOT NULL, thumbnail_url TEXT, view_count INTEGER, textual_upload_date TEXT, upload_date INTEGER," +
|
+ "service_id INTEGER NOT NULL, url TEXT NOT NULL, title TEXT NOT NULL, "
|
||||||
" is_upload_date_approximation INTEGER)");
|
+ "stream_type TEXT NOT NULL, duration INTEGER NOT NULL, "
|
||||||
|
+ "uploader TEXT NOT NULL, thumbnail_url TEXT, view_count INTEGER, "
|
||||||
|
+ "textual_upload_date TEXT, upload_date INTEGER, "
|
||||||
|
+ "is_upload_date_approximation INTEGER)");
|
||||||
|
|
||||||
database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type," +
|
database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type, "
|
||||||
"duration, uploader, thumbnail_url, view_count," +
|
+ "duration, uploader, thumbnail_url, view_count, textual_upload_date, "
|
||||||
"textual_upload_date, upload_date, is_upload_date_approximation) " +
|
+ "upload_date, is_upload_date_approximation) "
|
||||||
|
|
||||||
"SELECT uid, service_id, url, ifnull(title, ''), ifnull(stream_type, 'VIDEO_STREAM')," +
|
+ "SELECT uid, service_id, url, ifnull(title, ''), "
|
||||||
"ifnull(duration, 0), ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL," +
|
+ "ifnull(stream_type, 'VIDEO_STREAM'), ifnull(duration, 0), "
|
||||||
"NULL, NULL, NULL " +
|
+ "ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL, NULL, NULL, NULL "
|
||||||
|
|
||||||
"FROM streams " +
|
+ "FROM streams WHERE url IS NOT NULL");
|
||||||
"WHERE url IS NOT NULL");
|
|
||||||
|
|
||||||
database.execSQL("DROP TABLE streams");
|
database.execSQL("DROP TABLE streams");
|
||||||
database.execSQL("ALTER TABLE streams_new RENAME TO streams");
|
database.execSQL("ALTER TABLE streams_new RENAME TO streams");
|
||||||
database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url ON streams (service_id, url)");
|
database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url "
|
||||||
|
+ "ON streams (service_id, url)");
|
||||||
|
|
||||||
// Tables for feed feature
|
// Tables for feed feature
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed (stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(stream_id, subscription_id), FOREIGN KEY(stream_id) REFERENCES streams(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
database.execSQL("CREATE TABLE IF NOT EXISTS feed "
|
||||||
|
+ "(stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, "
|
||||||
|
+ "PRIMARY KEY(stream_id, subscription_id), "
|
||||||
|
+ "FOREIGN KEY(stream_id) REFERENCES streams(uid) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
|
||||||
|
+ "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||||
database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)");
|
database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)");
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)");
|
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group "
|
||||||
|
+ "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, "
|
||||||
|
+ "icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)");
|
||||||
database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)");
|
database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)");
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join (group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(group_id, subscription_id), FOREIGN KEY(group_id) REFERENCES feed_group(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join "
|
||||||
database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id ON feed_group_subscription_join (subscription_id)");
|
+ "(group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, "
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated (subscription_id INTEGER NOT NULL, last_updated INTEGER, PRIMARY KEY(subscription_id), FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
+ "PRIMARY KEY(group_id, subscription_id), "
|
||||||
|
+ "FOREIGN KEY(group_id) REFERENCES feed_group(uid) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
|
||||||
|
+ "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||||
|
database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id "
|
||||||
|
+ "ON feed_group_subscription_join (subscription_id)");
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated "
|
||||||
|
+ "(subscription_id INTEGER NOT NULL, last_updated INTEGER, "
|
||||||
|
+ "PRIMARY KEY(subscription_id), "
|
||||||
|
+ "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) "
|
||||||
|
+ "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private Migrations() { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.schabi.newpipe.database.history.dao;
|
package org.schabi.newpipe.database.history.dao;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
|
|
||||||
|
@ -18,11 +18,10 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||||
|
|
||||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME +
|
@Query("SELECT * FROM " + TABLE_NAME
|
||||||
" WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
+ " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
|
||||||
@Nullable
|
@Nullable
|
||||||
SearchHistoryEntry getLatestEntry();
|
SearchHistoryEntry getLatestEntry();
|
||||||
|
|
||||||
|
@ -37,13 +36,16 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||||
@Override
|
@Override
|
||||||
Flowable<List<SearchHistoryEntry>> getAll();
|
Flowable<List<SearchHistoryEntry>> getAll();
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit")
|
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE
|
||||||
|
+ " LIMIT :limit")
|
||||||
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
|
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
@Query("SELECT * FROM " + TABLE_NAME
|
||||||
|
+ " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
||||||
@Override
|
@Override
|
||||||
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
|
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
|
||||||
|
|
||||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit")
|
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
|
||||||
|
+ " GROUP BY " + SEARCH + " LIMIT :limit")
|
||||||
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
|
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,31 @@
|
||||||
package org.schabi.newpipe.database.history.dao;
|
package org.schabi.newpipe.database.history.dao;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.Flowable;
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT;
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT;
|
||||||
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
|
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
|
||||||
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
|
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
|
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
|
||||||
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE +
|
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE
|
||||||
" WHERE " + STREAM_ACCESS_DATE + " = " +
|
+ " WHERE " + STREAM_ACCESS_DATE + " = "
|
||||||
"(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
|
+ "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public abstract StreamHistoryEntity getLatestEntry();
|
public abstract StreamHistoryEntity getLatestEntry();
|
||||||
|
@ -40,33 +39,33 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
|
||||||
public abstract int deleteAll();
|
public abstract int deleteAll();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flowable<List<StreamHistoryEntity>> listByService(int serviceId) {
|
public Flowable<List<StreamHistoryEntity>> listByService(final int serviceId) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("SELECT * FROM " + STREAM_TABLE +
|
@Query("SELECT * FROM " + STREAM_TABLE
|
||||||
" INNER JOIN " + STREAM_HISTORY_TABLE +
|
+ " INNER JOIN " + STREAM_HISTORY_TABLE
|
||||||
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
|
||||||
" ORDER BY " + STREAM_ACCESS_DATE + " DESC")
|
+ " ORDER BY " + STREAM_ACCESS_DATE + " DESC")
|
||||||
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
|
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
|
||||||
|
|
||||||
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID +
|
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID
|
||||||
" = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
|
+ " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
|
||||||
@Nullable
|
@Nullable
|
||||||
public abstract StreamHistoryEntity getLatestEntry(final long streamId);
|
public abstract StreamHistoryEntity getLatestEntry(long streamId);
|
||||||
|
|
||||||
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||||
public abstract int deleteStreamHistory(final long streamId);
|
public abstract int deleteStreamHistory(long streamId);
|
||||||
|
|
||||||
@Query("SELECT * FROM " + STREAM_TABLE +
|
@Query("SELECT * FROM " + STREAM_TABLE
|
||||||
|
|
||||||
// Select the latest entry and watch count for each stream id on history table
|
// Select the latest entry and watch count for each stream id on history table
|
||||||
" INNER JOIN " +
|
+ " INNER JOIN "
|
||||||
"(SELECT " + JOIN_STREAM_ID + ", " +
|
+ "(SELECT " + JOIN_STREAM_ID + ", "
|
||||||
" MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " +
|
+ " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", "
|
||||||
" SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT +
|
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
|
||||||
" FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" +
|
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
|
||||||
|
|
||||||
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
|
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
|
||||||
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
|
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARC
|
||||||
@Entity(tableName = SearchHistoryEntry.TABLE_NAME,
|
@Entity(tableName = SearchHistoryEntry.TABLE_NAME,
|
||||||
indices = {@Index(value = SEARCH)})
|
indices = {@Index(value = SEARCH)})
|
||||||
public class SearchHistoryEntry {
|
public class SearchHistoryEntry {
|
||||||
|
|
||||||
public static final String ID = "id";
|
public static final String ID = "id";
|
||||||
public static final String TABLE_NAME = "search_history";
|
public static final String TABLE_NAME = "search_history";
|
||||||
public static final String SERVICE_ID = "service_id";
|
public static final String SERVICE_ID = "service_id";
|
||||||
|
@ -33,7 +32,7 @@ public class SearchHistoryEntry {
|
||||||
@ColumnInfo(name = SEARCH)
|
@ColumnInfo(name = SEARCH)
|
||||||
private String search;
|
private String search;
|
||||||
|
|
||||||
public SearchHistoryEntry(Date creationDate, int serviceId, String search) {
|
public SearchHistoryEntry(final Date creationDate, final int serviceId, final String search) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
this.search = search;
|
this.search = search;
|
||||||
|
@ -43,7 +42,7 @@ public class SearchHistoryEntry {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(long id) {
|
public void setId(final long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ public class SearchHistoryEntry {
|
||||||
return creationDate;
|
return creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreationDate(Date creationDate) {
|
public void setCreationDate(final Date creationDate) {
|
||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ public class SearchHistoryEntry {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServiceId(int serviceId) {
|
public void setServiceId(final int serviceId) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,13 +66,13 @@ public class SearchHistoryEntry {
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSearch(String search) {
|
public void setSearch(final String search) {
|
||||||
this.search = search;
|
this.search = search;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public boolean hasEqualValues(SearchHistoryEntry otherEntry) {
|
public boolean hasEqualValues(final SearchHistoryEntry otherEntry) {
|
||||||
return getServiceId() == otherEntry.getServiceId() &&
|
return getServiceId() == otherEntry.getServiceId()
|
||||||
getSearch().equals(otherEntry.getSearch());
|
&& getSearch().equals(otherEntry.getSearch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
package org.schabi.newpipe.database.history.model;
|
package org.schabi.newpipe.database.history.model;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.room.ColumnInfo;
|
import androidx.room.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.ForeignKey;
|
import androidx.room.ForeignKey;
|
||||||
import androidx.room.Ignore;
|
import androidx.room.Ignore;
|
||||||
import androidx.room.Index;
|
import androidx.room.Index;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static androidx.room.ForeignKey.CASCADE;
|
import static androidx.room.ForeignKey.CASCADE;
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
|
|
||||||
@Entity(tableName = STREAM_HISTORY_TABLE,
|
@Entity(tableName = STREAM_HISTORY_TABLE,
|
||||||
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
|
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
|
||||||
|
@ -27,10 +27,10 @@ import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STRE
|
||||||
onDelete = CASCADE, onUpdate = CASCADE)
|
onDelete = CASCADE, onUpdate = CASCADE)
|
||||||
})
|
})
|
||||||
public class StreamHistoryEntity {
|
public class StreamHistoryEntity {
|
||||||
final public static String STREAM_HISTORY_TABLE = "stream_history";
|
public static final String STREAM_HISTORY_TABLE = "stream_history";
|
||||||
final public static String JOIN_STREAM_ID = "stream_id";
|
public static final String JOIN_STREAM_ID = "stream_id";
|
||||||
final public static String STREAM_ACCESS_DATE = "access_date";
|
public static final String STREAM_ACCESS_DATE = "access_date";
|
||||||
final public static String STREAM_REPEAT_COUNT = "repeat_count";
|
public static final String STREAM_REPEAT_COUNT = "repeat_count";
|
||||||
|
|
||||||
@ColumnInfo(name = JOIN_STREAM_ID)
|
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||||
private long streamUid;
|
private long streamUid;
|
||||||
|
@ -42,14 +42,15 @@ public class StreamHistoryEntity {
|
||||||
@ColumnInfo(name = STREAM_REPEAT_COUNT)
|
@ColumnInfo(name = STREAM_REPEAT_COUNT)
|
||||||
private long repeatCount;
|
private long repeatCount;
|
||||||
|
|
||||||
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate, long repeatCount) {
|
public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate,
|
||||||
|
final long repeatCount) {
|
||||||
this.streamUid = streamUid;
|
this.streamUid = streamUid;
|
||||||
this.accessDate = accessDate;
|
this.accessDate = accessDate;
|
||||||
this.repeatCount = repeatCount;
|
this.repeatCount = repeatCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate) {
|
public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate) {
|
||||||
this(streamUid, accessDate, 1);
|
this(streamUid, accessDate, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ public class StreamHistoryEntity {
|
||||||
return streamUid;
|
return streamUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStreamUid(long streamUid) {
|
public void setStreamUid(final long streamUid) {
|
||||||
this.streamUid = streamUid;
|
this.streamUid = streamUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ public class StreamHistoryEntity {
|
||||||
return accessDate;
|
return accessDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccessDate(@NonNull Date accessDate) {
|
public void setAccessDate(@NonNull final Date accessDate) {
|
||||||
this.accessDate = accessDate;
|
this.accessDate = accessDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ public class StreamHistoryEntity {
|
||||||
return repeatCount;
|
return repeatCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRepeatCount(long repeatCount) {
|
public void setRepeatCount(final long repeatCount) {
|
||||||
this.repeatCount = repeatCount;
|
this.repeatCount = repeatCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,19 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
|
||||||
|
|
||||||
public class PlaylistMetadataEntry implements PlaylistLocalItem {
|
public class PlaylistMetadataEntry implements PlaylistLocalItem {
|
||||||
final public static String PLAYLIST_STREAM_COUNT = "streamCount";
|
public static final String PLAYLIST_STREAM_COUNT = "streamCount";
|
||||||
|
|
||||||
@ColumnInfo(name = PLAYLIST_ID)
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
final public long uid;
|
public final long uid;
|
||||||
@ColumnInfo(name = PLAYLIST_NAME)
|
@ColumnInfo(name = PLAYLIST_NAME)
|
||||||
final public String name;
|
public final String name;
|
||||||
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
||||||
final public String thumbnailUrl;
|
public final String thumbnailUrl;
|
||||||
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
|
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
|
||||||
final public long streamCount;
|
public final long streamCount;
|
||||||
|
|
||||||
public PlaylistMetadataEntry(long uid, String name, String thumbnailUrl, long streamCount) {
|
public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl,
|
||||||
|
final long streamCount) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
|
|
@ -24,13 +24,13 @@ public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> {
|
||||||
public abstract int deleteAll();
|
public abstract int deleteAll();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flowable<List<PlaylistEntity>> listByService(int serviceId) {
|
public Flowable<List<PlaylistEntity>> listByService(final int serviceId) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
public abstract Flowable<List<PlaylistEntity>> getPlaylist(final long playlistId);
|
public abstract Flowable<List<PlaylistEntity>> getPlaylist(long playlistId);
|
||||||
|
|
||||||
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
public abstract int deletePlaylist(final long playlistId);
|
public abstract int deletePlaylist(long playlistId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,22 +27,21 @@ public abstract class PlaylistRemoteDAO implements BasicDAO<PlaylistRemoteEntity
|
||||||
public abstract int deleteAll();
|
public abstract int deleteAll();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE +
|
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
|
||||||
" WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
+ " WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
||||||
public abstract Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
|
public abstract Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
|
||||||
|
|
||||||
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " +
|
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
|
||||||
REMOTE_PLAYLIST_URL + " = :url AND " +
|
+ REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
||||||
REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
|
||||||
public abstract Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
|
public abstract Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
|
||||||
|
|
||||||
@Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE +
|
@Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE
|
||||||
" WHERE " +
|
+ " WHERE " + REMOTE_PLAYLIST_URL + " = :url "
|
||||||
REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
+ "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
|
||||||
abstract Long getPlaylistIdInternal(long serviceId, String url);
|
abstract Long getPlaylistIdInternal(long serviceId, String url);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
public long upsert(PlaylistRemoteEntity playlist) {
|
public long upsert(final PlaylistRemoteEntity playlist) {
|
||||||
final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl());
|
final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl());
|
||||||
|
|
||||||
if (playlistId == null) {
|
if (playlistId == null) {
|
||||||
|
@ -54,7 +53,7 @@ public abstract class PlaylistRemoteDAO implements BasicDAO<PlaylistRemoteEntity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE +
|
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE
|
||||||
" WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId")
|
+ " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId")
|
||||||
public abstract int deletePlaylist(final long playlistId);
|
public abstract int deletePlaylist(long playlistId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,16 @@ import java.util.List;
|
||||||
import io.reactivex.Flowable;
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
|
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.*;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.*;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.*;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
|
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
|
||||||
|
@ -29,40 +36,39 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
|
||||||
public abstract int deleteAll();
|
public abstract int deleteAll();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flowable<List<PlaylistStreamEntity>> listByService(int serviceId) {
|
public Flowable<List<PlaylistStreamEntity>> listByService(final int serviceId) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE
|
||||||
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
public abstract void deleteBatch(final long playlistId);
|
public abstract void deleteBatch(long playlistId);
|
||||||
|
|
||||||
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" +
|
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)"
|
||||||
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
|
||||||
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
public abstract Flowable<Integer> getMaximumIndexOf(final long playlistId);
|
public abstract Flowable<Integer> getMaximumIndexOf(long playlistId);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " +
|
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN "
|
||||||
// get ids of streams of the given playlist
|
// get ids of streams of the given playlist
|
||||||
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX +
|
+ "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX
|
||||||
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
|
||||||
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" +
|
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)"
|
||||||
|
|
||||||
// then merge with the stream metadata
|
// then merge with the stream metadata
|
||||||
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
|
||||||
" ORDER BY " + JOIN_INDEX + " ASC")
|
+ " ORDER BY " + JOIN_INDEX + " ASC")
|
||||||
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
|
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " +
|
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
|
||||||
PLAYLIST_THUMBNAIL_URL + ", " +
|
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
|
||||||
"COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT +
|
|
||||||
|
|
||||||
" FROM " + PLAYLIST_TABLE +
|
+ " FROM " + PLAYLIST_TABLE
|
||||||
" LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
|
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
|
||||||
" ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID +
|
+ " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
|
||||||
" GROUP BY " + JOIN_PLAYLIST_ID +
|
+ " GROUP BY " + JOIN_PLAYLIST_ID
|
||||||
" ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
|
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
|
||||||
public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST
|
||||||
@Entity(tableName = PLAYLIST_TABLE,
|
@Entity(tableName = PLAYLIST_TABLE,
|
||||||
indices = {@Index(value = {PLAYLIST_NAME})})
|
indices = {@Index(value = {PLAYLIST_NAME})})
|
||||||
public class PlaylistEntity {
|
public class PlaylistEntity {
|
||||||
final public static String PLAYLIST_TABLE = "playlists";
|
public static final String PLAYLIST_TABLE = "playlists";
|
||||||
final public static String PLAYLIST_ID = "uid";
|
public static final String PLAYLIST_ID = "uid";
|
||||||
final public static String PLAYLIST_NAME = "name";
|
public static final String PLAYLIST_NAME = "name";
|
||||||
final public static String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
@ColumnInfo(name = PLAYLIST_ID)
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
|
@ -26,7 +26,7 @@ public class PlaylistEntity {
|
||||||
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
||||||
private String thumbnailUrl;
|
private String thumbnailUrl;
|
||||||
|
|
||||||
public PlaylistEntity(String name, String thumbnailUrl) {
|
public PlaylistEntity(final String name, final String thumbnailUrl) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public class PlaylistEntity {
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(long uid) {
|
public void setUid(final long uid) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class PlaylistEntity {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(final String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class PlaylistEntity {
|
||||||
return thumbnailUrl;
|
return thumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThumbnailUrl(String thumbnailUrl) {
|
public void setThumbnailUrl(final String thumbnailUrl) {
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,14 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE
|
||||||
@Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true)
|
@Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true)
|
||||||
})
|
})
|
||||||
public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
final public static String REMOTE_PLAYLIST_TABLE = "remote_playlists";
|
public static final String REMOTE_PLAYLIST_TABLE = "remote_playlists";
|
||||||
final public static String REMOTE_PLAYLIST_ID = "uid";
|
public static final String REMOTE_PLAYLIST_ID = "uid";
|
||||||
final public static String REMOTE_PLAYLIST_SERVICE_ID = "service_id";
|
public static final String REMOTE_PLAYLIST_SERVICE_ID = "service_id";
|
||||||
final public static String REMOTE_PLAYLIST_NAME = "name";
|
public static final String REMOTE_PLAYLIST_NAME = "name";
|
||||||
final public static String REMOTE_PLAYLIST_URL = "url";
|
public static final String REMOTE_PLAYLIST_URL = "url";
|
||||||
final public static String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
public static final String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
||||||
final public static String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
|
public static final String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
|
||||||
final public static String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
|
public static final String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
@ColumnInfo(name = REMOTE_PLAYLIST_ID)
|
@ColumnInfo(name = REMOTE_PLAYLIST_ID)
|
||||||
|
@ -55,8 +55,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
@ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
|
@ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
|
||||||
private Long streamCount;
|
private Long streamCount;
|
||||||
|
|
||||||
public PlaylistRemoteEntity(int serviceId, String name, String url, String thumbnailUrl,
|
public PlaylistRemoteEntity(final int serviceId, final String name, final String url,
|
||||||
String uploader, Long streamCount) {
|
final String thumbnailUrl, final String uploader,
|
||||||
|
final Long streamCount) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
@ -68,7 +69,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
@Ignore
|
@Ignore
|
||||||
public PlaylistRemoteEntity(final PlaylistInfo info) {
|
public PlaylistRemoteEntity(final PlaylistInfo info) {
|
||||||
this(info.getServiceId(), info.getName(), info.getUrl(),
|
this(info.getServiceId(), info.getName(), info.getUrl(),
|
||||||
info.getThumbnailUrl() == null ? info.getUploaderAvatarUrl() : info.getThumbnailUrl(),
|
info.getThumbnailUrl() == null
|
||||||
|
? info.getUploaderAvatarUrl() : info.getThumbnailUrl(),
|
||||||
info.getUploaderName(), info.getStreamCount());
|
info.getUploaderName(), info.getStreamCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +92,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(long uid) {
|
public void setUid(final long uid) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServiceId(int serviceId) {
|
public void setServiceId(final int serviceId) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +108,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(final String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +116,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return thumbnailUrl;
|
return thumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThumbnailUrl(String thumbnailUrl) {
|
public void setThumbnailUrl(final String thumbnailUrl) {
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +124,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrl(String url) {
|
public void setUrl(final String url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +132,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return uploader;
|
return uploader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUploader(String uploader) {
|
public void setUploader(final String uploader) {
|
||||||
this.uploader = uploader;
|
this.uploader = uploader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +140,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
|
||||||
return streamCount;
|
return streamCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStreamCount(Long streamCount) {
|
public void setStreamCount(final Long streamCount) {
|
||||||
this.streamCount = streamCount;
|
this.streamCount = streamCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,10 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PL
|
||||||
onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
|
onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
|
||||||
})
|
})
|
||||||
public class PlaylistStreamEntity {
|
public class PlaylistStreamEntity {
|
||||||
|
public static final String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
|
||||||
final public static String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
|
public static final String JOIN_PLAYLIST_ID = "playlist_id";
|
||||||
final public static String JOIN_PLAYLIST_ID = "playlist_id";
|
public static final String JOIN_STREAM_ID = "stream_id";
|
||||||
final public static String JOIN_STREAM_ID = "stream_id";
|
public static final String JOIN_INDEX = "join_index";
|
||||||
final public static String JOIN_INDEX = "join_index";
|
|
||||||
|
|
||||||
@ColumnInfo(name = JOIN_PLAYLIST_ID)
|
@ColumnInfo(name = JOIN_PLAYLIST_ID)
|
||||||
private long playlistUid;
|
private long playlistUid;
|
||||||
|
@ -55,23 +54,23 @@ public class PlaylistStreamEntity {
|
||||||
return playlistUid;
|
return playlistUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPlaylistUid(final long playlistUid) {
|
||||||
|
this.playlistUid = playlistUid;
|
||||||
|
}
|
||||||
|
|
||||||
public long getStreamUid() {
|
public long getStreamUid() {
|
||||||
return streamUid;
|
return streamUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStreamUid(final long streamUid) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
public int getIndex() {
|
public int getIndex() {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlaylistUid(long playlistUid) {
|
public void setIndex(final int index) {
|
||||||
this.playlistUid = playlistUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStreamUid(long streamUid) {
|
|
||||||
this.streamUid = streamUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIndex(int index) {
|
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,21 +27,21 @@ public abstract class StreamStateDAO implements BasicDAO<StreamStateEntity> {
|
||||||
public abstract int deleteAll();
|
public abstract int deleteAll();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flowable<List<StreamStateEntity>> listByService(int serviceId) {
|
public Flowable<List<StreamStateEntity>> listByService(final int serviceId) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
@Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||||
public abstract Flowable<List<StreamStateEntity>> getState(final long streamId);
|
public abstract Flowable<List<StreamStateEntity>> getState(long streamId);
|
||||||
|
|
||||||
@Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
@Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||||
public abstract int deleteState(final long streamId);
|
public abstract int deleteState(long streamId);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
abstract void silentInsertInternal(final StreamStateEntity streamState);
|
abstract void silentInsertInternal(StreamStateEntity streamState);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
public long upsert(StreamStateEntity stream) {
|
public long upsert(final StreamStateEntity stream) {
|
||||||
silentInsertInternal(stream);
|
silentInsertInternal(stream);
|
||||||
return update(stream);
|
return update(stream);
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,8 @@ data class StreamEntity(
|
||||||
if (viewCount != null) item.viewCount = viewCount as Long
|
if (viewCount != null) item.viewCount = viewCount as Long
|
||||||
item.textualUploadDate = textualUploadDate
|
item.textualUploadDate = textualUploadDate
|
||||||
item.uploadDate = uploadDate?.let {
|
item.uploadDate = uploadDate?.let {
|
||||||
DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation ?: false)
|
DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation
|
||||||
|
?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package org.schabi.newpipe.database.stream.model;
|
package org.schabi.newpipe.database.stream.model;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.ColumnInfo;
|
import androidx.room.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.ForeignKey;
|
import androidx.room.ForeignKey;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -21,14 +20,17 @@ import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_
|
||||||
onDelete = CASCADE, onUpdate = CASCADE)
|
onDelete = CASCADE, onUpdate = CASCADE)
|
||||||
})
|
})
|
||||||
public class StreamStateEntity {
|
public class StreamStateEntity {
|
||||||
final public static String STREAM_STATE_TABLE = "stream_state";
|
public static final String STREAM_STATE_TABLE = "stream_state";
|
||||||
final public static String JOIN_STREAM_ID = "stream_id";
|
public static final String JOIN_STREAM_ID = "stream_id";
|
||||||
final public static String STREAM_PROGRESS_TIME = "progress_time";
|
public static final String STREAM_PROGRESS_TIME = "progress_time";
|
||||||
|
|
||||||
|
/**
|
||||||
/** Playback state will not be saved, if playback time less than this threshold */
|
* Playback state will not be saved, if playback time is less than this threshold.
|
||||||
|
*/
|
||||||
private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5;
|
private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5;
|
||||||
/** Playback state will not be saved, if time left less than this threshold */
|
/**
|
||||||
|
* Playback state will not be saved, if time left is less than this threshold.
|
||||||
|
*/
|
||||||
private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10;
|
private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10;
|
||||||
|
|
||||||
@ColumnInfo(name = JOIN_STREAM_ID)
|
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||||
|
@ -37,7 +39,7 @@ public class StreamStateEntity {
|
||||||
@ColumnInfo(name = STREAM_PROGRESS_TIME)
|
@ColumnInfo(name = STREAM_PROGRESS_TIME)
|
||||||
private long progressTime;
|
private long progressTime;
|
||||||
|
|
||||||
public StreamStateEntity(long streamUid, long progressTime) {
|
public StreamStateEntity(final long streamUid, final long progressTime) {
|
||||||
this.streamUid = streamUid;
|
this.streamUid = streamUid;
|
||||||
this.progressTime = progressTime;
|
this.progressTime = progressTime;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +48,7 @@ public class StreamStateEntity {
|
||||||
return streamUid;
|
return streamUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStreamUid(long streamUid) {
|
public void setStreamUid(final long streamUid) {
|
||||||
this.streamUid = streamUid;
|
this.streamUid = streamUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,21 +56,23 @@ public class StreamStateEntity {
|
||||||
return progressTime;
|
return progressTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProgressTime(long progressTime) {
|
public void setProgressTime(final long progressTime) {
|
||||||
this.progressTime = progressTime;
|
this.progressTime = progressTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValid(int durationInSeconds) {
|
public boolean isValid(final int durationInSeconds) {
|
||||||
final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime);
|
final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime);
|
||||||
return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS
|
return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS
|
||||||
&& seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS;
|
&& seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable final Object obj) {
|
||||||
if (obj instanceof StreamStateEntity) {
|
if (obj instanceof StreamStateEntity) {
|
||||||
return ((StreamStateEntity) obj).streamUid == streamUid
|
return ((StreamStateEntity) obj).streamUid == streamUid
|
||||||
&& ((StreamStateEntity) obj).progressTime == progressTime;
|
&& ((StreamStateEntity) obj).progressTime == progressTime;
|
||||||
} else return false;
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||||
entity.uid = uidFromInsert
|
entity.uid = uidFromInsert
|
||||||
} else {
|
} else {
|
||||||
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
||||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||||
entity.uid = subscriptionIdFromDb
|
entity.uid = subscriptionIdFromDb
|
||||||
|
|
||||||
update(entity)
|
update(entity)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.schabi.newpipe.database.subscription;
|
package org.schabi.newpipe.database.subscription;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.room.ColumnInfo;
|
import androidx.room.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.Ignore;
|
import androidx.room.Ignore;
|
||||||
import androidx.room.Index;
|
import androidx.room.Index;
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
|
@ -18,15 +18,14 @@ import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCR
|
||||||
@Entity(tableName = SUBSCRIPTION_TABLE,
|
@Entity(tableName = SUBSCRIPTION_TABLE,
|
||||||
indices = {@Index(value = {SUBSCRIPTION_SERVICE_ID, SUBSCRIPTION_URL}, unique = true)})
|
indices = {@Index(value = {SUBSCRIPTION_SERVICE_ID, SUBSCRIPTION_URL}, unique = true)})
|
||||||
public class SubscriptionEntity {
|
public class SubscriptionEntity {
|
||||||
|
public static final String SUBSCRIPTION_UID = "uid";
|
||||||
public static final String SUBSCRIPTION_UID = "uid";
|
public static final String SUBSCRIPTION_TABLE = "subscriptions";
|
||||||
public static final String SUBSCRIPTION_TABLE = "subscriptions";
|
public static final String SUBSCRIPTION_SERVICE_ID = "service_id";
|
||||||
public static final String SUBSCRIPTION_SERVICE_ID = "service_id";
|
public static final String SUBSCRIPTION_URL = "url";
|
||||||
public static final String SUBSCRIPTION_URL = "url";
|
public static final String SUBSCRIPTION_NAME = "name";
|
||||||
public static final String SUBSCRIPTION_NAME = "name";
|
public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url";
|
||||||
public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url";
|
public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count";
|
||||||
public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count";
|
public static final String SUBSCRIPTION_DESCRIPTION = "description";
|
||||||
public static final String SUBSCRIPTION_DESCRIPTION = "description";
|
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
private long uid = 0;
|
private long uid = 0;
|
||||||
|
@ -49,11 +48,21 @@ public class SubscriptionEntity {
|
||||||
@ColumnInfo(name = SUBSCRIPTION_DESCRIPTION)
|
@ColumnInfo(name = SUBSCRIPTION_DESCRIPTION)
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
|
||||||
|
SubscriptionEntity result = new SubscriptionEntity();
|
||||||
|
result.setServiceId(info.getServiceId());
|
||||||
|
result.setUrl(info.getUrl());
|
||||||
|
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
|
||||||
|
info.getSubscriberCount());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public long getUid() {
|
public long getUid() {
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(long uid) {
|
public void setUid(final long uid) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +70,7 @@ public class SubscriptionEntity {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServiceId(int serviceId) {
|
public void setServiceId(final int serviceId) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +78,7 @@ public class SubscriptionEntity {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrl(String url) {
|
public void setUrl(final String url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +86,7 @@ public class SubscriptionEntity {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(final String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +94,7 @@ public class SubscriptionEntity {
|
||||||
return avatarUrl;
|
return avatarUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatarUrl(String avatarUrl) {
|
public void setAvatarUrl(final String avatarUrl) {
|
||||||
this.avatarUrl = avatarUrl;
|
this.avatarUrl = avatarUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +102,7 @@ public class SubscriptionEntity {
|
||||||
return subscriberCount;
|
return subscriberCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubscriberCount(Long subscriberCount) {
|
public void setSubscriberCount(final Long subscriberCount) {
|
||||||
this.subscriberCount = subscriberCount;
|
this.subscriberCount = subscriberCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,19 +110,16 @@ public class SubscriptionEntity {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDescription(String description) {
|
public void setDescription(final String description) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public void setData(final String name,
|
public void setData(final String n, final String au, final String d, final Long sc) {
|
||||||
final String avatarUrl,
|
this.setName(n);
|
||||||
final String description,
|
this.setAvatarUrl(au);
|
||||||
final Long subscriberCount) {
|
this.setDescription(d);
|
||||||
this.setName(name);
|
this.setSubscriberCount(sc);
|
||||||
this.setAvatarUrl(avatarUrl);
|
|
||||||
this.setDescription(description);
|
|
||||||
this.setSubscriberCount(subscriberCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
|
@ -124,13 +130,4 @@ public class SubscriptionEntity {
|
||||||
item.setDescription(getDescription());
|
item.setDescription(getDescription());
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
|
||||||
public static SubscriptionEntity from(@NonNull ChannelInfo info) {
|
|
||||||
SubscriptionEntity result = new SubscriptionEntity();
|
|
||||||
result.setServiceId(info.getServiceId());
|
|
||||||
result.setUrl(info.getUrl());
|
|
||||||
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,16 @@ package org.schabi.newpipe.download;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
|
@ -25,7 +25,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||||
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
|
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
// Service
|
// Service
|
||||||
Intent i = new Intent();
|
Intent i = new Intent();
|
||||||
i.setClass(this, DownloadManagerService.class);
|
i.setClass(this, DownloadManagerService.class);
|
||||||
|
@ -46,7 +46,8 @@ public class DownloadActivity extends AppCompatActivity {
|
||||||
actionBar.setDisplayShowTitleEnabled(true);
|
actionBar.setDisplayShowTitleEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
getWindow().getDecorView().getViewTreeObserver()
|
||||||
|
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onGlobalLayout() {
|
public void onGlobalLayout() {
|
||||||
updateFragments();
|
updateFragments();
|
||||||
|
@ -65,7 +66,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
onBackPressed();
|
onBackPressed();
|
||||||
|
|
|
@ -81,11 +81,12 @@ import us.shandian.giga.service.MissionState;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
|
public class DownloadDialog extends DialogFragment
|
||||||
|
implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
|
||||||
private static final String TAG = "DialogFragment";
|
private static final String TAG = "DialogFragment";
|
||||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
|
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
|
||||||
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
@State
|
@State
|
||||||
protected StreamInfo currentInfo;
|
protected StreamInfo currentInfo;
|
||||||
@State
|
@State
|
||||||
|
@ -100,30 +101,32 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
protected int selectedAudioIndex = 0;
|
protected int selectedAudioIndex = 0;
|
||||||
@State
|
@State
|
||||||
protected int selectedSubtitleIndex = 0;
|
protected int selectedSubtitleIndex = 0;
|
||||||
|
private StoredDirectoryHelper mainStorageAudio = null;
|
||||||
|
private StoredDirectoryHelper mainStorageVideo = null;
|
||||||
|
private DownloadManager downloadManager = null;
|
||||||
|
private ActionMenuItemView okButton = null;
|
||||||
|
private Context context;
|
||||||
|
private boolean askForSavePath;
|
||||||
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
|
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
|
||||||
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
|
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
|
||||||
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
|
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
|
||||||
|
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
|
||||||
|
|
||||||
private EditText nameEditText;
|
private EditText nameEditText;
|
||||||
private Spinner streamsSpinner;
|
private Spinner streamsSpinner;
|
||||||
private RadioGroup radioStreamsGroup;
|
private RadioGroup radioStreamsGroup;
|
||||||
private TextView threadsCountTextView;
|
private TextView threadsCountTextView;
|
||||||
private SeekBar threadsSeekBar;
|
private SeekBar threadsSeekBar;
|
||||||
|
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
public static DownloadDialog newInstance(StreamInfo info) {
|
public static DownloadDialog newInstance(final StreamInfo info) {
|
||||||
DownloadDialog dialog = new DownloadDialog();
|
DownloadDialog dialog = new DownloadDialog();
|
||||||
dialog.setInfo(info);
|
dialog.setInfo(info);
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DownloadDialog newInstance(Context context, StreamInfo info) {
|
public static DownloadDialog newInstance(final Context context, final StreamInfo info) {
|
||||||
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context,
|
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper
|
||||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false));
|
.getSortedStreamVideosList(context, info.getVideoStreams(),
|
||||||
|
info.getVideoOnlyStreams(), false));
|
||||||
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
|
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
|
||||||
|
|
||||||
final DownloadDialog instance = newInstance(info);
|
final DownloadDialog instance = newInstance(info);
|
||||||
|
@ -135,57 +138,61 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInfo(StreamInfo info) {
|
private void setInfo(final StreamInfo info) {
|
||||||
this.currentInfo = info;
|
this.currentInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioStreams(List<AudioStream> audioStreams) {
|
public void setAudioStreams(final List<AudioStream> audioStreams) {
|
||||||
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext()));
|
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) {
|
public void setAudioStreams(final StreamSizeWrapper<AudioStream> was) {
|
||||||
this.wrappedAudioStreams = wrappedAudioStreams;
|
this.wrappedAudioStreams = was;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVideoStreams(List<VideoStream> videoStreams) {
|
public void setVideoStreams(final List<VideoStream> videoStreams) {
|
||||||
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
|
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVideoStreams(StreamSizeWrapper<VideoStream> wrappedVideoStreams) {
|
|
||||||
this.wrappedVideoStreams = wrappedVideoStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubtitleStreams(List<SubtitlesStream> subtitleStreams) {
|
|
||||||
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubtitleStreams(StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams) {
|
|
||||||
this.wrappedSubtitleStreams = wrappedSubtitleStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedVideoStream(int selectedVideoIndex) {
|
|
||||||
this.selectedVideoIndex = selectedVideoIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedAudioStream(int selectedAudioIndex) {
|
|
||||||
this.selectedAudioIndex = selectedAudioIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedSubtitleStream(int selectedSubtitleIndex) {
|
|
||||||
this.selectedSubtitleIndex = selectedSubtitleIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
public void setVideoStreams(final StreamSizeWrapper<VideoStream> wvs) {
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
this.wrappedVideoStreams = wvs;
|
||||||
super.onCreate(savedInstanceState);
|
}
|
||||||
if (DEBUG)
|
|
||||||
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
|
||||||
|
|
||||||
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
public void setSubtitleStreams(final List<SubtitlesStream> subtitleStreams) {
|
||||||
|
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubtitleStreams(
|
||||||
|
final StreamSizeWrapper<SubtitlesStream> wss) {
|
||||||
|
this.wrappedSubtitleStreams = wss;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedVideoStream(final int svi) {
|
||||||
|
this.selectedVideoIndex = svi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedAudioStream(final int sai) {
|
||||||
|
this.selectedAudioIndex = sai;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedSubtitleStream(final int ssi) {
|
||||||
|
this.selectedSubtitleIndex = ssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreate() called with: "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PermissionHelper.checkStoragePermissions(getActivity(),
|
||||||
|
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -199,17 +206,23 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||||
|
|
||||||
for (int i = 0; i < videoStreams.size(); i++) {
|
for (int i = 0; i < videoStreams.size(); i++) {
|
||||||
if (!videoStreams.get(i).isVideoOnly()) continue;
|
if (!videoStreams.get(i).isVideoOnly()) {
|
||||||
AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
continue;
|
||||||
|
}
|
||||||
|
AudioStream audioStream = SecondaryStreamHelper
|
||||||
|
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
||||||
|
|
||||||
if (audioStream != null) {
|
if (audioStream != null) {
|
||||||
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
|
secondaryStreams
|
||||||
|
.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
Log.w(TAG, "No audio stream candidates for video format " + videoStreams.get(i).getFormat().name());
|
Log.w(TAG, "No audio stream candidates for video format "
|
||||||
|
+ videoStreams.get(i).getFormat().name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams, secondaryStreams);
|
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams,
|
||||||
|
secondaryStreams);
|
||||||
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
||||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
|
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
|
||||||
|
|
||||||
|
@ -218,7 +231,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
context.bindService(intent, new ServiceConnection() {
|
context.bindService(intent, new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(ComponentName cname, IBinder service) {
|
public void onServiceConnected(final ComponentName cname, final IBinder service) {
|
||||||
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||||
|
|
||||||
mainStorageAudio = mgr.getMainStorageAudio();
|
mainStorageAudio = mgr.getMainStorageAudio();
|
||||||
|
@ -232,25 +245,34 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(final ComponentName name) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
}, Context.BIND_AUTO_CREATE);
|
}, Context.BIND_AUTO_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Inits
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||||
if (DEBUG)
|
final Bundle savedInstanceState) {
|
||||||
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreateView() called with: "
|
||||||
|
+ "inflater = [" + inflater + "], container = [" + container + "], "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
}
|
||||||
return inflater.inflate(R.layout.download_dialog, container);
|
return inflater.inflate(R.layout.download_dialog, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
nameEditText = view.findViewById(R.id.file_name);
|
nameEditText = view.findViewById(R.id.file_name);
|
||||||
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
|
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
|
||||||
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
|
selectedAudioIndex = ListHelper
|
||||||
|
.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
|
||||||
|
|
||||||
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
|
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
|
||||||
|
|
||||||
|
@ -272,21 +294,20 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
threadsCountTextView.setText(String.valueOf(threads));
|
threadsCountTextView.setText(String.valueOf(threads));
|
||||||
threadsSeekBar.setProgress(threads - 1);
|
threadsSeekBar.setProgress(threads - 1);
|
||||||
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
|
public void onProgressChanged(final SeekBar seekbar, final int progress,
|
||||||
progress++;
|
final boolean fromUser) {
|
||||||
prefs.edit().putInt(getString(R.string.default_download_threads), progress).apply();
|
final int newProgress = progress + 1;
|
||||||
threadsCountTextView.setText(String.valueOf(progress));
|
prefs.edit().putInt(getString(R.string.default_download_threads), newProgress)
|
||||||
|
.apply();
|
||||||
|
threadsCountTextView.setText(String.valueOf(newProgress));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartTrackingTouch(SeekBar p1) {
|
public void onStartTrackingTouch(final SeekBar p1) { }
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar p1) {
|
public void onStopTrackingTouch(final SeekBar p1) { }
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchStreamsSize();
|
fetchStreamsSize();
|
||||||
|
@ -295,17 +316,20 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
private void fetchStreamsSize() {
|
private void fetchStreamsSize() {
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
|
|
||||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> {
|
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
|
||||||
|
.subscribe(result -> {
|
||||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) {
|
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) {
|
||||||
setupVideoSpinner();
|
setupVideoSpinner();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> {
|
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
|
||||||
|
.subscribe(result -> {
|
||||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
|
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
|
||||||
setupAudioSpinner();
|
setupAudioSpinner();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> {
|
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
|
||||||
|
.subscribe(result -> {
|
||||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
||||||
setupSubtitleSpinner();
|
setupSubtitleSpinner();
|
||||||
}
|
}
|
||||||
|
@ -318,14 +342,22 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Radio group Video&Audio options - Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Streams Spinner Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) {
|
if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) {
|
||||||
|
@ -336,7 +368,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
|
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
|
||||||
File file = Utils.getFileForUri(data.getData());
|
File file = Utils.getFileForUri(data.getData());
|
||||||
checkSelectedDownload(null, Uri.fromFile(file), file.getName(), StoredFileHelper.DEFAULT_MIME);
|
checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
|
||||||
|
StoredFileHelper.DEFAULT_MIME);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,27 +380,27 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the selected file was previously used
|
// check if the selected file was previously used
|
||||||
checkSelectedDownload(null, data.getData(), docFile.getName(), docFile.getType());
|
checkSelectedDownload(null, data.getData(), docFile.getName(),
|
||||||
|
docFile.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
private void initToolbar(final Toolbar toolbar) {
|
||||||
// Inits
|
if (DEBUG) {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
||||||
|
}
|
||||||
private void initToolbar(Toolbar toolbar) {
|
|
||||||
if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
|
||||||
|
|
||||||
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
|
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
|
||||||
|
|
||||||
toolbar.setTitle(R.string.download_dialog_title);
|
toolbar.setTitle(R.string.download_dialog_title);
|
||||||
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
|
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp
|
||||||
|
: R.drawable.ic_arrow_back_white_24dp);
|
||||||
toolbar.inflateMenu(R.menu.dialog_url);
|
toolbar.inflateMenu(R.menu.dialog_url);
|
||||||
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
|
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
|
||||||
toolbar.setNavigationContentDescription(R.string.cancel);
|
toolbar.setNavigationContentDescription(R.string.cancel);
|
||||||
|
|
||||||
okButton = toolbar.findViewById(R.id.okay);
|
okButton = toolbar.findViewById(R.id.okay);
|
||||||
okButton.setEnabled(false);// disable until the download service connection is done
|
okButton.setEnabled(false); // disable until the download service connection is done
|
||||||
|
|
||||||
toolbar.setOnMenuItemClickListener(item -> {
|
toolbar.setOnMenuItemClickListener(item -> {
|
||||||
if (item.getItemId() == R.id.okay) {
|
if (item.getItemId() == R.id.okay) {
|
||||||
|
@ -381,8 +414,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void setupAudioSpinner() {
|
private void setupAudioSpinner() {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
streamsSpinner.setAdapter(audioStreamsAdapter);
|
streamsSpinner.setAdapter(audioStreamsAdapter);
|
||||||
streamsSpinner.setSelection(selectedAudioIndex);
|
streamsSpinner.setSelection(selectedAudioIndex);
|
||||||
|
@ -390,7 +429,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupVideoSpinner() {
|
private void setupVideoSpinner() {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
streamsSpinner.setAdapter(videoStreamsAdapter);
|
streamsSpinner.setAdapter(videoStreamsAdapter);
|
||||||
streamsSpinner.setSelection(selectedVideoIndex);
|
streamsSpinner.setSelection(selectedVideoIndex);
|
||||||
|
@ -398,21 +439,21 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSubtitleSpinner() {
|
private void setupSubtitleSpinner() {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
streamsSpinner.setAdapter(subtitleStreamsAdapter);
|
streamsSpinner.setAdapter(subtitleStreamsAdapter);
|
||||||
streamsSpinner.setSelection(selectedSubtitleIndex);
|
streamsSpinner.setSelection(selectedSubtitleIndex);
|
||||||
setRadioButtonsState(true);
|
setRadioButtonsState(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Radio group Video&Audio options - Listener
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
|
public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) {
|
||||||
if (DEBUG)
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
|
Log.d(TAG, "onCheckedChanged() called with: "
|
||||||
|
+ "group = [" + group + "], checkedId = [" + checkedId + "]");
|
||||||
|
}
|
||||||
boolean flag = true;
|
boolean flag = true;
|
||||||
|
|
||||||
switch (checkedId) {
|
switch (checkedId) {
|
||||||
|
@ -431,14 +472,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
threadsSeekBar.setEnabled(flag);
|
threadsSeekBar.setEnabled(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Streams Spinner Listener
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(final AdapterView<?> parent, final View view,
|
||||||
if (DEBUG)
|
final int position, final long id) {
|
||||||
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onItemSelected() called with: "
|
||||||
|
+ "parent = [" + parent + "], view = [" + view + "], "
|
||||||
|
+ "position = [" + position + "], id = [" + id + "]");
|
||||||
|
}
|
||||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||||
case R.id.audio_button:
|
case R.id.audio_button:
|
||||||
selectedAudioIndex = position;
|
selectedAudioIndex = position;
|
||||||
|
@ -453,13 +494,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
public void onNothingSelected(final AdapterView<?> parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Utils
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void setupDownloadOptions() {
|
protected void setupDownloadOptions() {
|
||||||
setRadioButtonsState(false);
|
setRadioButtonsState(false);
|
||||||
|
|
||||||
|
@ -484,30 +521,36 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
subtitleButton.setChecked(true);
|
subtitleButton.setChecked(true);
|
||||||
setupSubtitleSpinner();
|
setupSubtitleSpinner();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), R.string.no_streams_available_download,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setRadioButtonsState(boolean enabled) {
|
private void setRadioButtonsState(final boolean enabled) {
|
||||||
radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled);
|
radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled);
|
||||||
radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled);
|
radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled);
|
||||||
radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
|
radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getSubtitleIndexBy(List<SubtitlesStream> streams) {
|
private int getSubtitleIndexBy(final List<SubtitlesStream> streams) {
|
||||||
final Localization preferredLocalization = NewPipe.getPreferredLocalization();
|
final Localization preferredLocalization = NewPipe.getPreferredLocalization();
|
||||||
|
|
||||||
int candidate = 0;
|
int candidate = 0;
|
||||||
for (int i = 0; i < streams.size(); i++) {
|
for (int i = 0; i < streams.size(); i++) {
|
||||||
final Locale streamLocale = streams.get(i).getLocale();
|
final Locale streamLocale = streams.get(i).getLocale();
|
||||||
|
|
||||||
final boolean languageEquals = streamLocale.getLanguage() != null && preferredLocalization.getLanguageCode() != null &&
|
final boolean languageEquals = streamLocale.getLanguage() != null
|
||||||
streamLocale.getLanguage().equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage());
|
&& preferredLocalization.getLanguageCode() != null
|
||||||
final boolean countryEquals = streamLocale.getCountry() != null && streamLocale.getCountry().equals(preferredLocalization.getCountryCode());
|
&& streamLocale.getLanguage()
|
||||||
|
.equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage());
|
||||||
|
final boolean countryEquals = streamLocale.getCountry() != null
|
||||||
|
&& streamLocale.getCountry().equals(preferredLocalization.getCountryCode());
|
||||||
|
|
||||||
if (languageEquals) {
|
if (languageEquals) {
|
||||||
if (countryEquals) return i;
|
if (countryEquals) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
candidate = i;
|
candidate = i;
|
||||||
}
|
}
|
||||||
|
@ -516,20 +559,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
StoredDirectoryHelper mainStorageAudio = null;
|
|
||||||
StoredDirectoryHelper mainStorageVideo = null;
|
|
||||||
DownloadManager downloadManager = null;
|
|
||||||
ActionMenuItemView okButton = null;
|
|
||||||
Context context;
|
|
||||||
boolean askForSavePath;
|
|
||||||
|
|
||||||
private String getNameEditText() {
|
private String getNameEditText() {
|
||||||
String str = nameEditText.getText().toString().trim();
|
String str = nameEditText.getText().toString().trim();
|
||||||
|
|
||||||
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
|
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showFailedDialog(@StringRes int msg) {
|
private void showFailedDialog(@StringRes final int msg) {
|
||||||
assureCorrectAppLanguage(getContext());
|
assureCorrectAppLanguage(getContext());
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
.setTitle(R.string.general_error)
|
.setTitle(R.string.general_error)
|
||||||
|
@ -539,13 +575,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showErrorActivity(Exception e) {
|
private void showErrorActivity(final Exception e) {
|
||||||
ErrorActivity.reportError(
|
ErrorActivity.reportError(
|
||||||
context,
|
context,
|
||||||
Collections.singletonList(e),
|
Collections.singletonList(e),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
|
ErrorActivity.ErrorInfo
|
||||||
|
.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,7 +600,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
case R.id.audio_button:
|
case R.id.audio_button:
|
||||||
mainStorage = mainStorageAudio;
|
mainStorage = mainStorageAudio;
|
||||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||||
switch(format) {
|
switch (format) {
|
||||||
case WEBMA_OPUS:
|
case WEBMA_OPUS:
|
||||||
mime = "audio/ogg";
|
mime = "audio/ogg";
|
||||||
filename += "opus";
|
filename += "opus";
|
||||||
|
@ -581,7 +618,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
filename += format.suffix;
|
filename += format.suffix;
|
||||||
break;
|
break;
|
||||||
case R.id.subtitle_button:
|
case R.id.subtitle_button:
|
||||||
mainStorage = mainStorageVideo;// subtitle & video files go together
|
mainStorage = mainStorageVideo; // subtitle & video files go together
|
||||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||||
mime = format.mimeType;
|
mime = format.mimeType;
|
||||||
filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix;
|
filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix;
|
||||||
|
@ -596,23 +633,25 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
// * save path not defined (via download settings)
|
// * save path not defined (via download settings)
|
||||||
// * the user checked the "ask where to download" option
|
// * the user checked the "ask where to download" option
|
||||||
|
|
||||||
if (!askForSavePath)
|
if (!askForSavePath) {
|
||||||
Toast.makeText(context, getString(R.string.no_available_dir), Toast.LENGTH_LONG).show();
|
Toast.makeText(context, getString(R.string.no_available_dir),
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
if (NewPipeSettings.useStorageAccessFramework(context)) {
|
if (NewPipeSettings.useStorageAccessFramework(context)) {
|
||||||
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, filename, mime);
|
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS,
|
||||||
|
filename, mime);
|
||||||
} else {
|
} else {
|
||||||
File initialSavePath;
|
File initialSavePath;
|
||||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button)
|
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
|
||||||
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
|
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
|
||||||
else
|
} else {
|
||||||
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
|
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
|
||||||
|
}
|
||||||
|
|
||||||
initialSavePath = new File(initialSavePath, filename);
|
initialSavePath = new File(initialSavePath, filename);
|
||||||
startActivityForResult(
|
startActivityForResult(FilePickerActivityHelper.chooseFileToSave(context,
|
||||||
FilePickerActivityHelper.chooseFileToSave(context, initialSavePath.getAbsolutePath()),
|
initialSavePath.getAbsolutePath()), REQUEST_DOWNLOAD_SAVE_AS);
|
||||||
REQUEST_DOWNLOAD_SAVE_AS
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -622,7 +661,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
|
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri targetFile, String filename, String mime) {
|
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
||||||
|
final Uri targetFile, final String filename,
|
||||||
|
final String mime) {
|
||||||
StoredFileHelper storage;
|
StoredFileHelper storage;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -631,10 +672,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
storage = new StoredFileHelper(context, null, targetFile, "");
|
storage = new StoredFileHelper(context, null, targetFile, "");
|
||||||
} else if (targetFile == null) {
|
} else if (targetFile == null) {
|
||||||
// the file does not exist, but it is probably used in a pending download
|
// the file does not exist, but it is probably used in a pending download
|
||||||
storage = new StoredFileHelper(mainStorage.getUri(), filename, mime, mainStorage.getTag());
|
storage = new StoredFileHelper(mainStorage.getUri(), filename, mime,
|
||||||
|
mainStorage.getTag());
|
||||||
} else {
|
} else {
|
||||||
// the target filename is already use, attempt to use it
|
// the target filename is already use, attempt to use it
|
||||||
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag());
|
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
|
||||||
|
mainStorage.getTag());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
showErrorActivity(e);
|
showErrorActivity(e);
|
||||||
|
@ -738,24 +781,28 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// try take (or steal) the file
|
// try take (or steal) the file
|
||||||
storageNew = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag());
|
storageNew = new StoredFileHelper(context, mainStorage.getUri(),
|
||||||
|
targetFile, mainStorage.getTag());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Failed to take (or steal) the file in " + targetFile.toString());
|
Log.e(TAG, "Failed to take (or steal) the file in "
|
||||||
|
+ targetFile.toString());
|
||||||
storageNew = null;
|
storageNew = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storageNew != null && storageNew.canWrite())
|
if (storageNew != null && storageNew.canWrite()) {
|
||||||
continueSelectedDownload(storageNew);
|
continueSelectedDownload(storageNew);
|
||||||
else
|
} else {
|
||||||
showFailedDialog(R.string.error_file_creation);
|
showFailedDialog(R.string.error_file_creation);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PendingRunning:
|
case PendingRunning:
|
||||||
storageNew = mainStorage.createUniqueFile(filename, mime);
|
storageNew = mainStorage.createUniqueFile(filename, mime);
|
||||||
if (storageNew == null)
|
if (storageNew == null) {
|
||||||
showFailedDialog(R.string.error_file_creation);
|
showFailedDialog(R.string.error_file_creation);
|
||||||
else
|
} else {
|
||||||
continueSelectedDownload(storageNew);
|
continueSelectedDownload(storageNew);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -763,7 +810,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
askDialog.create().show();
|
askDialog.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void continueSelectedDownload(@NonNull StoredFileHelper storage) {
|
private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
|
||||||
if (!storage.canWrite()) {
|
if (!storage.canWrite()) {
|
||||||
showFailedDialog(R.string.permission_denied);
|
showFailedDialog(R.string.permission_denied);
|
||||||
return;
|
return;
|
||||||
|
@ -771,7 +818,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
// check if the selected file has to be overwritten, by simply checking its length
|
// check if the selected file has to be overwritten, by simply checking its length
|
||||||
try {
|
try {
|
||||||
if (storage.length() > 0) storage.truncate();
|
if (storage.length() > 0) {
|
||||||
|
storage.truncate();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
||||||
showFailedDialog(R.string.overwrite_failed);
|
showFailedDialog(R.string.overwrite_failed);
|
||||||
|
@ -811,13 +860,15 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
if (secondary != null) {
|
if (secondary != null) {
|
||||||
secondaryStream = secondary.getStream();
|
secondaryStream = secondary.getStream();
|
||||||
|
|
||||||
if (selectedStream.getFormat() == MediaFormat.MPEG_4)
|
if (selectedStream.getFormat() == MediaFormat.MPEG_4) {
|
||||||
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
|
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
|
||||||
else
|
} else {
|
||||||
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
|
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||||
|
}
|
||||||
|
|
||||||
psArgs = null;
|
psArgs = null;
|
||||||
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
|
long videoSize = wrappedVideoStreams
|
||||||
|
.getSizeInBytes((VideoStream) selectedStream);
|
||||||
|
|
||||||
// set nearLength, only, if both sizes are fetched or known. This probably
|
// set nearLength, only, if both sizes are fetched or known. This probably
|
||||||
// does not work on slow networks but is later updated in the downloader
|
// does not work on slow networks but is later updated in the downloader
|
||||||
|
@ -827,7 +878,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.subtitle_button:
|
case R.id.subtitle_button:
|
||||||
threads = 1;// use unique thread for subtitles due small file size
|
threads = 1; // use unique thread for subtitles due small file size
|
||||||
kind = 's';
|
kind = 's';
|
||||||
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||||
|
|
||||||
|
@ -835,7 +886,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
|
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
|
||||||
psArgs = new String[]{
|
psArgs = new String[]{
|
||||||
selectedStream.getFormat().getSuffix(),
|
selectedStream.getFormat().getSuffix(),
|
||||||
"false",// ignore empty frames
|
"false" // ignore empty frames
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -854,14 +905,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
urls = new String[]{
|
urls = new String[]{
|
||||||
selectedStream.getUrl(), secondaryStream.getUrl()
|
selectedStream.getUrl(), secondaryStream.getUrl()
|
||||||
};
|
};
|
||||||
recoveryInfo = new MissionRecoveryInfo[]{
|
recoveryInfo = new MissionRecoveryInfo[]{new MissionRecoveryInfo(selectedStream),
|
||||||
new MissionRecoveryInfo(selectedStream), new MissionRecoveryInfo(secondaryStream)
|
new MissionRecoveryInfo(secondaryStream)};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadManagerService.startMission(
|
DownloadManagerService.startMission(context, urls, storage, kind, threads,
|
||||||
context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo
|
currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo);
|
||||||
);
|
|
||||||
|
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the current fragment can handle back presses
|
* Indicates that the current fragment can handle back presses.
|
||||||
*/
|
*/
|
||||||
public interface BackPressable {
|
public interface BackPressable {
|
||||||
/**
|
/**
|
||||||
* A back press was delegated to this fragment
|
* A back press was delegated to this fragment.
|
||||||
*
|
*
|
||||||
* @return if the back press was handled
|
* @return if the back press was handled
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
@ -11,6 +10,9 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
import com.jakewharton.rxbinding2.view.RxView;
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
|
@ -36,22 +38,21 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {
|
public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected AtomicBoolean wasLoading = new AtomicBoolean();
|
protected AtomicBoolean wasLoading = new AtomicBoolean();
|
||||||
protected AtomicBoolean isLoading = new AtomicBoolean();
|
protected AtomicBoolean isLoading = new AtomicBoolean();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected View emptyStateView;
|
private View emptyStateView;
|
||||||
@Nullable
|
@Nullable
|
||||||
protected ProgressBar loadingProgressBar;
|
private ProgressBar loadingProgressBar;
|
||||||
|
|
||||||
protected View errorPanelRoot;
|
protected View errorPanelRoot;
|
||||||
protected Button errorButtonRetry;
|
private Button errorButtonRetry;
|
||||||
protected TextView errorTextView;
|
private TextView errorTextView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View rootView, Bundle savedInstanceState) {
|
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.onViewCreated(rootView, savedInstanceState);
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
doInitialLoadLogic();
|
doInitialLoadLogic();
|
||||||
}
|
}
|
||||||
|
@ -62,14 +63,12 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
wasLoading.set(isLoading.get());
|
wasLoading.set(isLoading.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
emptyStateView = rootView.findViewById(R.id.empty_state_view);
|
emptyStateView = rootView.findViewById(R.id.empty_state_view);
|
||||||
|
@ -105,8 +104,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
startLoading(true);
|
startLoading(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startLoading(boolean forceLoad) {
|
protected void startLoading(final boolean forceLoad) {
|
||||||
if (DEBUG) Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]");
|
||||||
|
}
|
||||||
showLoading();
|
showLoading();
|
||||||
isLoading.set(true);
|
isLoading.set(true);
|
||||||
}
|
}
|
||||||
|
@ -117,42 +118,62 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
if (emptyStateView != null) animateView(emptyStateView, false, 150);
|
if (emptyStateView != null) {
|
||||||
if (loadingProgressBar != null) animateView(loadingProgressBar, true, 400);
|
animateView(emptyStateView, false, 150);
|
||||||
|
}
|
||||||
|
if (loadingProgressBar != null) {
|
||||||
|
animateView(loadingProgressBar, true, 400);
|
||||||
|
}
|
||||||
animateView(errorPanelRoot, false, 150);
|
animateView(errorPanelRoot, false, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideLoading() {
|
public void hideLoading() {
|
||||||
if (emptyStateView != null) animateView(emptyStateView, false, 150);
|
if (emptyStateView != null) {
|
||||||
if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0);
|
animateView(emptyStateView, false, 150);
|
||||||
|
}
|
||||||
|
if (loadingProgressBar != null) {
|
||||||
|
animateView(loadingProgressBar, false, 0);
|
||||||
|
}
|
||||||
animateView(errorPanelRoot, false, 150);
|
animateView(errorPanelRoot, false, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
if (emptyStateView != null) animateView(emptyStateView, true, 200);
|
if (emptyStateView != null) {
|
||||||
if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0);
|
animateView(emptyStateView, true, 200);
|
||||||
|
}
|
||||||
|
if (loadingProgressBar != null) {
|
||||||
|
animateView(loadingProgressBar, false, 0);
|
||||||
|
}
|
||||||
animateView(errorPanelRoot, false, 150);
|
animateView(errorPanelRoot, false, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showError(String message, boolean showRetryButton) {
|
public void showError(final String message, final boolean showRetryButton) {
|
||||||
if (DEBUG) Log.d(TAG, "showError() called with: message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "showError() called with: "
|
||||||
|
+ "message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
|
||||||
|
}
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
InfoCache.getInstance().clearCache();
|
InfoCache.getInstance().clearCache();
|
||||||
hideLoading();
|
hideLoading();
|
||||||
|
|
||||||
errorTextView.setText(message);
|
errorTextView.setText(message);
|
||||||
if (showRetryButton) animateView(errorButtonRetry, true, 600);
|
if (showRetryButton) {
|
||||||
else animateView(errorButtonRetry, false, 0);
|
animateView(errorButtonRetry, true, 600);
|
||||||
|
} else {
|
||||||
|
animateView(errorButtonRetry, false, 0);
|
||||||
|
}
|
||||||
animateView(errorPanelRoot, true, 300);
|
animateView(errorPanelRoot, true, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(I result) {
|
public void handleResult(final I result) {
|
||||||
if (DEBUG) Log.d(TAG, "handleResult() called with: result = [" + result + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "handleResult() called with: result = [" + result + "]");
|
||||||
|
}
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,21 +182,28 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation handles some general exceptions
|
* Default implementation handles some general exceptions.
|
||||||
*
|
*
|
||||||
* @return if the exception was handled
|
* @param exception The exception that should be handled
|
||||||
|
* @return If the exception was handled
|
||||||
*/
|
*/
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onError() called with: exception = [" + exception + "]");
|
||||||
|
}
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
|
|
||||||
if (isDetached() || isRemoving()) {
|
if (isDetached() || isRemoving()) {
|
||||||
if (DEBUG) Log.w(TAG, "onError() is detached or removing = [" + exception + "]");
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, "onError() is detached or removing = [" + exception + "]");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ExtractorHelper.isInterruptedCaused(exception)) {
|
if (ExtractorHelper.isInterruptedCaused(exception)) {
|
||||||
if (DEBUG) Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,8 +221,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onReCaptchaException(ReCaptchaException exception) {
|
public void onReCaptchaException(final ReCaptchaException exception) {
|
||||||
if (DEBUG) Log.d(TAG, "onReCaptchaException() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onReCaptchaException() called");
|
||||||
|
}
|
||||||
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||||
// Starting ReCaptcha Challenge Activity
|
// Starting ReCaptcha Challenge Activity
|
||||||
Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
||||||
|
@ -204,33 +234,58 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
showError(getString(R.string.recaptcha_request_toast), false);
|
showError(getString(R.string.recaptcha_request_toast), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUnrecoverableError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
|
public void onUnrecoverableError(final Throwable exception, final UserAction userAction,
|
||||||
onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName, request, errorId);
|
final String serviceName, final String request,
|
||||||
|
@StringRes final int errorId) {
|
||||||
|
onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName,
|
||||||
|
request, errorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUnrecoverableError(List<Throwable> exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
|
public void onUnrecoverableError(final List<Throwable> exception, final UserAction userAction,
|
||||||
if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
|
final String serviceName, final String request,
|
||||||
|
@StringRes final int errorId) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
|
||||||
|
}
|
||||||
|
|
||||||
if (serviceName == null) serviceName = "none";
|
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
|
||||||
if (request == null) request = "none";
|
ErrorActivity.ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
|
||||||
|
request == null ? "none" : request, errorId));
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showSnackBarError(Throwable exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
|
public void showSnackBarError(final Throwable exception, final UserAction userAction,
|
||||||
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request, errorId);
|
final String serviceName, final String request,
|
||||||
|
@StringRes final int errorId) {
|
||||||
|
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request,
|
||||||
|
errorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a SnackBar and only call ErrorActivity#reportError IF we a find a valid view (otherwise the error screen appears)
|
* Show a SnackBar and only call
|
||||||
|
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorActivity.ErrorInfo)}
|
||||||
|
* IF we a find a valid view (otherwise the error screen appears).
|
||||||
|
*
|
||||||
|
* @param exception List of the exceptions to show
|
||||||
|
* @param userAction The user action that caused the exception
|
||||||
|
* @param serviceName The service where the exception happened
|
||||||
|
* @param request The page that was requested
|
||||||
|
* @param errorId The ID of the error
|
||||||
*/
|
*/
|
||||||
public void showSnackBarError(List<Throwable> exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) {
|
public void showSnackBarError(final List<Throwable> exception, final UserAction userAction,
|
||||||
|
final String serviceName, final String request,
|
||||||
|
@StringRes final int errorId) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "showSnackBarError() called with: exception = [" + exception + "], userAction = [" + userAction + "], request = [" + request + "], errorId = [" + errorId + "]");
|
Log.d(TAG, "showSnackBarError() called with: "
|
||||||
|
+ "exception = [" + exception + "], userAction = [" + userAction + "], "
|
||||||
|
+ "request = [" + request + "], errorId = [" + errorId + "]");
|
||||||
}
|
}
|
||||||
View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
|
View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
|
||||||
if (rootView == null && getView() != null) rootView = getView();
|
if (rootView == null && getView() != null) {
|
||||||
if (rootView == null) return;
|
rootView = getView();
|
||||||
|
}
|
||||||
|
if (rootView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
||||||
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
public class BlankFragment extends BaseFragment {
|
public class BlankFragment extends BaseFragment {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
|
final Bundle savedInstanceState) {
|
||||||
setTitle("NewPipe");
|
setTitle("NewPipe");
|
||||||
return inflater.inflate(R.layout.fragment_blank, container, false);
|
return inflater.inflate(R.layout.fragment_blank, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
setTitle("NewPipe");
|
setTitle("NewPipe");
|
||||||
// leave this inline. Will make it harder for copy cats.
|
// leave this inline. Will make it harder for copy cats.
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
public class EmptyFragment extends BaseFragment {
|
public class EmptyFragment extends BaseFragment {
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
|
final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_empty, container, false);
|
return inflater.inflate(R.layout.fragment_empty, container, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,14 +50,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
tabsManager = TabsManager.getManager(activity);
|
tabsManager = TabsManager.getManager(activity);
|
||||||
tabsManager.setSavedTabsListener(() -> {
|
tabsManager.setSavedTabsListener(() -> {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed());
|
Log.d(TAG, "TabsManager.SavedTabsChangeListener: "
|
||||||
|
+ "onTabsChanged called, isResumed = " + isResumed());
|
||||||
}
|
}
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
setupTabs();
|
setupTabs();
|
||||||
|
@ -68,12 +69,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_main, container, false);
|
return inflater.inflate(R.layout.fragment_main, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
||||||
|
@ -89,14 +92,18 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (hasTabsChanged) setupTabs();
|
if (hasTabsChanged) {
|
||||||
|
setupTabs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
tabsManager.unsetSavedTabsListener();
|
tabsManager.unsetSavedTabsListener();
|
||||||
if (viewPager != null) viewPager.setAdapter(null);
|
if (viewPager != null) {
|
||||||
|
viewPager.setAdapter(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -104,9 +111,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
|
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||||
|
}
|
||||||
inflater.inflate(R.menu.main_fragment_menu, menu);
|
inflater.inflate(R.menu.main_fragment_menu, menu);
|
||||||
|
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
|
@ -116,7 +126,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_search:
|
case R.id.action_search:
|
||||||
try {
|
try {
|
||||||
|
@ -141,7 +151,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
tabsList.addAll(tabsManager.getTabs());
|
tabsList.addAll(tabsManager.getTabs());
|
||||||
|
|
||||||
if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) {
|
if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) {
|
||||||
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), getChildFragmentManager(), tabsList);
|
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(),
|
||||||
|
getChildFragmentManager(), tabsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewPager.setAdapter(null);
|
viewPager.setAdapter(null);
|
||||||
|
@ -165,31 +176,37 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitleForTab(int tabPosition) {
|
private void updateTitleForTab(final int tabPosition) {
|
||||||
setTitle(tabsList.get(tabPosition).getTabName(requireContext()));
|
setTitle(tabsList.get(tabPosition).getTabName(requireContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabSelected(TabLayout.Tab selectedTab) {
|
public void onTabSelected(final TabLayout.Tab selectedTab) {
|
||||||
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
|
||||||
|
}
|
||||||
updateTitleForTab(selectedTab.getPosition());
|
updateTitleForTab(selectedTab.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabUnselected(TabLayout.Tab tab) {
|
public void onTabUnselected(final TabLayout.Tab tab) { }
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabReselected(TabLayout.Tab tab) {
|
public void onTabReselected(final TabLayout.Tab tab) {
|
||||||
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
|
||||||
|
}
|
||||||
updateTitleForTab(tab.getPosition());
|
updateTitleForTab(tab.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapterMenuWorkaround {
|
private static final class SelectedTabsPagerAdapter
|
||||||
|
extends FragmentStatePagerAdapterMenuWorkaround {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final List<Tab> internalTabsList;
|
private final List<Tab> internalTabsList;
|
||||||
|
|
||||||
private SelectedTabsPagerAdapter(Context context, FragmentManager fragmentManager, List<Tab> tabsList) {
|
private SelectedTabsPagerAdapter(final Context context,
|
||||||
|
final FragmentManager fragmentManager,
|
||||||
|
final List<Tab> tabsList) {
|
||||||
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.internalTabsList = new ArrayList<>(tabsList);
|
this.internalTabsList = new ArrayList<>(tabsList);
|
||||||
|
@ -197,7 +214,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(final int position) {
|
||||||
final Tab tab = internalTabsList.get(position);
|
final Tab tab = internalTabsList.get(position);
|
||||||
|
|
||||||
Throwable throwable = null;
|
Throwable throwable = null;
|
||||||
|
@ -209,8 +226,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
ErrorActivity.reportError(context, throwable, null, null,
|
ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
||||||
return new BlankFragment();
|
return new BlankFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +239,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemPosition(Object object) {
|
public int getItemPosition(final Object object) {
|
||||||
// Causes adapter to reload all Fragments when
|
// Causes adapter to reload all Fragments when
|
||||||
// notifyDataSetChanged is called
|
// notifyDataSetChanged is called
|
||||||
return POSITION_NONE;
|
return POSITION_NONE;
|
||||||
|
@ -233,7 +250,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
return internalTabsList.size();
|
return internalTabsList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sameTabs(List<Tab> tabsToCompare) {
|
public boolean sameTabs(final List<Tab> tabsToCompare) {
|
||||||
return internalTabsList.equals(tabsToCompare);
|
return internalTabsList.equals(tabsToCompare);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||||
* if the view is scrolled below the last item.
|
* if the view is scrolled below the last item.
|
||||||
*/
|
*/
|
||||||
public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener {
|
public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
if (dy > 0) {
|
if (dy > 0) {
|
||||||
int pastVisibleItems = 0, visibleItemCount, totalItemCount;
|
int pastVisibleItems = 0;
|
||||||
|
int visibleItemCount;
|
||||||
|
int totalItemCount;
|
||||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||||
|
|
||||||
visibleItemCount = layoutManager.getChildCount();
|
visibleItemCount = layoutManager.getChildCount();
|
||||||
|
@ -22,10 +23,14 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
||||||
|
|
||||||
// Already covers the GridLayoutManager case
|
// Already covers the GridLayoutManager case
|
||||||
if (layoutManager instanceof LinearLayoutManager) {
|
if (layoutManager instanceof LinearLayoutManager) {
|
||||||
pastVisibleItems = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
|
pastVisibleItems = ((LinearLayoutManager) layoutManager)
|
||||||
|
.findFirstVisibleItemPosition();
|
||||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null);
|
int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
||||||
if (positions != null && positions.length > 0) pastVisibleItems = positions[0];
|
.findFirstVisibleItemPositions(null);
|
||||||
|
if (positions != null && positions.length > 0) {
|
||||||
|
pastVisibleItems = positions[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
|
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
|
||||||
|
|
|
@ -2,8 +2,11 @@ package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
public interface ViewContract<I> {
|
public interface ViewContract<I> {
|
||||||
void showLoading();
|
void showLoading();
|
||||||
|
|
||||||
void hideLoading();
|
void hideLoading();
|
||||||
|
|
||||||
void showEmptyState();
|
void showEmptyState();
|
||||||
|
|
||||||
void showError(String message, boolean showRetryButton);
|
void showError(String message, boolean showRetryButton);
|
||||||
|
|
||||||
void handleResult(I result);
|
void handleResult(I result);
|
||||||
|
|
|
@ -4,19 +4,15 @@ import java.io.Serializable;
|
||||||
|
|
||||||
class StackItem implements Serializable {
|
class StackItem implements Serializable {
|
||||||
private final int serviceId;
|
private final int serviceId;
|
||||||
private String title;
|
|
||||||
private final String url;
|
private final String url;
|
||||||
|
private String title;
|
||||||
|
|
||||||
StackItem(int serviceId, String url, String title) {
|
StackItem(final int serviceId, final String url, final String title) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getServiceId() {
|
public int getServiceId() {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +21,10 @@ class StackItem implements Serializable {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTitle(final String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
package org.schabi.newpipe.fragments.detail;
|
package org.schabi.newpipe.fragments.detail;
|
||||||
|
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TabAdaptor extends FragmentPagerAdapter {
|
public class TabAdaptor extends FragmentPagerAdapter {
|
||||||
|
|
||||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||||
private final FragmentManager fragmentManager;
|
private final FragmentManager fragmentManager;
|
||||||
|
|
||||||
public TabAdaptor(FragmentManager fm) {
|
public TabAdaptor(final FragmentManager fm) {
|
||||||
super(fm);
|
super(fm);
|
||||||
this.fragmentManager = fm;
|
this.fragmentManager = fm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(final int position) {
|
||||||
return mFragmentList.get(position);
|
return mFragmentList.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
||||||
return mFragmentList.size();
|
return mFragmentList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFragment(Fragment fragment, String title) {
|
public void addFragment(final Fragment fragment, final String title) {
|
||||||
mFragmentList.add(fragment);
|
mFragmentList.add(fragment);
|
||||||
mFragmentTitleList.add(title);
|
mFragmentTitleList.add(title);
|
||||||
}
|
}
|
||||||
|
@ -40,46 +40,49 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
||||||
mFragmentTitleList.clear();
|
mFragmentTitleList.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeItem(int position){
|
public void removeItem(final int position) {
|
||||||
mFragmentList.remove(position == 0 ? 0 : position - 1);
|
mFragmentList.remove(position == 0 ? 0 : position - 1);
|
||||||
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
|
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateItem(int position, Fragment fragment){
|
public void updateItem(final int position, final Fragment fragment) {
|
||||||
mFragmentList.set(position, fragment);
|
mFragmentList.set(position, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateItem(String title, Fragment fragment){
|
public void updateItem(final String title, final Fragment fragment) {
|
||||||
int index = mFragmentTitleList.indexOf(title);
|
int index = mFragmentTitleList.indexOf(title);
|
||||||
if(index != -1){
|
if (index != -1) {
|
||||||
updateItem(index, fragment);
|
updateItem(index, fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemPosition(Object object) {
|
public int getItemPosition(final Object object) {
|
||||||
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
|
if (mFragmentList.contains(object)) {
|
||||||
else return POSITION_NONE;
|
return mFragmentList.indexOf(object);
|
||||||
|
} else {
|
||||||
|
return POSITION_NONE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getItemPositionByTitle(String title) {
|
public int getItemPositionByTitle(final String title) {
|
||||||
return mFragmentTitleList.indexOf(title);
|
return mFragmentTitleList.indexOf(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getItemTitle(int position) {
|
public String getItemTitle(final int position) {
|
||||||
if (position < 0 || position >= mFragmentTitleList.size()) {
|
if (position < 0 || position >= mFragmentTitleList.size()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return mFragmentTitleList.get(position);
|
return mFragmentTitleList.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyDataSetUpdate(){
|
public void notifyDataSetUpdate() {
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
public void destroyItem(final ViewGroup container, final int position, final Object object) {
|
||||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,16 +7,17 @@ import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
@ -40,24 +41,26 @@ import java.util.Queue;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener {
|
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
|
implements ListViewContract<I, N>, StateSaver.WriteRead,
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
||||||
protected InfoListAdapter infoListAdapter;
|
protected InfoListAdapter infoListAdapter;
|
||||||
protected RecyclerView itemsList;
|
protected RecyclerView itemsList;
|
||||||
private int updateFlags = 0;
|
protected StateSaver.SavedState savedState;
|
||||||
|
|
||||||
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
private boolean useDefaultStateSaving = true;
|
||||||
|
private int updateFlags = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
|
|
||||||
if (infoListAdapter == null) {
|
if (infoListAdapter == null) {
|
||||||
|
@ -71,17 +74,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
.registerOnSharedPreferenceChangeListener(this);
|
.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// State Saving
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (useDefaultStateSaving) StateSaver.onDestroy(savedState);
|
if (useDefaultStateSaving) {
|
||||||
|
StateSaver.onDestroy(savedState);
|
||||||
|
}
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
.unregisterOnSharedPreferenceChangeListener(this);
|
.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
@ -93,28 +102,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
if (updateFlags != 0) {
|
if (updateFlags != 0) {
|
||||||
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
||||||
final boolean useGrid = isGridLayout();
|
final boolean useGrid = isGridLayout();
|
||||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
itemsList.setLayoutManager(useGrid
|
||||||
infoListAdapter.setGridItemVariants(useGrid);
|
? getGridLayoutManager() : getListLayoutManager());
|
||||||
|
infoListAdapter.setUseGridVariant(useGrid);
|
||||||
infoListAdapter.notifyDataSetChanged();
|
infoListAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
updateFlags = 0;
|
updateFlags = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// State Saving
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected StateSaver.SavedState savedState;
|
|
||||||
protected boolean useDefaultStateSaving = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the default implementation of {@link StateSaver.WriteRead} should be used.
|
* If the default implementation of {@link StateSaver.WriteRead} should be used.
|
||||||
*
|
*
|
||||||
* @see StateSaver
|
* @see StateSaver
|
||||||
|
* @param useDefaultStateSaving Whether the default implementation should be used
|
||||||
*/
|
*/
|
||||||
public void useDefaultStateSaving(boolean useDefault) {
|
public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
|
||||||
this.useDefaultStateSaving = useDefault;
|
this.useDefaultStateSaving = useDefaultStateSaving;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -124,13 +128,15 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(Queue<Object> objectsToSave) {
|
public void writeTo(final Queue<Object> objectsToSave) {
|
||||||
if (useDefaultStateSaving) objectsToSave.add(infoListAdapter.getItemsList());
|
if (useDefaultStateSaving) {
|
||||||
|
objectsToSave.add(infoListAdapter.getItemsList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||||
if (useDefaultStateSaving) {
|
if (useDefaultStateSaving) {
|
||||||
infoListAdapter.getItemsList().clear();
|
infoListAdapter.getItemsList().clear();
|
||||||
infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll());
|
infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll());
|
||||||
|
@ -138,15 +144,20 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle bundle) {
|
public void onSaveInstanceState(final Bundle bundle) {
|
||||||
super.onSaveInstanceState(bundle);
|
super.onSaveInstanceState(bundle);
|
||||||
if (useDefaultStateSaving) savedState = StateSaver.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this);
|
if (useDefaultStateSaving) {
|
||||||
|
savedState = StateSaver
|
||||||
|
.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
|
protected void onRestoreInstanceState(@NonNull final Bundle bundle) {
|
||||||
super.onRestoreInstanceState(bundle);
|
super.onRestoreInstanceState(bundle);
|
||||||
if (useDefaultStateSaving) savedState = StateSaver.tryToRestore(bundle, this);
|
if (useDefaultStateSaving) {
|
||||||
|
savedState = StateSaver.tryToRestore(bundle, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -169,29 +180,32 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
final Resources resources = activity.getResources();
|
final Resources resources = activity.getResources();
|
||||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||||
width += (24 * resources.getDisplayMetrics().density);
|
width += (24 * resources.getDisplayMetrics().density);
|
||||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
|
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
|
||||||
|
/ (double) width);
|
||||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||||
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
|
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
|
||||||
return lm;
|
return lm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
final boolean useGrid = isGridLayout();
|
final boolean useGrid = isGridLayout();
|
||||||
itemsList = rootView.findViewById(R.id.items_list);
|
itemsList = rootView.findViewById(R.id.items_list);
|
||||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||||
|
|
||||||
infoListAdapter.setGridItemVariants(useGrid);
|
infoListAdapter.setUseGridVariant(useGrid);
|
||||||
infoListAdapter.setFooter(getListFooter());
|
infoListAdapter.setFooter(getListFooter());
|
||||||
infoListAdapter.setHeader(getListHeader());
|
infoListAdapter.setHeader(getListHeader());
|
||||||
|
|
||||||
itemsList.setAdapter(infoListAdapter);
|
itemsList.setAdapter(infoListAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onItemSelected(InfoItem selectedItem) {
|
protected void onItemSelected(final InfoItem selectedItem) {
|
||||||
if (DEBUG) Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -199,19 +213,19 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
super.initListeners();
|
super.initListeners();
|
||||||
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
|
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(StreamInfoItem selectedItem) {
|
public void selected(final StreamInfoItem selectedItem) {
|
||||||
onStreamSelected(selectedItem);
|
onStreamSelected(selectedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void held(StreamInfoItem selectedItem) {
|
public void held(final StreamInfoItem selectedItem) {
|
||||||
showStreamDialog(selectedItem);
|
showStreamDialog(selectedItem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
|
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(ChannelInfoItem selectedItem) {
|
public void selected(final ChannelInfoItem selectedItem) {
|
||||||
try {
|
try {
|
||||||
onItemSelected(selectedItem);
|
onItemSelected(selectedItem);
|
||||||
NavigationHelper.openChannelFragment(getFM(),
|
NavigationHelper.openChannelFragment(getFM(),
|
||||||
|
@ -226,7 +240,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
|
|
||||||
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
|
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(PlaylistInfoItem selectedItem) {
|
public void selected(final PlaylistInfoItem selectedItem) {
|
||||||
try {
|
try {
|
||||||
onItemSelected(selectedItem);
|
onItemSelected(selectedItem);
|
||||||
NavigationHelper.openPlaylistFragment(getFM(),
|
NavigationHelper.openPlaylistFragment(getFM(),
|
||||||
|
@ -241,7 +255,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
|
|
||||||
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
|
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(CommentsInfoItem selectedItem) {
|
public void selected(final CommentsInfoItem selectedItem) {
|
||||||
onItemSelected(selectedItem);
|
onItemSelected(selectedItem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -249,13 +263,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
itemsList.clearOnScrollListeners();
|
itemsList.clearOnScrollListeners();
|
||||||
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrolledDown(RecyclerView recyclerView) {
|
public void onScrolledDown(final RecyclerView recyclerView) {
|
||||||
onScrollToBottom();
|
onScrollToBottom();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStreamSelected(StreamInfoItem selectedItem) {
|
private void onStreamSelected(final StreamInfoItem selectedItem) {
|
||||||
onItemSelected(selectedItem);
|
onItemSelected(selectedItem);
|
||||||
NavigationHelper.openVideoDetailFragment(getFM(),
|
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||||
|
@ -268,12 +282,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected void showStreamDialog(final StreamInfoItem item) {
|
protected void showStreamDialog(final StreamInfoItem item) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (context == null || context.getResources() == null || activity == null) return;
|
if (context == null || context.getResources() == null || activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
|
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||||
StreamDialogEntry.setEnabledEntries(
|
StreamDialogEntry.setEnabledEntries(
|
||||||
|
@ -291,8 +305,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
StreamDialogEntry.share);
|
StreamDialogEntry.share);
|
||||||
}
|
}
|
||||||
|
|
||||||
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
|
||||||
StreamDialogEntry.clickOn(which, this, item)).show();
|
(dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -300,8 +314,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
|
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||||
|
}
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null) {
|
if (supportActionBar != null) {
|
||||||
|
@ -339,7 +356,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showError(String message, boolean showRetryButton) {
|
public void showError(final String message, final boolean showRetryButton) {
|
||||||
super.showError(message, showRetryButton);
|
super.showError(message, showRetryButton);
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
animateView(itemsList, false, 200);
|
animateView(itemsList, false, 200);
|
||||||
|
@ -361,25 +378,28 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(N result) {
|
public void handleNextItems(final N result) {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||||
|
final String key) {
|
||||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||||
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isGridLayout() {
|
protected boolean isGridLayout() {
|
||||||
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
|
final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
if ("auto".equals(list_mode)) {
|
.getString(getString(R.string.list_view_mode_key),
|
||||||
|
getString(R.string.list_view_mode_value));
|
||||||
|
if ("auto".equals(listMode)) {
|
||||||
final Configuration configuration = getResources().getConfiguration();
|
final Configuration configuration = getResources().getConfiguration();
|
||||||
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
||||||
} else {
|
} else {
|
||||||
return "grid".equals(list_mode);
|
return "grid".equals(listMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
public abstract class BaseListInfoFragment<I extends ListInfo>
|
public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
|
extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||||
@State
|
@State
|
||||||
|
@ -34,7 +33,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
protected Disposable currentWorker;
|
protected Disposable currentWorker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
setTitle(name);
|
setTitle(name);
|
||||||
showListFooter(hasMoreItems());
|
showListFooter(hasMoreItems());
|
||||||
|
@ -43,7 +42,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
if (currentWorker != null) currentWorker.dispose();
|
if (currentWorker != null) {
|
||||||
|
currentWorker.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,7 +74,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(Queue<Object> objectsToSave) {
|
public void writeTo(final Queue<Object> objectsToSave) {
|
||||||
super.writeTo(objectsToSave);
|
super.writeTo(objectsToSave);
|
||||||
objectsToSave.add(currentInfo);
|
objectsToSave.add(currentInfo);
|
||||||
objectsToSave.add(currentNextPageUrl);
|
objectsToSave.add(currentNextPageUrl);
|
||||||
|
@ -81,7 +82,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||||
super.readFrom(savedObjects);
|
super.readFrom(savedObjects);
|
||||||
currentInfo = (I) savedObjects.poll();
|
currentInfo = (I) savedObjects.poll();
|
||||||
currentNextPageUrl = (String) savedObjects.poll();
|
currentNextPageUrl = (String) savedObjects.poll();
|
||||||
|
@ -92,10 +93,14 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void doInitialLoadLogic() {
|
protected void doInitialLoadLogic() {
|
||||||
if (DEBUG) Log.d(TAG, "doInitialLoadLogic() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "doInitialLoadLogic() called");
|
||||||
|
}
|
||||||
if (currentInfo == null) {
|
if (currentInfo == null) {
|
||||||
startLoading(false);
|
startLoading(false);
|
||||||
} else handleResult(currentInfo);
|
} else {
|
||||||
|
handleResult(currentInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,18 +108,21 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}.
|
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}.
|
||||||
*
|
*
|
||||||
* @param forceLoad allow or disallow the result to come from the cache
|
* @param forceLoad allow or disallow the result to come from the cache
|
||||||
|
* @return Rx {@link Single} containing the {@link ListInfo}
|
||||||
*/
|
*/
|
||||||
protected abstract Single<I> loadResult(boolean forceLoad);
|
protected abstract Single<I> loadResult(boolean forceLoad);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startLoading(boolean forceLoad) {
|
public void startLoading(final boolean forceLoad) {
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
|
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
|
|
||||||
currentInfo = null;
|
currentInfo = null;
|
||||||
if (currentWorker != null) currentWorker.dispose();
|
if (currentWorker != null) {
|
||||||
|
currentWorker.dispose();
|
||||||
|
}
|
||||||
currentWorker = loadResult(forceLoad)
|
currentWorker = loadResult(forceLoad)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -127,19 +135,25 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implement the logic to load more items<br/>
|
* Implement the logic to load more items.
|
||||||
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}
|
* <p>You can use the default implementations
|
||||||
|
* from {@link org.schabi.newpipe.util.ExtractorHelper}.</p>
|
||||||
|
*
|
||||||
|
* @return Rx {@link Single} containing the {@link ListExtractor.InfoItemsPage}
|
||||||
*/
|
*/
|
||||||
protected abstract Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic();
|
protected abstract Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic();
|
||||||
|
|
||||||
protected void loadMoreItems() {
|
protected void loadMoreItems() {
|
||||||
isLoading.set(true);
|
isLoading.set(true);
|
||||||
|
|
||||||
if (currentWorker != null) currentWorker.dispose();
|
if (currentWorker != null) {
|
||||||
|
currentWorker.dispose();
|
||||||
|
}
|
||||||
currentWorker = loadMoreItemsLogic()
|
currentWorker = loadMoreItemsLogic()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
.subscribe((@io.reactivex.annotations.NonNull
|
||||||
|
ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
handleNextItems(InfoItemsPage);
|
handleNextItems(InfoItemsPage);
|
||||||
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
|
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
|
||||||
|
@ -149,7 +163,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
currentNextPageUrl = result.getNextPageUrl();
|
currentNextPageUrl = result.getNextPageUrl();
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
|
@ -167,7 +181,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull I result) {
|
public void handleResult(@NonNull final I result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
name = result.getName();
|
name = result.getName();
|
||||||
|
@ -188,9 +202,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void setInitialData(int serviceId, String url, String name) {
|
protected void setInitialData(final int sid, final String u, final String title) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = sid;
|
||||||
this.url = url;
|
this.url = u;
|
||||||
this.name = !TextUtils.isEmpty(name) ? name : "";
|
this.name = !TextUtils.isEmpty(title) ? title : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,6 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -21,6 +17,11 @@ import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.jakewharton.rxbinding2.view.RxView;
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -29,7 +30,6 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
|
@ -63,15 +63,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
|
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
private Disposable subscribeButtonMonitor;
|
private Disposable subscribeButtonMonitor;
|
||||||
private SubscriptionManager subscriptionManager;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
private SubscriptionManager subscriptionManager;
|
||||||
private View headerRootLayout;
|
private View headerRootLayout;
|
||||||
private ImageView headerChannelBanner;
|
private ImageView headerChannelBanner;
|
||||||
private ImageView headerAvatarView;
|
private ImageView headerAvatarView;
|
||||||
|
@ -79,25 +78,24 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
private TextView headerSubscribersTextView;
|
private TextView headerSubscribersTextView;
|
||||||
private Button headerSubscribeButton;
|
private Button headerSubscribeButton;
|
||||||
private View playlistCtrl;
|
private View playlistCtrl;
|
||||||
|
|
||||||
private LinearLayout headerPlayAllButton;
|
private LinearLayout headerPlayAllButton;
|
||||||
private LinearLayout headerPopupButton;
|
private LinearLayout headerPopupButton;
|
||||||
private LinearLayout headerBackgroundButton;
|
private LinearLayout headerBackgroundButton;
|
||||||
|
|
||||||
private MenuItem menuRssButton;
|
private MenuItem menuRssButton;
|
||||||
|
|
||||||
public static ChannelFragment getInstance(int serviceId, String url, String name) {
|
|
||||||
ChannelFragment instance = new ChannelFragment();
|
|
||||||
instance.setInitialData(serviceId, url, name);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public static ChannelFragment getInstance(final int serviceId, final String url,
|
||||||
|
final String name) {
|
||||||
|
ChannelFragment instance = new ChannelFragment();
|
||||||
|
instance.setInitialData(serviceId, url, name);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
if (activity != null
|
if (activity != null
|
||||||
&& useAsFrontPage
|
&& useAsFrontPage
|
||||||
|
@ -107,29 +105,40 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
subscriptionManager = new SubscriptionManager(activity);
|
subscriptionManager = new SubscriptionManager(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_channel, container, false);
|
return inflater.inflate(R.layout.fragment_channel, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (disposables != null) disposables.clear();
|
|
||||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
|
if (subscribeButtonMonitor != null) {
|
||||||
|
subscribeButtonMonitor.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Menu
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected View getListHeader() {
|
protected View getListHeader() {
|
||||||
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, itemsList, false);
|
headerRootLayout = activity.getLayoutInflater()
|
||||||
|
.inflate(R.layout.channel_header, itemsList, false);
|
||||||
headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image);
|
headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image);
|
||||||
headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view);
|
headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view);
|
||||||
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
|
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
|
||||||
|
@ -145,12 +154,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
return headerRootLayout;
|
return headerRootLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Menu
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (useAsFrontPage && supportActionBar != null) {
|
if (useAsFrontPage && supportActionBar != null) {
|
||||||
|
@ -158,8 +163,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
} else {
|
} else {
|
||||||
inflater.inflate(R.menu.menu_channel, menu);
|
inflater.inflate(R.menu.menu_channel, menu);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu +
|
if (DEBUG) {
|
||||||
"], inflater = [" + inflater + "]");
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
|
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||||
|
}
|
||||||
menuRssButton = menu.findItem(R.id.menu_item_rss);
|
menuRssButton = menu.findItem(R.id.menu_item_rss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,8 +179,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Channel Subscription
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_settings:
|
case R.id.action_settings:
|
||||||
NavigationHelper.openSettings(requireContext());
|
NavigationHelper.openSettings(requireContext());
|
||||||
|
@ -197,22 +208,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Channel Subscription
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
|
||||||
|
|
||||||
private void monitorSubscription(final ChannelInfo info) {
|
private void monitorSubscription(final ChannelInfo info) {
|
||||||
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
||||||
animateView(headerSubscribeButton, false, 100);
|
animateView(headerSubscribeButton, false, 100);
|
||||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
|
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
|
||||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
||||||
"Get subscription status",
|
"Get subscription status", 0);
|
||||||
0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final Observable<List<SubscriptionEntity>> observable = subscriptionManager.subscriptionTable()
|
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
|
||||||
|
.subscriptionTable()
|
||||||
.getSubscriptionFlowable(info.getServiceId(), info.getUrl())
|
.getSubscriptionFlowable(info.getServiceId(), info.getUrl())
|
||||||
.toObservable();
|
.toObservable();
|
||||||
|
|
||||||
|
@ -221,17 +226,19 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
.subscribe(getSubscribeUpdateMonitor(info), onError));
|
.subscribe(getSubscribeUpdateMonitor(info), onError));
|
||||||
|
|
||||||
disposables.add(observable
|
disposables.add(observable
|
||||||
// Some updates are very rapid (when calling the updateSubscription(info), for example)
|
// Some updates are very rapid
|
||||||
// so only update the UI for the latest emission ("sync" the subscribe button's state)
|
// (for example when calling the updateSubscription(info))
|
||||||
|
// so only update the UI for the latest emission
|
||||||
|
// ("sync" the subscribe button's state)
|
||||||
.debounce(100, TimeUnit.MILLISECONDS)
|
.debounce(100, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
|
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
|
||||||
updateSubscribeButton(!subscriptionEntities.isEmpty())
|
updateSubscribeButton(!subscriptionEntities.isEmpty()), onError));
|
||||||
, onError));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription, ChannelInfo info) {
|
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription,
|
||||||
|
final ChannelInfo info) {
|
||||||
return (@NonNull Object o) -> {
|
return (@NonNull Object o) -> {
|
||||||
subscriptionManager.insertSubscription(subscription, info);
|
subscriptionManager.insertSubscription(subscription, info);
|
||||||
return o;
|
return o;
|
||||||
|
@ -246,9 +253,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSubscription(final ChannelInfo info) {
|
private void updateSubscription(final ChannelInfo info) {
|
||||||
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
|
||||||
|
}
|
||||||
final Action onComplete = () -> {
|
final Action onComplete = () -> {
|
||||||
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Updated subscription: " + info.getUrl());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
|
@ -264,9 +275,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
.subscribe(onComplete, onError));
|
.subscribe(onComplete, onError));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) {
|
private Disposable monitorSubscribeButton(final Button subscribeButton,
|
||||||
|
final Function<Object, Object> action) {
|
||||||
final Consumer<Object> onNext = (@NonNull Object o) -> {
|
final Consumer<Object> onNext = (@NonNull Object o) -> {
|
||||||
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Changed subscription status to this channel!");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
|
@ -287,12 +301,18 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
|
|
||||||
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
|
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
|
||||||
return (List<SubscriptionEntity> subscriptionEntities) -> {
|
return (List<SubscriptionEntity> subscriptionEntities) -> {
|
||||||
if (DEBUG)
|
if (DEBUG) {
|
||||||
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
|
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: "
|
||||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
+ "subscriptionEntities = [" + subscriptionEntities + "]");
|
||||||
|
}
|
||||||
|
if (subscribeButtonMonitor != null) {
|
||||||
|
subscribeButtonMonitor.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
if (subscriptionEntities.isEmpty()) {
|
if (subscriptionEntities.isEmpty()) {
|
||||||
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "No subscription to this channel!");
|
||||||
|
}
|
||||||
SubscriptionEntity channel = new SubscriptionEntity();
|
SubscriptionEntity channel = new SubscriptionEntity();
|
||||||
channel.setServiceId(info.getServiceId());
|
channel.setServiceId(info.getServiceId());
|
||||||
channel.setUrl(info.getUrl());
|
channel.setUrl(info.getUrl());
|
||||||
|
@ -300,34 +320,45 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
info.getAvatarUrl(),
|
info.getAvatarUrl(),
|
||||||
info.getDescription(),
|
info.getDescription(),
|
||||||
info.getSubscriberCount());
|
info.getSubscriberCount());
|
||||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel, info));
|
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
|
||||||
|
mapOnSubscribe(channel, info));
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Found subscription to this channel!");
|
||||||
|
}
|
||||||
final SubscriptionEntity subscription = subscriptionEntities.get(0);
|
final SubscriptionEntity subscription = subscriptionEntities.get(0);
|
||||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
|
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
|
||||||
|
mapOnUnsubscribe(subscription));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSubscribeButton(boolean isSubscribed) {
|
private void updateSubscribeButton(final boolean isSubscribed) {
|
||||||
if (DEBUG) Log.d(TAG, "updateSubscribeButton() called with: isSubscribed = [" + isSubscribed + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "updateSubscribeButton() called with: "
|
||||||
|
+ "isSubscribed = [" + isSubscribed + "]");
|
||||||
|
}
|
||||||
|
|
||||||
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
||||||
int backgroundDuration = isButtonVisible ? 300 : 0;
|
int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||||
int textDuration = isButtonVisible ? 200 : 0;
|
int textDuration = isButtonVisible ? 200 : 0;
|
||||||
|
|
||||||
int subscribeBackground = ContextCompat.getColor(activity, R.color.subscribe_background_color);
|
int subscribeBackground = ContextCompat
|
||||||
|
.getColor(activity, R.color.subscribe_background_color);
|
||||||
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||||
int subscribedBackground = ContextCompat.getColor(activity, R.color.subscribed_background_color);
|
int subscribedBackground = ContextCompat
|
||||||
|
.getColor(activity, R.color.subscribed_background_color);
|
||||||
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
|
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
|
||||||
|
|
||||||
if (!isSubscribed) {
|
if (!isSubscribed) {
|
||||||
headerSubscribeButton.setText(R.string.subscribe_button_title);
|
headerSubscribeButton.setText(R.string.subscribe_button_title);
|
||||||
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground, subscribeBackground);
|
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground,
|
||||||
|
subscribeBackground);
|
||||||
animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText);
|
animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText);
|
||||||
} else {
|
} else {
|
||||||
headerSubscribeButton.setText(R.string.subscribed_button_title);
|
headerSubscribeButton.setText(R.string.subscribed_button_title);
|
||||||
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground, subscribedBackground);
|
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground,
|
||||||
|
subscribedBackground);
|
||||||
animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText);
|
animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +375,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<ChannelInfo> loadResult(boolean forceLoad) {
|
protected Single<ChannelInfo> loadResult(final boolean forceLoad) {
|
||||||
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
|
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,47 +387,55 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
|
|
||||||
imageLoader.cancelDisplayTask(headerChannelBanner);
|
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
|
||||||
imageLoader.cancelDisplayTask(headerAvatarView);
|
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
|
||||||
animateView(headerSubscribeButton, false, 100);
|
animateView(headerSubscribeButton, false, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull ChannelInfo result) {
|
public void handleResult(@NonNull final ChannelInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
headerRootLayout.setVisibility(View.VISIBLE);
|
headerRootLayout.setVisibility(View.VISIBLE);
|
||||||
imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner,
|
IMAGE_LOADER.displayImage(result.getBannerUrl(), headerChannelBanner,
|
||||||
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
|
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
|
||||||
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
|
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
|
||||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
|
|
||||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||||
if (result.getSubscriberCount() >= 0) {
|
if (result.getSubscriberCount() >= 0) {
|
||||||
headerSubscribersTextView.setText(Localization.shortSubscriberCount(activity, result.getSubscriberCount()));
|
headerSubscribersTextView.setText(Localization
|
||||||
|
.shortSubscriberCount(activity, result.getSubscriberCount()));
|
||||||
} else {
|
} else {
|
||||||
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
if (menuRssButton != null) {
|
||||||
|
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||||
|
}
|
||||||
|
|
||||||
playlistCtrl.setVisibility(View.VISIBLE);
|
playlistCtrl.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL,
|
||||||
|
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
disposables.clear();
|
||||||
|
}
|
||||||
|
if (subscribeButtonMonitor != null) {
|
||||||
|
subscribeButtonMonitor.dispose();
|
||||||
|
}
|
||||||
updateSubscription(result);
|
updateSubscription(result);
|
||||||
monitorSubscription(result);
|
monitorSubscription(result);
|
||||||
|
|
||||||
headerPlayAllButton.setOnClickListener(
|
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
|
||||||
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||||
headerPopupButton.setOnClickListener(
|
headerPopupButton.setOnClickListener(view -> NavigationHelper
|
||||||
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||||
headerBackgroundButton.setOnClickListener(
|
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
|
||||||
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
|
.playOnBackgroundPlayer(activity, getPlayQueue(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayQueue getPlayQueue() {
|
private PlayQueue getPlayQueue() {
|
||||||
|
@ -410,17 +449,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
streamItems.add((StreamInfoItem) i);
|
streamItems.add((StreamInfoItem) i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ChannelPlayQueue(
|
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
||||||
currentInfo.getServiceId(),
|
currentInfo.getNextPageUrl(), streamItems, index);
|
||||||
currentInfo.getUrl(),
|
|
||||||
currentInfo.getNextPageUrl(),
|
|
||||||
streamItems,
|
|
||||||
index
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
|
@ -437,8 +471,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
if (super.onError(exception)) return true;
|
if (super.onError(exception)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int errorId = exception instanceof ExtractionException
|
int errorId = exception instanceof ExtractionException
|
||||||
? R.string.parsing_error : R.string.general_error;
|
? R.string.parsing_error : R.string.general_error;
|
||||||
|
@ -454,8 +490,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitle(String title) {
|
public void setTitle(final String title) {
|
||||||
super.setTitle(title);
|
super.setTitle(title);
|
||||||
if (!useAsFrontPage) headerTitleView.setText(title);
|
if (!useAsFrontPage) {
|
||||||
|
headerTitleView.setText(title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,15 @@ package org.schabi.newpipe.fragments.list.comments;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
@ -23,17 +24,12 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
|
||||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
|
|
||||||
private CompositeDisposable disposables = new CompositeDisposable();
|
private CompositeDisposable disposables = new CompositeDisposable();
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Views
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private boolean mIsVisibleToUser = false;
|
private boolean mIsVisibleToUser = false;
|
||||||
|
|
||||||
public static CommentsFragment getInstance(int serviceId, String url, String name) {
|
public static CommentsFragment getInstance(final int serviceId, final String url,
|
||||||
|
final String name) {
|
||||||
CommentsFragment instance = new CommentsFragment();
|
CommentsFragment instance = new CommentsFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -44,28 +40,31 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
mIsVisibleToUser = isVisibleToUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_comments, container, false);
|
return inflater.inflate(R.layout.fragment_comments, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Load and handle
|
// Load and handle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -76,7 +75,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
|
protected Single<CommentsInfo> loadResult(final boolean forceLoad) {
|
||||||
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
|
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,27 +89,28 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull CommentsInfo result) {
|
public void handleResult(@NonNull final CommentsInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
AnimationUtils.slideUp(getView(),120, 150, 0.06f);
|
AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
||||||
|
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(),
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
||||||
UserAction.REQUESTED_COMMENTS,
|
NewPipe.getNameOfService(serviceId), "Get next page of: " + url,
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
R.string.general_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,14 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
if (super.onError(exception)) return true;
|
if (super.onError(exception)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
hideLoading();
|
hideLoading();
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS,
|
||||||
|
NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,14 +136,10 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitle(String title) {
|
public void setTitle(final String title) { }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isGridLayout() {
|
protected boolean isGridLayout() {
|
||||||
|
|
|
@ -10,9 +10,8 @@ import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
|
||||||
public class DefaultKioskFragment extends KioskFragment {
|
public class DefaultKioskFragment extends KioskFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (serviceId < 0) {
|
if (serviceId < 0) {
|
||||||
|
@ -25,7 +24,9 @@ public class DefaultKioskFragment extends KioskFragment {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) {
|
if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) {
|
||||||
if (currentWorker != null) currentWorker.dispose();
|
if (currentWorker != null) {
|
||||||
|
currentWorker.dispose();
|
||||||
|
}
|
||||||
updateSelectedDefaultKiosk();
|
updateSelectedDefaultKiosk();
|
||||||
reloadContent();
|
reloadContent();
|
||||||
}
|
}
|
||||||
|
@ -45,7 +46,8 @@ public class DefaultKioskFragment extends KioskFragment {
|
||||||
currentInfo = null;
|
currentInfo = null;
|
||||||
currentNextPageUrl = null;
|
currentNextPageUrl = null;
|
||||||
} catch (ExtractionException e) {
|
} catch (ExtractionException e) {
|
||||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none", "Loading default kiosk from selected service", 0);
|
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
||||||
|
"Loading default kiosk from selected service", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package org.schabi.newpipe.fragments.list.kiosk;
|
package org.schabi.newpipe.fragments.list.kiosk;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
@ -33,45 +32,45 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 23.09.17.
|
* Created by Christian Schabesberger on 23.09.17.
|
||||||
*
|
* <p>
|
||||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||||
* KioskFragment.java is part of NewPipe.
|
* KioskFragment.java is part of NewPipe.
|
||||||
*
|
* </p>
|
||||||
|
* <p>
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
* </p>
|
||||||
|
* <p>
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
* </p>
|
||||||
|
* <p>
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected String kioskId = "";
|
String kioskId = "";
|
||||||
protected String kioskTranslatedName;
|
String kioskTranslatedName;
|
||||||
@State
|
@State
|
||||||
protected ContentCountry contentCountry;
|
ContentCountry contentCountry;
|
||||||
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public static KioskFragment getInstance(int serviceId)
|
public static KioskFragment getInstance(final int serviceId) throws ExtractionException {
|
||||||
throws ExtractionException {
|
|
||||||
return getInstance(serviceId, NewPipe.getService(serviceId)
|
return getInstance(serviceId, NewPipe.getService(serviceId)
|
||||||
.getKioskList()
|
.getKioskList().getDefaultKioskId());
|
||||||
.getDefaultKioskId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KioskFragment getInstance(int serviceId, String kioskId)
|
public static KioskFragment getInstance(final int serviceId, final String kioskId)
|
||||||
throws ExtractionException {
|
throws ExtractionException {
|
||||||
KioskFragment instance = new KioskFragment();
|
KioskFragment instance = new KioskFragment();
|
||||||
StreamingService service = NewPipe.getService(serviceId);
|
StreamingService service = NewPipe.getService(serviceId);
|
||||||
|
@ -88,7 +87,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity);
|
kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity);
|
||||||
|
@ -97,9 +96,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
if(useAsFrontPage && isVisibleToUser && activity != null) {
|
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
||||||
try {
|
try {
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -111,7 +110,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_kiosk, container, false);
|
return inflater.inflate(R.layout.fragment_kiosk, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar != null && useAsFrontPage) {
|
if (supportActionBar != null && useAsFrontPage) {
|
||||||
|
@ -142,18 +143,14 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Single<KioskInfo> loadResult(boolean forceReload) {
|
public Single<KioskInfo> loadResult(final boolean forceReload) {
|
||||||
contentCountry = Localization.getPreferredContentCountry(requireContext());
|
contentCountry = Localization.getPreferredContentCountry(requireContext());
|
||||||
return ExtractorHelper.getKioskInfo(serviceId,
|
return ExtractorHelper.getKioskInfo(serviceId, url, forceReload);
|
||||||
url,
|
|
||||||
forceReload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||||
return ExtractorHelper.getMoreKioskItems(serviceId,
|
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
|
||||||
url,
|
|
||||||
currentNextPageUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -181,13 +178,13 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(),
|
showSnackBarError(result.getErrors(),
|
||||||
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
|
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId),
|
||||||
, "Get next page of: " + url, 0);
|
"Get next page of: " + url, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,6 @@ package org.schabi.newpipe.fragments.list.playlist;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -17,6 +14,10 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
|
@ -57,7 +58,6 @@ import io.reactivex.disposables.Disposables;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
|
|
||||||
private CompositeDisposable disposables;
|
private CompositeDisposable disposables;
|
||||||
private Subscription bookmarkReactor;
|
private Subscription bookmarkReactor;
|
||||||
private AtomicBoolean isBookmarkButtonReady;
|
private AtomicBoolean isBookmarkButtonReady;
|
||||||
|
@ -82,7 +82,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
|
|
||||||
private MenuItem playlistBookmarkButton;
|
private MenuItem playlistBookmarkButton;
|
||||||
|
|
||||||
public static PlaylistFragment getInstance(int serviceId, String url, String name) {
|
public static PlaylistFragment getInstance(final int serviceId, final String url,
|
||||||
|
final String name) {
|
||||||
PlaylistFragment instance = new PlaylistFragment();
|
PlaylistFragment instance = new PlaylistFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -93,17 +94,18 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
isBookmarkButtonReady = new AtomicBoolean(false);
|
isBookmarkButtonReady = new AtomicBoolean(false);
|
||||||
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase.getInstance(
|
remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase
|
||||||
requireContext()));
|
.getInstance(requireContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +114,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected View getListHeader() {
|
protected View getListHeader() {
|
||||||
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_header, itemsList, false);
|
headerRootLayout = activity.getLayoutInflater()
|
||||||
|
.inflate(R.layout.playlist_header, itemsList, false);
|
||||||
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
|
headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
|
||||||
headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout);
|
headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout);
|
||||||
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
|
headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
|
||||||
|
@ -129,21 +132,23 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
infoListAdapter.useMiniItemVariants(true);
|
infoListAdapter.setUseMiniVariant(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayQueue getPlayQueueStartingAt(StreamInfoItem infoItem) {
|
private PlayQueue getPlayQueueStartingAt(final StreamInfoItem infoItem) {
|
||||||
return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0));
|
return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void showStreamDialog(StreamInfoItem item) {
|
protected void showStreamDialog(final StreamInfoItem item) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (context == null || context.getResources() == null || activity == null) return;
|
if (context == null || context.getResources() == null || activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
|
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||||
StreamDialogEntry.setEnabledEntries(
|
StreamDialogEntry.setEnabledEntries(
|
||||||
|
@ -160,21 +165,25 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
StreamDialogEntry.append_playlist,
|
StreamDialogEntry.append_playlist,
|
||||||
StreamDialogEntry.share);
|
StreamDialogEntry.share);
|
||||||
|
|
||||||
StreamDialogEntry.start_here_on_popup.setCustomAction(
|
StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItem) ->
|
||||||
(fragment, infoItem) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(infoItem), true));
|
NavigationHelper.playOnPopupPlayer(context,
|
||||||
|
getPlayQueueStartingAt(infoItem), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamDialogEntry.start_here_on_background.setCustomAction(
|
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) ->
|
||||||
(fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(infoItem), true));
|
NavigationHelper.playOnBackgroundPlayer(context,
|
||||||
|
getPlayQueueStartingAt(infoItem), true));
|
||||||
|
|
||||||
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
|
||||||
StreamDialogEntry.clickOn(which, this, item)).show();
|
(dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu +
|
if (DEBUG) {
|
||||||
"], inflater = [" + inflater + "]");
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
|
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||||
|
}
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
inflater.inflate(R.menu.menu_playlist, menu);
|
inflater.inflate(R.menu.menu_playlist, menu);
|
||||||
|
|
||||||
|
@ -185,10 +194,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
if (isBookmarkButtonReady != null) isBookmarkButtonReady.set(false);
|
if (isBookmarkButtonReady != null) {
|
||||||
|
isBookmarkButtonReady.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
if (bookmarkReactor != null) bookmarkReactor.cancel();
|
disposables.clear();
|
||||||
|
}
|
||||||
|
if (bookmarkReactor != null) {
|
||||||
|
bookmarkReactor.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
bookmarkReactor = null;
|
bookmarkReactor = null;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +212,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
if (disposables != null) disposables.dispose();
|
if (disposables != null) {
|
||||||
|
disposables.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
disposables = null;
|
disposables = null;
|
||||||
remotePlaylistManager = null;
|
remotePlaylistManager = null;
|
||||||
|
@ -215,12 +232,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Single<PlaylistInfo> loadResult(boolean forceLoad) {
|
protected Single<PlaylistInfo> loadResult(final boolean forceLoad) {
|
||||||
return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad);
|
return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_settings:
|
case R.id.action_settings:
|
||||||
NavigationHelper.openSettings(requireContext());
|
NavigationHelper.openSettings(requireContext());
|
||||||
|
@ -251,7 +268,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
animateView(headerRootLayout, false, 200);
|
animateView(headerRootLayout, false, 200);
|
||||||
animateView(itemsList, false, 100);
|
animateView(itemsList, false, 100);
|
||||||
|
|
||||||
imageLoader.cancelDisplayTask(headerUploaderAvatar);
|
IMAGE_LOADER.cancelDisplayTask(headerUploaderAvatar);
|
||||||
animateView(headerUploaderLayout, false, 200);
|
animateView(headerUploaderLayout, false, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +279,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
animateView(headerRootLayout, true, 100);
|
animateView(headerRootLayout, true, 100);
|
||||||
animateView(headerUploaderLayout, true, 300);
|
animateView(headerUploaderLayout, true, 300);
|
||||||
headerUploaderLayout.setOnClickListener(null);
|
headerUploaderLayout.setOnClickListener(null);
|
||||||
if (!TextUtils.isEmpty(result.getUploaderName())) { // If we have an uploader : Put them into the ui
|
// If we have an uploader put them into the UI
|
||||||
|
if (!TextUtils.isEmpty(result.getUploaderName())) {
|
||||||
headerUploaderName.setText(result.getUploaderName());
|
headerUploaderName.setText(result.getUploaderName());
|
||||||
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
||||||
headerUploaderLayout.setOnClickListener(v -> {
|
headerUploaderLayout.setOnClickListener(v -> {
|
||||||
|
@ -276,19 +294,20 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else { // Else : say we have no uploader
|
} else { // Else say we have no uploader
|
||||||
headerUploaderName.setText(R.string.playlist_no_uploader);
|
headerUploaderName.setText(R.string.playlist_no_uploader);
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistCtrl.setVisibility(View.VISIBLE);
|
playlistCtrl.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
|
IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
|
||||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos,
|
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos,
|
||||||
(int) result.getStreamCount(), (int) result.getStreamCount()));
|
(int) result.getStreamCount(), (int) result.getStreamCount()));
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
||||||
|
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
remotePlaylistManager.getPlaylist(result)
|
remotePlaylistManager.getPlaylist(result)
|
||||||
|
@ -321,8 +340,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
|
|
||||||
private PlayQueue getPlayQueue(final int index) {
|
private PlayQueue getPlayQueue(final int index) {
|
||||||
final List<StreamInfoItem> infoItems = new ArrayList<>();
|
final List<StreamInfoItem> infoItems = new ArrayList<>();
|
||||||
for(InfoItem i : infoListAdapter.getItemsList()) {
|
for (InfoItem i : infoListAdapter.getItemsList()) {
|
||||||
if(i instanceof StreamInfoItem) {
|
if (i instanceof StreamInfoItem) {
|
||||||
infoItems.add((StreamInfoItem) i);
|
infoItems.add((StreamInfoItem) i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,12 +355,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId)
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
||||||
, "Get next page of: " + url, 0);
|
NewPipe.getNameOfService(serviceId), "Get next page of: " + url, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,15 +369,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
if (super.onError(exception)) return true;
|
if (super.onError(exception)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
|
int errorId = exception instanceof ExtractionException
|
||||||
onUnrecoverableError(exception,
|
? R.string.parsing_error : R.string.general_error;
|
||||||
UserAction.REQUESTED_PLAYLIST,
|
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
||||||
NewPipe.getNameOfService(serviceId),
|
NewPipe.getNameOfService(serviceId), url, errorId);
|
||||||
url,
|
|
||||||
errorId);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,13 +385,18 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private Flowable<Integer> getUpdateProcessor(@NonNull List<PlaylistRemoteEntity> playlists,
|
private Flowable<Integer> getUpdateProcessor(
|
||||||
@NonNull PlaylistInfo result) {
|
@NonNull final List<PlaylistRemoteEntity> playlists,
|
||||||
|
@NonNull final PlaylistInfo result) {
|
||||||
final Flowable<Integer> noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1);
|
final Flowable<Integer> noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1);
|
||||||
if (playlists.isEmpty()) return noItemToUpdate;
|
if (playlists.isEmpty()) {
|
||||||
|
return noItemToUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
final PlaylistRemoteEntity playlistEntity = playlists.get(0);
|
final PlaylistRemoteEntity playlistRemoteEntity = playlists.get(0);
|
||||||
if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate;
|
if (playlistRemoteEntity.isIdenticalTo(result)) {
|
||||||
|
return noItemToUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable();
|
return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable();
|
||||||
}
|
}
|
||||||
|
@ -380,56 +404,59 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
private Subscriber<List<PlaylistRemoteEntity>> getPlaylistBookmarkSubscriber() {
|
private Subscriber<List<PlaylistRemoteEntity>> getPlaylistBookmarkSubscriber() {
|
||||||
return new Subscriber<List<PlaylistRemoteEntity>>() {
|
return new Subscriber<List<PlaylistRemoteEntity>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Subscription s) {
|
public void onSubscribe(final Subscription s) {
|
||||||
if (bookmarkReactor != null) bookmarkReactor.cancel();
|
if (bookmarkReactor != null) {
|
||||||
|
bookmarkReactor.cancel();
|
||||||
|
}
|
||||||
bookmarkReactor = s;
|
bookmarkReactor = s;
|
||||||
bookmarkReactor.request(1);
|
bookmarkReactor.request(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(List<PlaylistRemoteEntity> playlist) {
|
public void onNext(final List<PlaylistRemoteEntity> playlist) {
|
||||||
playlistEntity = playlist.isEmpty() ? null : playlist.get(0);
|
playlistEntity = playlist.isEmpty() ? null : playlist.get(0);
|
||||||
|
|
||||||
updateBookmarkButtons();
|
updateBookmarkButtons();
|
||||||
isBookmarkButtonReady.set(true);
|
isBookmarkButtonReady.set(true);
|
||||||
|
|
||||||
if (bookmarkReactor != null) bookmarkReactor.request(1);
|
if (bookmarkReactor != null) {
|
||||||
|
bookmarkReactor.request(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable t) {
|
public void onError(final Throwable t) {
|
||||||
PlaylistFragment.this.onError(t);
|
PlaylistFragment.this.onError(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() { }
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitle(String title) {
|
public void setTitle(final String title) {
|
||||||
super.setTitle(title);
|
super.setTitle(title);
|
||||||
headerTitleView.setText(title);
|
headerTitleView.setText(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBookmarkClicked() {
|
private void onBookmarkClicked() {
|
||||||
if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get() ||
|
if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get()
|
||||||
remotePlaylistManager == null)
|
|| remotePlaylistManager == null) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Disposable action;
|
final Disposable action;
|
||||||
|
|
||||||
if (currentInfo != null && playlistEntity == null) {
|
if (currentInfo != null && playlistEntity == null) {
|
||||||
action = remotePlaylistManager.onBookmark(currentInfo)
|
action = remotePlaylistManager.onBookmark(currentInfo)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> {/* Do nothing */}, this::onError);
|
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
|
||||||
} else if (playlistEntity != null) {
|
} else if (playlistEntity != null) {
|
||||||
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
|
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doFinally(() -> playlistEntity = null)
|
.doFinally(() -> playlistEntity = null)
|
||||||
.subscribe(ignored -> {/* Do nothing */}, this::onError);
|
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
|
||||||
} else {
|
} else {
|
||||||
action = Disposables.empty();
|
action = Disposables.empty();
|
||||||
}
|
}
|
||||||
|
@ -438,13 +465,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBookmarkButtons() {
|
private void updateBookmarkButtons() {
|
||||||
if (playlistBookmarkButton == null || activity == null) return;
|
if (playlistBookmarkButton == null || activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final int iconAttr = playlistEntity == null ?
|
final int iconAttr = playlistEntity == null
|
||||||
R.attr.ic_playlist_add : R.attr.ic_playlist_check;
|
? R.attr.ic_playlist_add : R.attr.ic_playlist_check;
|
||||||
|
|
||||||
final int titleRes = playlistEntity == null ?
|
final int titleRes = playlistEntity == null
|
||||||
R.string.bookmark_playlist : R.string.unbookmark_playlist;
|
? R.string.bookmark_playlist : R.string.unbookmark_playlist;
|
||||||
|
|
||||||
playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
|
playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
|
||||||
playlistBookmarkButton.setTitle(titleRes);
|
playlistBookmarkButton.setTitle(titleRes);
|
||||||
|
|
|
@ -6,13 +6,6 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.appcompat.widget.TooltipCompat;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
@ -30,6 +23,14 @@ import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.TooltipCompat;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
import org.schabi.newpipe.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
|
@ -40,7 +41,6 @@ import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.util.FireTvUtils;
|
|
||||||
import org.schabi.newpipe.fragments.BackPressable;
|
import org.schabi.newpipe.fragments.BackPressable;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
@ -49,6 +49,7 @@ import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
import org.schabi.newpipe.util.FireTvUtils;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
|
||||||
|
@ -77,8 +78,7 @@ import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovement
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public class SearchFragment
|
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
|
||||||
extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
|
|
||||||
implements BackPressable {
|
implements BackPressable {
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -92,20 +92,19 @@ public class SearchFragment
|
||||||
private static final int THRESHOLD_NETWORK_SUGGESTION = 1;
|
private static final int THRESHOLD_NETWORK_SUGGESTION = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much time have to pass without emitting a item (i.e. the user stop typing) to fetch/show the suggestions, in milliseconds.
|
* How much time have to pass without emitting a item (i.e. the user stop typing)
|
||||||
|
* to fetch/show the suggestions, in milliseconds.
|
||||||
*/
|
*/
|
||||||
private static final int SUGGESTIONS_DEBOUNCE = 120; //ms
|
private static final int SUGGESTIONS_DEBOUNCE = 120; //ms
|
||||||
|
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
|
||||||
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
@State
|
@State
|
||||||
protected int filterItemCheckedId = -1;
|
protected int filterItemCheckedId = -1;
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||||
|
|
||||||
// this three represet the current search query
|
// this three represet the current search query
|
||||||
@State
|
@State
|
||||||
protected String searchString;
|
protected String searchString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No content filter should add like contentfilter = all
|
* No content filter should add like contentfilter = all
|
||||||
* be aware of this when implementing an extractor.
|
* be aware of this when implementing an extractor.
|
||||||
|
@ -114,26 +113,19 @@ public class SearchFragment
|
||||||
protected String[] contentFilter = new String[0];
|
protected String[] contentFilter = new String[0];
|
||||||
@State
|
@State
|
||||||
protected String sortFilter;
|
protected String sortFilter;
|
||||||
|
|
||||||
// these represtent the last search
|
// these represtent the last search
|
||||||
@State
|
@State
|
||||||
protected String lastSearchedString;
|
protected String lastSearchedString;
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected boolean wasSearchFocused = false;
|
protected boolean wasSearchFocused = false;
|
||||||
|
|
||||||
private Map<Integer, String> menuItemToFilterName;
|
private Map<Integer, String> menuItemToFilterName;
|
||||||
private StreamingService service;
|
private StreamingService service;
|
||||||
private String currentPageUrl;
|
private String currentPageUrl;
|
||||||
private String nextPageUrl;
|
private String nextPageUrl;
|
||||||
private String contentCountry;
|
private String contentCountry;
|
||||||
private boolean isSuggestionsEnabled = true;
|
private boolean isSuggestionsEnabled = true;
|
||||||
|
|
||||||
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
|
|
||||||
private Disposable searchDisposable;
|
private Disposable searchDisposable;
|
||||||
private Disposable suggestionDisposable;
|
private Disposable suggestionDisposable;
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
|
||||||
|
|
||||||
private SuggestionListAdapter suggestionListAdapter;
|
private SuggestionListAdapter suggestionListAdapter;
|
||||||
private HistoryRecordManager historyRecordManager;
|
private HistoryRecordManager historyRecordManager;
|
||||||
|
|
||||||
|
@ -149,8 +141,9 @@ public class SearchFragment
|
||||||
private RecyclerView suggestionsRecyclerView;
|
private RecyclerView suggestionsRecyclerView;
|
||||||
|
|
||||||
/*////////////////////////////////////////////////////////////////////////*/
|
/*////////////////////////////////////////////////////////////////////////*/
|
||||||
|
private TextWatcher textWatcher;
|
||||||
|
|
||||||
public static SearchFragment getInstance(int serviceId, String searchString) {
|
public static SearchFragment getInstance(final int serviceId, final String searchString) {
|
||||||
SearchFragment searchFragment = new SearchFragment();
|
SearchFragment searchFragment = new SearchFragment();
|
||||||
searchFragment.setQuery(serviceId, searchString, new String[0], "");
|
searchFragment.setQuery(serviceId, searchString, new String[0], "");
|
||||||
|
|
||||||
|
@ -161,6 +154,10 @@ public class SearchFragment
|
||||||
return searchFragment;
|
return searchFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment's LifeCycle
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set wasLoading to true so when the fragment onResume is called, the initial search is done.
|
* Set wasLoading to true so when the fragment onResume is called, the initial search is done.
|
||||||
*/
|
*/
|
||||||
|
@ -168,38 +165,38 @@ public class SearchFragment
|
||||||
wasLoading.set(true);
|
wasLoading.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Fragment's LifeCycle
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
|
|
||||||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
boolean isSearchHistoryEnabled = preferences
|
||||||
|
.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||||
|
|
||||||
historyRecordManager = new HistoryRecordManager(context);
|
historyRecordManager = new HistoryRecordManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
isSuggestionsEnabled = preferences
|
||||||
contentCountry = preferences.getString(getString(R.string.content_country_key), getString(R.string.default_localization_key));
|
.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
||||||
|
contentCountry = preferences.getString(getString(R.string.content_country_key),
|
||||||
|
getString(R.string.default_localization_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_search, container, false);
|
return inflater.inflate(R.layout.fragment_search, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View rootView, Bundle savedInstanceState) {
|
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.onViewCreated(rootView, savedInstanceState);
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
showSearchOnStart();
|
showSearchOnStart();
|
||||||
initSearchListeners();
|
initSearchListeners();
|
||||||
|
@ -211,15 +208,23 @@ public class SearchFragment
|
||||||
|
|
||||||
wasSearchFocused = searchEditText.hasFocus();
|
wasSearchFocused = searchEditText.hasFocus();
|
||||||
|
|
||||||
if (searchDisposable != null) searchDisposable.dispose();
|
if (searchDisposable != null) {
|
||||||
if (suggestionDisposable != null) suggestionDisposable.dispose();
|
searchDisposable.dispose();
|
||||||
if (disposables != null) disposables.clear();
|
}
|
||||||
|
if (suggestionDisposable != null) {
|
||||||
|
suggestionDisposable.dispose();
|
||||||
|
}
|
||||||
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
if (DEBUG) Log.d(TAG, "onResume() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onResume() called");
|
||||||
|
}
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -245,7 +250,9 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
|
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||||
|
initSuggestionObserver();
|
||||||
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
|
@ -259,7 +266,9 @@ public class SearchFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
if (DEBUG) Log.d(TAG, "onDestroyView() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onDestroyView() called");
|
||||||
|
}
|
||||||
unsetSearchListeners();
|
unsetSearchListeners();
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
@ -267,19 +276,31 @@ public class SearchFragment
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (searchDisposable != null) searchDisposable.dispose();
|
if (searchDisposable != null) {
|
||||||
if (suggestionDisposable != null) suggestionDisposable.dispose();
|
searchDisposable.dispose();
|
||||||
if (disposables != null) disposables.clear();
|
}
|
||||||
|
if (suggestionDisposable != null) {
|
||||||
|
suggestionDisposable.dispose();
|
||||||
|
}
|
||||||
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Init
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case ReCaptchaActivity.RECAPTCHA_REQUEST:
|
case ReCaptchaActivity.RECAPTCHA_REQUEST:
|
||||||
if (resultCode == Activity.RESULT_OK
|
if (resultCode == Activity.RESULT_OK
|
||||||
&& !TextUtils.isEmpty(searchString)) {
|
&& !TextUtils.isEmpty(searchString)) {
|
||||||
search(searchString, contentFilter, sortFilter);
|
search(searchString, contentFilter, sortFilter);
|
||||||
} else Log.e(TAG, "ReCaptcha failed");
|
} else {
|
||||||
|
Log.e(TAG, "ReCaptcha failed");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -289,29 +310,31 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// State Saving
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
|
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
|
||||||
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
|
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
|
||||||
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
|
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
|
||||||
new ItemTouchHelper(new ItemTouchHelper.Callback() {
|
new ItemTouchHelper(new ItemTouchHelper.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
public int getMovementFlags(@NonNull final RecyclerView recyclerView,
|
||||||
|
@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||||
return getSuggestionMovementFlags(recyclerView, viewHolder);
|
return getSuggestionMovementFlags(recyclerView, viewHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
|
public boolean onMove(@NonNull final RecyclerView recyclerView,
|
||||||
@NonNull RecyclerView.ViewHolder viewHolder1) {
|
@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||||
|
@NonNull final RecyclerView.ViewHolder viewHolder1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int i) {
|
||||||
onSuggestionItemSwiped(viewHolder, i);
|
onSuggestionItemSwiped(viewHolder, i);
|
||||||
}
|
}
|
||||||
}).attachToRecyclerView(suggestionsRecyclerView);
|
}).attachToRecyclerView(suggestionsRecyclerView);
|
||||||
|
@ -321,26 +344,26 @@ public class SearchFragment
|
||||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// State Saving
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(Queue<Object> objectsToSave) {
|
public void writeTo(final Queue<Object> objectsToSave) {
|
||||||
super.writeTo(objectsToSave);
|
super.writeTo(objectsToSave);
|
||||||
objectsToSave.add(currentPageUrl);
|
objectsToSave.add(currentPageUrl);
|
||||||
objectsToSave.add(nextPageUrl);
|
objectsToSave.add(nextPageUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||||
super.readFrom(savedObjects);
|
super.readFrom(savedObjects);
|
||||||
currentPageUrl = (String) savedObjects.poll();
|
currentPageUrl = (String) savedObjects.poll();
|
||||||
nextPageUrl = (String) savedObjects.poll();
|
nextPageUrl = (String) savedObjects.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Init's
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle bundle) {
|
public void onSaveInstanceState(final Bundle bundle) {
|
||||||
searchString = searchEditText != null
|
searchString = searchEditText != null
|
||||||
? searchEditText.getText().toString()
|
? searchEditText.getText().toString()
|
||||||
: searchString;
|
: searchString;
|
||||||
|
@ -348,7 +371,7 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init's
|
// Menu
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -367,12 +390,8 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Menu
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
|
||||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
|
@ -386,13 +405,13 @@ public class SearchFragment
|
||||||
int itemId = 0;
|
int itemId = 0;
|
||||||
boolean isFirstItem = true;
|
boolean isFirstItem = true;
|
||||||
final Context c = getContext();
|
final Context c = getContext();
|
||||||
for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||||
menuItemToFilterName.put(itemId, filter);
|
menuItemToFilterName.put(itemId, filter);
|
||||||
MenuItem item = menu.add(1,
|
MenuItem item = menu.add(1,
|
||||||
itemId++,
|
itemId++,
|
||||||
0,
|
0,
|
||||||
ServiceHelper.getTranslatedFilterString(filter, c));
|
ServiceHelper.getTranslatedFilterString(filter, c));
|
||||||
if(isFirstItem) {
|
if (isFirstItem) {
|
||||||
item.setChecked(true);
|
item.setChecked(true);
|
||||||
isFirstItem = false;
|
isFirstItem = false;
|
||||||
}
|
}
|
||||||
|
@ -403,35 +422,36 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
|
List<String> cf = new ArrayList<>(1);
|
||||||
List<String> contentFilter = new ArrayList<>(1);
|
cf.add(menuItemToFilterName.get(item.getItemId()));
|
||||||
contentFilter.add(menuItemToFilterName.get(item.getItemId()));
|
changeContentFilter(item, cf);
|
||||||
changeContentFilter(item, contentFilter);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreFilterChecked(Menu menu, int itemId) {
|
|
||||||
if (itemId != -1) {
|
|
||||||
MenuItem item = menu.findItem(itemId);
|
|
||||||
if (item == null) return;
|
|
||||||
|
|
||||||
item.setChecked(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Search
|
// Search
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private TextWatcher textWatcher;
|
private void restoreFilterChecked(final Menu menu, final int itemId) {
|
||||||
|
if (itemId != -1) {
|
||||||
|
MenuItem item = menu.findItem(itemId);
|
||||||
|
if (item == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void showSearchOnStart() {
|
private void showSearchOnStart() {
|
||||||
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → "
|
if (DEBUG) {
|
||||||
+ searchString
|
Log.d(TAG, "showSearchOnStart() called, searchQuery → "
|
||||||
+ ", lastSearchedQuery → "
|
+ searchString
|
||||||
+ lastSearchedString);
|
+ ", lastSearchedQuery → "
|
||||||
|
+ lastSearchedString);
|
||||||
|
}
|
||||||
searchEditText.setText(searchString);
|
searchEditText.setText(searchString);
|
||||||
|
|
||||||
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||||
|
@ -451,9 +471,13 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSearchListeners() {
|
private void initSearchListeners() {
|
||||||
if (DEBUG) Log.d(TAG, "initSearchListeners() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "initSearchListeners() called");
|
||||||
|
}
|
||||||
searchClear.setOnClickListener(v -> {
|
searchClear.setOnClickListener(v -> {
|
||||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||||
|
}
|
||||||
if (TextUtils.isEmpty(searchEditText.getText())) {
|
if (TextUtils.isEmpty(searchEditText.getText())) {
|
||||||
NavigationHelper.gotoMainFragment(getFragmentManager());
|
NavigationHelper.gotoMainFragment(getFragmentManager());
|
||||||
return;
|
return;
|
||||||
|
@ -467,53 +491,63 @@ public class SearchFragment
|
||||||
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
|
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
|
||||||
|
|
||||||
searchEditText.setOnClickListener(v -> {
|
searchEditText.setOnClickListener(v -> {
|
||||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||||
|
}
|
||||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
if(FireTvUtils.isFireTv()){
|
if (FireTvUtils.isFireTv()) {
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
|
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
|
||||||
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
if (DEBUG) {
|
||||||
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
Log.d(TAG, "onFocusChange() called with: "
|
||||||
|
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
||||||
|
}
|
||||||
|
if (isSuggestionsEnabled && hasFocus
|
||||||
|
&& errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
|
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuggestionItemSelected(SuggestionItem item) {
|
public void onSuggestionItemSelected(final SuggestionItem item) {
|
||||||
search(item.query, new String[0], "");
|
search(item.query, new String[0], "");
|
||||||
searchEditText.setText(item.query);
|
searchEditText.setText(item.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuggestionItemInserted(SuggestionItem item) {
|
public void onSuggestionItemInserted(final SuggestionItem item) {
|
||||||
searchEditText.setText(item.query);
|
searchEditText.setText(item.query);
|
||||||
searchEditText.setSelection(searchEditText.getText().length());
|
searchEditText.setSelection(searchEditText.getText().length());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuggestionItemLongClick(SuggestionItem item) {
|
public void onSuggestionItemLongClick(final SuggestionItem item) {
|
||||||
if (item.fromHistory) showDeleteSuggestionDialog(item);
|
if (item.fromHistory) {
|
||||||
|
showDeleteSuggestionDialog(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
|
if (textWatcher != null) {
|
||||||
|
searchEditText.removeTextChangedListener(textWatcher);
|
||||||
|
}
|
||||||
textWatcher = new TextWatcher() {
|
textWatcher = new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
public void beforeTextChanged(final CharSequence s, final int start,
|
||||||
}
|
final int count, final int after) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void onTextChanged(final CharSequence s, final int start,
|
||||||
}
|
final int before, final int count) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(final Editable s) {
|
||||||
String newText = searchEditText.getText().toString();
|
String newText = searchEditText.getText().toString();
|
||||||
suggestionPublisher.onNext(newText);
|
suggestionPublisher.onNext(newText);
|
||||||
}
|
}
|
||||||
|
@ -522,48 +556,62 @@ public class SearchFragment
|
||||||
searchEditText.setOnEditorActionListener(
|
searchEditText.setOnEditorActionListener(
|
||||||
(TextView v, int actionId, KeyEvent event) -> {
|
(TextView v, int actionId, KeyEvent event) -> {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], "
|
||||||
|
+ "actionId = [" + actionId + "], event = [" + event + "]");
|
||||||
}
|
}
|
||||||
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){
|
if (actionId == EditorInfo.IME_ACTION_PREVIOUS) {
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
} else if (event != null
|
} else if (event != null
|
||||||
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|
||||||
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
||||||
search(searchEditText.getText().toString(), new String[0], "");
|
search(searchEditText.getText().toString(), new String[0], "");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed())
|
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||||
initSuggestionObserver();
|
initSuggestionObserver();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unsetSearchListeners() {
|
private void unsetSearchListeners() {
|
||||||
if (DEBUG) Log.d(TAG, "unsetSearchListeners() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "unsetSearchListeners() called");
|
||||||
|
}
|
||||||
searchClear.setOnClickListener(null);
|
searchClear.setOnClickListener(null);
|
||||||
searchClear.setOnLongClickListener(null);
|
searchClear.setOnLongClickListener(null);
|
||||||
searchEditText.setOnClickListener(null);
|
searchEditText.setOnClickListener(null);
|
||||||
searchEditText.setOnFocusChangeListener(null);
|
searchEditText.setOnFocusChangeListener(null);
|
||||||
searchEditText.setOnEditorActionListener(null);
|
searchEditText.setOnEditorActionListener(null);
|
||||||
|
|
||||||
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
|
if (textWatcher != null) {
|
||||||
|
searchEditText.removeTextChangedListener(textWatcher);
|
||||||
|
}
|
||||||
textWatcher = null;
|
textWatcher = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSuggestionsPanel() {
|
private void showSuggestionsPanel() {
|
||||||
if (DEBUG) Log.d(TAG, "showSuggestionsPanel() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "showSuggestionsPanel() called");
|
||||||
|
}
|
||||||
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
|
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideSuggestionsPanel() {
|
private void hideSuggestionsPanel() {
|
||||||
if (DEBUG) Log.d(TAG, "hideSuggestionsPanel() called");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "hideSuggestionsPanel() called");
|
||||||
|
}
|
||||||
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
|
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showKeyboardSearch() {
|
private void showKeyboardSearch() {
|
||||||
if (DEBUG) Log.d(TAG, "showKeyboardSearch() called");
|
if (DEBUG) {
|
||||||
if (searchEditText == null) return;
|
Log.d(TAG, "showKeyboardSearch() called");
|
||||||
|
}
|
||||||
|
if (searchEditText == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (searchEditText.requestFocus()) {
|
if (searchEditText.requestFocus()) {
|
||||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||||
|
@ -573,19 +621,26 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideKeyboardSearch() {
|
private void hideKeyboardSearch() {
|
||||||
if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called");
|
if (DEBUG) {
|
||||||
if (searchEditText == null) return;
|
Log.d(TAG, "hideKeyboardSearch() called");
|
||||||
|
}
|
||||||
|
if (searchEditText == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
InputMethodManager imm = (InputMethodManager) activity
|
||||||
Context.INPUT_METHOD_SERVICE);
|
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||||
|
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||||
|
|
||||||
searchEditText.clearFocus();
|
searchEditText.clearFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
||||||
if (activity == null || historyRecordManager == null || suggestionPublisher == null ||
|
if (activity == null || historyRecordManager == null || suggestionPublisher == null
|
||||||
searchEditText == null || disposables == null) return;
|
|| searchEditText == null || disposables == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final String query = item.query;
|
final String query = item.query;
|
||||||
new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle(query)
|
.setTitle(query)
|
||||||
|
@ -624,15 +679,19 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSuggestionObserver() {
|
private void initSuggestionObserver() {
|
||||||
if (DEBUG) Log.d(TAG, "initSuggestionObserver() called");
|
if (DEBUG) {
|
||||||
if (suggestionDisposable != null) suggestionDisposable.dispose();
|
Log.d(TAG, "initSuggestionObserver() called");
|
||||||
|
}
|
||||||
|
if (suggestionDisposable != null) {
|
||||||
|
suggestionDisposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
final Observable<String> observable = suggestionPublisher
|
final Observable<String> observable = suggestionPublisher
|
||||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||||
.startWith(searchString != null
|
.startWith(searchString != null
|
||||||
? searchString
|
? searchString
|
||||||
: "")
|
: "")
|
||||||
.filter(searchString -> isSuggestionsEnabled);
|
.filter(ss -> isSuggestionsEnabled);
|
||||||
|
|
||||||
suggestionDisposable = observable
|
suggestionDisposable = observable
|
||||||
.switchMap(query -> {
|
.switchMap(query -> {
|
||||||
|
@ -641,13 +700,15 @@ public class SearchFragment
|
||||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||||
.map(searchHistoryEntries -> {
|
.map(searchHistoryEntries -> {
|
||||||
List<SuggestionItem> result = new ArrayList<>();
|
List<SuggestionItem> result = new ArrayList<>();
|
||||||
for (SearchHistoryEntry entry : searchHistoryEntries)
|
for (SearchHistoryEntry entry : searchHistoryEntries) {
|
||||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
|
||||||
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
// Only pass through if the query length
|
||||||
|
// is equal or greater than THRESHOLD_NETWORK_SUGGESTION
|
||||||
return local.materialize();
|
return local.materialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,7 +725,9 @@ public class SearchFragment
|
||||||
|
|
||||||
return Observable.zip(local, network, (localResult, networkResult) -> {
|
return Observable.zip(local, network, (localResult, networkResult) -> {
|
||||||
List<SuggestionItem> result = new ArrayList<>();
|
List<SuggestionItem> result = new ArrayList<>();
|
||||||
if (localResult.size() > 0) result.addAll(localResult);
|
if (localResult.size() > 0) {
|
||||||
|
result.addAll(localResult);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||||
|
@ -678,7 +741,9 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (networkResult.size() > 0) result.addAll(networkResult);
|
if (networkResult.size() > 0) {
|
||||||
|
result.addAll(networkResult);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}).materialize();
|
}).materialize();
|
||||||
})
|
})
|
||||||
|
@ -703,17 +768,21 @@ public class SearchFragment
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
private void search(final String searchString, String[] contentFilter, String sortFilter) {
|
private void search(final String ss, final String[] cf, final String sf) {
|
||||||
if (DEBUG) Log.d(TAG, "search() called with: query = [" + searchString + "]");
|
if (DEBUG) {
|
||||||
if (searchString.isEmpty()) return;
|
Log.d(TAG, "search() called with: query = [" + ss + "]");
|
||||||
|
}
|
||||||
|
if (ss.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final StreamingService service = NewPipe.getServiceByUrl(searchString);
|
final StreamingService streamingService = NewPipe.getServiceByUrl(ss);
|
||||||
if (service != null) {
|
if (streamingService != null) {
|
||||||
showLoading();
|
showLoading();
|
||||||
disposables.add(Observable
|
disposables.add(Observable
|
||||||
.fromCallable(() ->
|
.fromCallable(() ->
|
||||||
NavigationHelper.getIntentByLink(activity, service, searchString))
|
NavigationHelper.getIntentByLink(activity, streamingService, ss))
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
|
@ -728,31 +797,36 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSearchedString = this.searchString;
|
lastSearchedString = this.searchString;
|
||||||
this.searchString = searchString;
|
this.searchString = ss;
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
hideSuggestionsPanel();
|
hideSuggestionsPanel();
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
|
|
||||||
historyRecordManager.onSearched(serviceId, searchString)
|
historyRecordManager.onSearched(serviceId, ss)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
ignored -> {},
|
ignored -> {
|
||||||
|
},
|
||||||
error -> showSnackBarError(error, UserAction.SEARCHED,
|
error -> showSnackBarError(error, UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0)
|
NewPipe.getNameOfService(serviceId), ss, 0)
|
||||||
);
|
);
|
||||||
suggestionPublisher.onNext(searchString);
|
suggestionPublisher.onNext(ss);
|
||||||
startLoading(false);
|
startLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startLoading(boolean forceLoad) {
|
public void startLoading(final boolean forceLoad) {
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
if (searchDisposable != null) searchDisposable.dispose();
|
disposables.clear();
|
||||||
|
}
|
||||||
|
if (searchDisposable != null) {
|
||||||
|
searchDisposable.dispose();
|
||||||
|
}
|
||||||
searchDisposable = ExtractorHelper.searchFor(serviceId,
|
searchDisposable = ExtractorHelper.searchFor(serviceId,
|
||||||
searchString,
|
searchString,
|
||||||
Arrays.asList(contentFilter),
|
Arrays.asList(contentFilter),
|
||||||
sortFilter)
|
sortFilter)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
||||||
|
@ -762,16 +836,20 @@ public class SearchFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loadMoreItems() {
|
protected void loadMoreItems() {
|
||||||
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
|
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
isLoading.set(true);
|
isLoading.set(true);
|
||||||
showListFooter(true);
|
showListFooter(true);
|
||||||
if (searchDisposable != null) searchDisposable.dispose();
|
if (searchDisposable != null) {
|
||||||
|
searchDisposable.dispose();
|
||||||
|
}
|
||||||
searchDisposable = ExtractorHelper.getMoreSearchItems(
|
searchDisposable = ExtractorHelper.getMoreSearchItems(
|
||||||
serviceId,
|
serviceId,
|
||||||
searchString,
|
searchString,
|
||||||
asList(contentFilter),
|
asList(contentFilter),
|
||||||
sortFilter,
|
sortFilter,
|
||||||
nextPageUrl)
|
nextPageUrl)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||||
|
@ -785,7 +863,7 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onItemSelected(InfoItem selectedItem) {
|
protected void onItemSelected(final InfoItem selectedItem) {
|
||||||
super.onItemSelected(selectedItem);
|
super.onItemSelected(selectedItem);
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
}
|
}
|
||||||
|
@ -794,22 +872,22 @@ public class SearchFragment
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void changeContentFilter(MenuItem item, List<String> contentFilter) {
|
private void changeContentFilter(final MenuItem item, final List<String> cf) {
|
||||||
this.filterItemCheckedId = item.getItemId();
|
this.filterItemCheckedId = item.getItemId();
|
||||||
item.setChecked(true);
|
item.setChecked(true);
|
||||||
|
|
||||||
this.contentFilter = new String[] {contentFilter.get(0)};
|
this.contentFilter = new String[]{cf.get(0)};
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchString)) {
|
if (!TextUtils.isEmpty(searchString)) {
|
||||||
search(searchString, this.contentFilter, sortFilter);
|
search(searchString, this.contentFilter, sortFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setQuery(int serviceId, String searchString, String[] contentfilter, String sortFilter) {
|
private void setQuery(final int sid, final String ss, final String[] cf, final String sf) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = sid;
|
||||||
this.searchString = searchString;
|
this.searchString = searchString;
|
||||||
this.contentFilter = contentfilter;
|
this.contentFilter = cf;
|
||||||
this.sortFilter = sortFilter;
|
this.sortFilter = sf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -817,7 +895,9 @@ public class SearchFragment
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) {
|
public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) {
|
||||||
if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]");
|
||||||
|
}
|
||||||
suggestionsRecyclerView.smoothScrollToPosition(0);
|
suggestionsRecyclerView.smoothScrollToPosition(0);
|
||||||
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
|
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
|
||||||
|
|
||||||
|
@ -826,9 +906,13 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSuggestionError(Throwable exception) {
|
public void onSuggestionError(final Throwable exception) {
|
||||||
if (DEBUG) Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
|
if (DEBUG) {
|
||||||
if (super.onError(exception)) return;
|
Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
|
||||||
|
}
|
||||||
|
if (super.onError(exception)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int errorId = exception instanceof ParsingException
|
int errorId = exception instanceof ParsingException
|
||||||
? R.string.parsing_error
|
? R.string.parsing_error
|
||||||
|
@ -848,7 +932,7 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showError(String message, boolean showRetryButton) {
|
public void showError(final String message, final boolean showRetryButton) {
|
||||||
super.showError(message, showRetryButton);
|
super.showError(message, showRetryButton);
|
||||||
hideSuggestionsPanel();
|
hideSuggestionsPanel();
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
|
@ -859,11 +943,11 @@ public class SearchFragment
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull SearchInfo result) {
|
public void handleResult(@NonNull final SearchInfo result) {
|
||||||
final List<Throwable> exceptions = result.getErrors();
|
final List<Throwable> exceptions = result.getErrors();
|
||||||
if (!exceptions.isEmpty()
|
if (!exceptions.isEmpty()
|
||||||
&& !(exceptions.size() == 1
|
&& !(exceptions.size() == 1
|
||||||
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){
|
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||||
}
|
}
|
||||||
|
@ -886,7 +970,7 @@ public class SearchFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
currentPageUrl = result.getNextPageUrl();
|
currentPageUrl = result.getNextPageUrl();
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
|
@ -894,15 +978,17 @@ public class SearchFragment
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId)
|
NewPipe.getNameOfService(serviceId),
|
||||||
, "\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
||||||
}
|
}
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
if (super.onError(exception)) return true;
|
if (super.onError(exception)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (exception instanceof SearchExtractor.NothingFoundException) {
|
if (exception instanceof SearchExtractor.NothingFoundException) {
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
|
@ -922,13 +1008,16 @@ public class SearchFragment
|
||||||
// Suggestion item touch helper
|
// Suggestion item touch helper
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
|
||||||
|
@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||||
final int position = viewHolder.getAdapterPosition();
|
final int position = viewHolder.getAdapterPosition();
|
||||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||||
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
return item.fromHistory ? makeMovementFlags(0,
|
||||||
|
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||||
|
final int i) {
|
||||||
final int position = viewHolder.getAdapterPosition();
|
final int position = viewHolder.getAdapterPosition();
|
||||||
final String query = suggestionListAdapter.getItem(position).query;
|
final String query = suggestionListAdapter.getItem(position).query;
|
||||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package org.schabi.newpipe.fragments.list.search;
|
package org.schabi.newpipe.fragments.list.search;
|
||||||
|
|
||||||
public class SuggestionItem {
|
public class SuggestionItem {
|
||||||
public final boolean fromHistory;
|
final boolean fromHistory;
|
||||||
public final String query;
|
public final String query;
|
||||||
|
|
||||||
public SuggestionItem(boolean fromHistory, String query) {
|
public SuggestionItem(final boolean fromHistory, final String query) {
|
||||||
this.fromHistory = fromHistory;
|
this.fromHistory = fromHistory;
|
||||||
this.query = query;
|
this.query = query;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,36 +2,32 @@ package org.schabi.newpipe.fragments.list.search;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import androidx.annotation.AttrRes;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.AttrRes;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> {
|
public class SuggestionListAdapter
|
||||||
|
extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> {
|
||||||
private final ArrayList<SuggestionItem> items = new ArrayList<>();
|
private final ArrayList<SuggestionItem> items = new ArrayList<>();
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private OnSuggestionItemSelected listener;
|
private OnSuggestionItemSelected listener;
|
||||||
private boolean showSuggestionHistory = true;
|
private boolean showSuggestionHistory = true;
|
||||||
|
|
||||||
public interface OnSuggestionItemSelected {
|
public SuggestionListAdapter(final Context context) {
|
||||||
void onSuggestionItemSelected(SuggestionItem item);
|
|
||||||
void onSuggestionItemInserted(SuggestionItem item);
|
|
||||||
void onSuggestionItemLongClick(SuggestionItem item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SuggestionListAdapter(Context context) {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<SuggestionItem> items) {
|
public void setItems(final List<SuggestionItem> items) {
|
||||||
this.items.clear();
|
this.items.clear();
|
||||||
if (showSuggestionHistory) {
|
if (showSuggestionHistory) {
|
||||||
this.items.addAll(items);
|
this.items.addAll(items);
|
||||||
|
@ -46,36 +42,43 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListener(OnSuggestionItemSelected listener) {
|
public void setListener(final OnSuggestionItemSelected listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setShowSuggestionHistory(boolean v) {
|
public void setShowSuggestionHistory(final boolean v) {
|
||||||
showSuggestionHistory = v;
|
showSuggestionHistory = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SuggestionItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public SuggestionItemHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
|
||||||
return new SuggestionItemHolder(LayoutInflater.from(context).inflate(R.layout.item_search_suggestion, parent, false));
|
return new SuggestionItemHolder(LayoutInflater.from(context)
|
||||||
|
.inflate(R.layout.item_search_suggestion, parent, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(SuggestionItemHolder holder, int position) {
|
public void onBindViewHolder(final SuggestionItemHolder holder, final int position) {
|
||||||
final SuggestionItem currentItem = getItem(position);
|
final SuggestionItem currentItem = getItem(position);
|
||||||
holder.updateFrom(currentItem);
|
holder.updateFrom(currentItem);
|
||||||
holder.queryView.setOnClickListener(v -> {
|
holder.queryView.setOnClickListener(v -> {
|
||||||
if (listener != null) listener.onSuggestionItemSelected(currentItem);
|
if (listener != null) {
|
||||||
|
listener.onSuggestionItemSelected(currentItem);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
holder.queryView.setOnLongClickListener(v -> {
|
holder.queryView.setOnLongClickListener(v -> {
|
||||||
if (listener != null) listener.onSuggestionItemLongClick(currentItem);
|
if (listener != null) {
|
||||||
return true;
|
listener.onSuggestionItemLongClick(currentItem);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
holder.insertView.setOnClickListener(v -> {
|
holder.insertView.setOnClickListener(v -> {
|
||||||
if (listener != null) listener.onSuggestionItemInserted(currentItem);
|
if (listener != null) {
|
||||||
|
listener.onSuggestionItemInserted(currentItem);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
SuggestionItem getItem(int position) {
|
SuggestionItem getItem(final int position) {
|
||||||
return items.get(position);
|
return items.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +91,15 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||||
return getItemCount() == 0;
|
return getItemCount() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SuggestionItemHolder extends RecyclerView.ViewHolder {
|
public interface OnSuggestionItemSelected {
|
||||||
|
void onSuggestionItemSelected(SuggestionItem item);
|
||||||
|
|
||||||
|
void onSuggestionItemInserted(SuggestionItem item);
|
||||||
|
|
||||||
|
void onSuggestionItemLongClick(SuggestionItem item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class SuggestionItemHolder extends RecyclerView.ViewHolder {
|
||||||
private final TextView itemSuggestionQuery;
|
private final TextView itemSuggestionQuery;
|
||||||
private final ImageView suggestionIcon;
|
private final ImageView suggestionIcon;
|
||||||
private final View queryView;
|
private final View queryView;
|
||||||
|
@ -98,7 +109,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||||
private final int historyResId;
|
private final int historyResId;
|
||||||
private final int searchResId;
|
private final int searchResId;
|
||||||
|
|
||||||
private SuggestionItemHolder(View rootView) {
|
private SuggestionItemHolder(final View rootView) {
|
||||||
super(rootView);
|
super(rootView);
|
||||||
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
|
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
|
||||||
itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query);
|
itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query);
|
||||||
|
@ -110,16 +121,17 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFrom(SuggestionItem item) {
|
private static int resolveResourceIdFromAttr(final Context context,
|
||||||
suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId);
|
@AttrRes final int attr) {
|
||||||
itemSuggestionQuery.setText(item.query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) {
|
|
||||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||||
int attributeResourceId = a.getResourceId(0, 0);
|
int attributeResourceId = a.getResourceId(0, 0);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
return attributeResourceId;
|
return attributeResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateFrom(final SuggestionItem item) {
|
||||||
|
suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId);
|
||||||
|
itemSuggestionQuery.setText(item.query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,6 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -14,6 +12,9 @@ import android.view.ViewGroup;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
@ -28,8 +29,9 @@ import java.io.Serializable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
|
||||||
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{
|
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo>
|
||||||
|
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
private static final String INFO_KEY = "related_info_key";
|
||||||
private CompositeDisposable disposables = new CompositeDisposable();
|
private CompositeDisposable disposables = new CompositeDisposable();
|
||||||
private RelatedStreamInfo relatedStreamInfo;
|
private RelatedStreamInfo relatedStreamInfo;
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -37,44 +39,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
private View headerRootLayout;
|
private View headerRootLayout;
|
||||||
private Switch aSwitch;
|
private Switch aSwitch;
|
||||||
|
|
||||||
private boolean mIsVisibleToUser = false;
|
private boolean mIsVisibleToUser = false;
|
||||||
|
|
||||||
public static RelatedVideosFragment getInstance(StreamInfo info) {
|
|
||||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
|
||||||
instance.setInitialData(info);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
||||||
|
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||||
|
instance.setInitialData(info);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
mIsVisibleToUser = isVisibleToUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_related_streams, container, false);
|
return inflater.inflate(R.layout.fragment_related_streams, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected View getListHeader(){
|
protected View getListHeader() {
|
||||||
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
|
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
|
||||||
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
|
headerRootLayout = activity.getLayoutInflater()
|
||||||
|
.inflate(R.layout.related_streams_header, itemsList, false);
|
||||||
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||||
|
|
||||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
|
@ -82,14 +88,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
aSwitch.setChecked(autoplay);
|
aSwitch.setChecked(autoplay);
|
||||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
public void onCheckedChanged(final CompoundButton compoundButton,
|
||||||
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
|
final boolean b) {
|
||||||
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
|
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
|
||||||
prefEdit.apply();
|
.putBoolean(getString(R.string.auto_queue_key), b).apply();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return headerRootLayout;
|
return headerRootLayout;
|
||||||
}else{
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,38 +105,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
|
|
||||||
return Single.fromCallable(() -> relatedStreamInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Single<RelatedStreamInfo> loadResult(final boolean forceLoad) {
|
||||||
|
return Single.fromCallable(() -> relatedStreamInfo);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
|
if (headerRootLayout != null) {
|
||||||
|
headerRootLayout.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull RelatedStreamInfo result) {
|
public void handleResult(@NonNull final RelatedStreamInfo result) {
|
||||||
|
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
|
if (headerRootLayout != null) {
|
||||||
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
|
headerRootLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
AnimationUtils.slideUp(getView(), 120, 96, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM,
|
||||||
|
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// OnError
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
|
@ -142,63 +158,64 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(Throwable exception) {
|
|
||||||
if (super.onError(exception)) return true;
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitle(String title) {
|
protected boolean onError(final Throwable exception) {
|
||||||
|
if (super.onError(exception)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading();
|
||||||
|
showSnackBarError(exception, UserAction.REQUESTED_STREAM,
|
||||||
|
NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTitle(final String title) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInitialData(StreamInfo info) {
|
private void setInitialData(final StreamInfo info) {
|
||||||
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||||
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
|
if (this.relatedStreamInfo == null) {
|
||||||
|
this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static final String INFO_KEY = "related_info_key";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putSerializable(INFO_KEY, relatedStreamInfo);
|
outState.putSerializable(INFO_KEY, relatedStreamInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||||
super.onRestoreInstanceState(savedState);
|
super.onRestoreInstanceState(savedState);
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||||
if(serializable instanceof RelatedStreamInfo){
|
if (serializable instanceof RelatedStreamInfo) {
|
||||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||||
|
final String s) {
|
||||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||||
if(null != aSwitch) aSwitch.setChecked(autoplay);
|
if (null != aSwitch) {
|
||||||
|
aSwitch.setChecked(autoplay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package org.schabi.newpipe.info_list;
|
package org.schabi.newpipe.info_list;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
@ -29,24 +30,26 @@ import org.schabi.newpipe.util.OnClickGesture;
|
||||||
* <p>
|
* <p>
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
* InfoItemBuilder.java is part of NewPipe.
|
* InfoItemBuilder.java is part of NewPipe.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class InfoItemBuilder {
|
public class InfoItemBuilder {
|
||||||
private static final String TAG = InfoItemBuilder.class.toString();
|
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
|
|
||||||
|
@ -55,31 +58,39 @@ public class InfoItemBuilder {
|
||||||
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
|
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
|
||||||
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
|
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
|
||||||
|
|
||||||
public InfoItemBuilder(Context context) {
|
public InfoItemBuilder(final Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
|
||||||
|
final HistoryRecordManager historyRecordManager) {
|
||||||
return buildView(parent, infoItem, historyRecordManager, false);
|
return buildView(parent, infoItem, historyRecordManager, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem,
|
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
|
||||||
final HistoryRecordManager historyRecordManager, boolean useMiniVariant) {
|
final HistoryRecordManager historyRecordManager,
|
||||||
|
final boolean useMiniVariant) {
|
||||||
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||||
holder.updateFromItem(infoItem, historyRecordManager);
|
holder.updateFromItem(infoItem, historyRecordManager);
|
||||||
return holder.itemView;
|
return holder.itemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InfoItemHolder holderFromInfoType(@NonNull ViewGroup parent, @NonNull InfoItem.InfoType infoType, boolean useMiniVariant) {
|
private InfoItemHolder holderFromInfoType(@NonNull final ViewGroup parent,
|
||||||
|
@NonNull final InfoItem.InfoType infoType,
|
||||||
|
final boolean useMiniVariant) {
|
||||||
switch (infoType) {
|
switch (infoType) {
|
||||||
case STREAM:
|
case STREAM:
|
||||||
return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent) : new StreamInfoItemHolder(this, parent);
|
return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent)
|
||||||
|
: new StreamInfoItemHolder(this, parent);
|
||||||
case CHANNEL:
|
case CHANNEL:
|
||||||
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent);
|
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent)
|
||||||
|
: new ChannelInfoItemHolder(this, parent);
|
||||||
case PLAYLIST:
|
case PLAYLIST:
|
||||||
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent);
|
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent)
|
||||||
|
: new PlaylistInfoItemHolder(this, parent);
|
||||||
case COMMENT:
|
case COMMENT:
|
||||||
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent);
|
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent)
|
||||||
|
: new CommentsInfoItemHolder(this, parent);
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("InfoType not expected = " + infoType.name());
|
throw new RuntimeException("InfoType not expected = " + infoType.name());
|
||||||
}
|
}
|
||||||
|
@ -97,7 +108,7 @@ public class InfoItemBuilder {
|
||||||
return onStreamSelectedListener;
|
return onStreamSelectedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnStreamSelectedListener(OnClickGesture<StreamInfoItem> listener) {
|
public void setOnStreamSelectedListener(final OnClickGesture<StreamInfoItem> listener) {
|
||||||
this.onStreamSelectedListener = listener;
|
this.onStreamSelectedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +116,7 @@ public class InfoItemBuilder {
|
||||||
return onChannelSelectedListener;
|
return onChannelSelectedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnChannelSelectedListener(OnClickGesture<ChannelInfoItem> listener) {
|
public void setOnChannelSelectedListener(final OnClickGesture<ChannelInfoItem> listener) {
|
||||||
this.onChannelSelectedListener = listener;
|
this.onChannelSelectedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +124,7 @@ public class InfoItemBuilder {
|
||||||
return onPlaylistSelectedListener;
|
return onPlaylistSelectedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnPlaylistSelectedListener(OnClickGesture<PlaylistInfoItem> listener) {
|
public void setOnPlaylistSelectedListener(final OnClickGesture<PlaylistInfoItem> listener) {
|
||||||
this.onPlaylistSelectedListener = listener;
|
this.onPlaylistSelectedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,8 +132,8 @@ public class InfoItemBuilder {
|
||||||
return onCommentsSelectedListener;
|
return onCommentsSelectedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
|
public void setOnCommentsSelectedListener(
|
||||||
|
final OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
|
||||||
this.onCommentsSelectedListener = onCommentsSelectedListener;
|
this.onCommentsSelectedListener = onCommentsSelectedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,12 @@ package org.schabi.newpipe.info_list;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package org.schabi.newpipe.info_list;
|
package org.schabi.newpipe.info_list;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
@ -83,42 +84,33 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
private View header = null;
|
private View header = null;
|
||||||
private View footer = null;
|
private View footer = null;
|
||||||
|
|
||||||
public class HFHolder extends RecyclerView.ViewHolder {
|
public InfoListAdapter(final Context context) {
|
||||||
public View view;
|
|
||||||
|
|
||||||
public HFHolder(View v) {
|
|
||||||
super(v);
|
|
||||||
view = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InfoListAdapter(Context context) {
|
|
||||||
this.recordManager = new HistoryRecordManager(context);
|
this.recordManager = new HistoryRecordManager(context);
|
||||||
infoItemBuilder = new InfoItemBuilder(context);
|
infoItemBuilder = new InfoItemBuilder(context);
|
||||||
infoItemList = new ArrayList<>();
|
infoItemList = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnStreamSelectedListener(OnClickGesture<StreamInfoItem> listener) {
|
public void setOnStreamSelectedListener(final OnClickGesture<StreamInfoItem> listener) {
|
||||||
infoItemBuilder.setOnStreamSelectedListener(listener);
|
infoItemBuilder.setOnStreamSelectedListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnChannelSelectedListener(OnClickGesture<ChannelInfoItem> listener) {
|
public void setOnChannelSelectedListener(final OnClickGesture<ChannelInfoItem> listener) {
|
||||||
infoItemBuilder.setOnChannelSelectedListener(listener);
|
infoItemBuilder.setOnChannelSelectedListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnPlaylistSelectedListener(OnClickGesture<PlaylistInfoItem> listener) {
|
public void setOnPlaylistSelectedListener(final OnClickGesture<PlaylistInfoItem> listener) {
|
||||||
infoItemBuilder.setOnPlaylistSelectedListener(listener);
|
infoItemBuilder.setOnPlaylistSelectedListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
|
public void setOnCommentsSelectedListener(final OnClickGesture<CommentsInfoItem> listener) {
|
||||||
infoItemBuilder.setOnCommentsSelectedListener(listener);
|
infoItemBuilder.setOnCommentsSelectedListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useMiniItemVariants(boolean useMiniVariant) {
|
public void setUseMiniVariant(final boolean useMiniVariant) {
|
||||||
this.useMiniVariant = useMiniVariant;
|
this.useMiniVariant = useMiniVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGridItemVariants(boolean useGridVariant) {
|
public void setUseGridVariant(final boolean useGridVariant) {
|
||||||
this.useGridVariant = useGridVariant;
|
this.useGridVariant = useGridVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,55 +118,67 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (DEBUG) Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " +
|
if (DEBUG) {
|
||||||
infoItemList.size() + ", data.size() = " + data.size());
|
Log.d(TAG, "addInfoItemList() before > infoItemList.size() = "
|
||||||
|
+ infoItemList.size() + ", data.size() = " + data.size());
|
||||||
|
}
|
||||||
|
|
||||||
int offsetStart = sizeConsideringHeaderOffset();
|
int offsetStart = sizeConsideringHeaderOffset();
|
||||||
infoItemList.addAll(data);
|
infoItemList.addAll(data);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart +
|
if (DEBUG) {
|
||||||
", infoItemList.size() = " + infoItemList.size() +
|
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", "
|
||||||
", header = " + header + ", footer = " + footer +
|
+ "infoItemList.size() = " + infoItemList.size() + ", "
|
||||||
", showFooter = " + showFooter);
|
+ "header = " + header + ", footer = " + footer + ", "
|
||||||
|
+ "showFooter = " + showFooter);
|
||||||
|
}
|
||||||
notifyItemRangeInserted(offsetStart, data.size());
|
notifyItemRangeInserted(offsetStart, data.size());
|
||||||
|
|
||||||
if (footer != null && showFooter) {
|
if (footer != null && showFooter) {
|
||||||
int footerNow = sizeConsideringHeaderOffset();
|
int footerNow = sizeConsideringHeaderOffset();
|
||||||
notifyItemMoved(offsetStart, footerNow);
|
notifyItemMoved(offsetStart, footerNow);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart +
|
if (DEBUG) {
|
||||||
" to " + footerNow);
|
Log.d(TAG, "addInfoItemList() footer from " + offsetStart
|
||||||
|
+ " to " + footerNow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInfoItemList(List<? extends InfoItem> data) {
|
public void setInfoItemList(final List<? extends InfoItem> data) {
|
||||||
infoItemList.clear();
|
infoItemList.clear();
|
||||||
infoItemList.addAll(data);
|
infoItemList.addAll(data);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addInfoItem(@Nullable InfoItem data) {
|
public void addInfoItem(@Nullable final InfoItem data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (DEBUG) Log.d(TAG, "addInfoItem() before > infoItemList.size() = " +
|
if (DEBUG) {
|
||||||
infoItemList.size() + ", thread = " + Thread.currentThread());
|
Log.d(TAG, "addInfoItem() before > infoItemList.size() = "
|
||||||
|
+ infoItemList.size() + ", thread = " + Thread.currentThread());
|
||||||
|
}
|
||||||
|
|
||||||
int positionInserted = sizeConsideringHeaderOffset();
|
int positionInserted = sizeConsideringHeaderOffset();
|
||||||
infoItemList.add(data);
|
infoItemList.add(data);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "addInfoItem() after > position = " + positionInserted +
|
if (DEBUG) {
|
||||||
", infoItemList.size() = " + infoItemList.size() +
|
Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", "
|
||||||
", header = " + header + ", footer = " + footer +
|
+ "infoItemList.size() = " + infoItemList.size() + ", "
|
||||||
", showFooter = " + showFooter);
|
+ "header = " + header + ", footer = " + footer + ", "
|
||||||
|
+ "showFooter = " + showFooter);
|
||||||
|
}
|
||||||
notifyItemInserted(positionInserted);
|
notifyItemInserted(positionInserted);
|
||||||
|
|
||||||
if (footer != null && showFooter) {
|
if (footer != null && showFooter) {
|
||||||
int footerNow = sizeConsideringHeaderOffset();
|
int footerNow = sizeConsideringHeaderOffset();
|
||||||
notifyItemMoved(positionInserted, footerNow);
|
notifyItemMoved(positionInserted, footerNow);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted +
|
if (DEBUG) {
|
||||||
" to " + footerNow);
|
Log.d(TAG, "addInfoItem() footer from " + positionInserted
|
||||||
|
+ " to " + footerNow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,29 +190,39 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeader(View header) {
|
public void setHeader(final View header) {
|
||||||
boolean changed = header != this.header;
|
boolean changed = header != this.header;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
if (changed) notifyDataSetChanged();
|
if (changed) {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFooter(View view) {
|
public void setFooter(final View view) {
|
||||||
this.footer = view;
|
this.footer = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showFooter(boolean show) {
|
public void showFooter(final boolean show) {
|
||||||
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]");
|
if (DEBUG) {
|
||||||
if (show == showFooter) return;
|
Log.d(TAG, "showFooter() called with: show = [" + show + "]");
|
||||||
|
}
|
||||||
|
if (show == showFooter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
showFooter = show;
|
showFooter = show;
|
||||||
if (show) notifyItemInserted(sizeConsideringHeaderOffset());
|
if (show) {
|
||||||
else notifyItemRemoved(sizeConsideringHeaderOffset());
|
notifyItemInserted(sizeConsideringHeaderOffset());
|
||||||
|
} else {
|
||||||
|
notifyItemRemoved(sizeConsideringHeaderOffset());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private int sizeConsideringHeaderOffset() {
|
private int sizeConsideringHeaderOffset() {
|
||||||
int i = infoItemList.size() + (header != null ? 1 : 0);
|
int i = infoItemList.size() + (header != null ? 1 : 0);
|
||||||
if (DEBUG) Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
|
||||||
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,18 +233,27 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
int count = infoItemList.size();
|
int count = infoItemList.size();
|
||||||
if (header != null) count++;
|
if (header != null) {
|
||||||
if (footer != null && showFooter) count++;
|
count++;
|
||||||
|
}
|
||||||
|
if (footer != null && showFooter) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "getItemCount() called, count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
|
Log.d(TAG, "getItemCount() called with: "
|
||||||
|
+ "count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", "
|
||||||
|
+ "header = " + header + ", footer = " + footer + ", "
|
||||||
|
+ "showFooter = " + showFooter);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
|
||||||
|
}
|
||||||
|
|
||||||
if (header != null && position == 0) {
|
if (header != null && position == 0) {
|
||||||
return HEADER_TYPE;
|
return HEADER_TYPE;
|
||||||
|
@ -243,11 +266,14 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
final InfoItem item = infoItemList.get(position);
|
final InfoItem item = infoItemList.get(position);
|
||||||
switch (item.getInfoType()) {
|
switch (item.getInfoType()) {
|
||||||
case STREAM:
|
case STREAM:
|
||||||
return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
|
return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant
|
||||||
|
? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
|
||||||
case CHANNEL:
|
case CHANNEL:
|
||||||
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant
|
||||||
|
? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||||
case PLAYLIST:
|
case PLAYLIST:
|
||||||
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant
|
||||||
|
? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
||||||
case COMMENT:
|
case COMMENT:
|
||||||
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
|
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
|
||||||
default:
|
default:
|
||||||
|
@ -257,9 +283,12 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
|
||||||
if (DEBUG)
|
final int type) {
|
||||||
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreateViewHolder() called with: "
|
||||||
|
+ "parent = [" + parent + "], type = [" + type + "]");
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case HEADER_TYPE:
|
case HEADER_TYPE:
|
||||||
return new HFHolder(header);
|
return new HFHolder(header);
|
||||||
|
@ -293,28 +322,38 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
|
||||||
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onBindViewHolder() called with: "
|
||||||
|
+ "holder = [" + holder.getClass().getSimpleName() + "], "
|
||||||
|
+ "position = [" + position + "]");
|
||||||
|
}
|
||||||
if (holder instanceof InfoItemHolder) {
|
if (holder instanceof InfoItemHolder) {
|
||||||
// If header isn't null, offset the items by -1
|
// If header isn't null, offset the items by -1
|
||||||
if (header != null) position--;
|
if (header != null) {
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
|
||||||
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager);
|
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager);
|
||||||
} else if (holder instanceof HFHolder && position == 0 && header != null) {
|
} else if (holder instanceof HFHolder && position == 0 && header != null) {
|
||||||
((HFHolder) holder).view = header;
|
((HFHolder) holder).view = header;
|
||||||
} else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) {
|
} else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset()
|
||||||
|
&& footer != null && showFooter) {
|
||||||
((HFHolder) holder).view = footer;
|
((HFHolder) holder).view = footer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||||
|
@NonNull final List<Object> payloads) {
|
||||||
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
||||||
for (Object payload : payloads) {
|
for (Object payload : payloads) {
|
||||||
if (payload instanceof StreamStateEntity) {
|
if (payload instanceof StreamStateEntity) {
|
||||||
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager);
|
((InfoItemHolder) holder).updateState(infoItemList
|
||||||
|
.get(header == null ? position : position - 1), recordManager);
|
||||||
} else if (payload instanceof Boolean) {
|
} else if (payload instanceof Boolean) {
|
||||||
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager);
|
((InfoItemHolder) holder).updateState(infoItemList
|
||||||
|
.get(header == null ? position : position - 1), recordManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -325,10 +364,19 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
||||||
return new GridLayoutManager.SpanSizeLookup() {
|
return new GridLayoutManager.SpanSizeLookup() {
|
||||||
@Override
|
@Override
|
||||||
public int getSpanSize(int position) {
|
public int getSpanSize(final int position) {
|
||||||
final int type = getItemViewType(position);
|
final int type = getItemViewType(position);
|
||||||
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
|
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class HFHolder extends RecyclerView.ViewHolder {
|
||||||
|
public View view;
|
||||||
|
|
||||||
|
HFHolder(final View v) {
|
||||||
|
super(v);
|
||||||
|
view = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
|
|
||||||
public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder {
|
public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder {
|
||||||
|
public ChannelGridInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
||||||
public ChannelGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, R.layout.list_channel_grid_item, parent);
|
super(infoItemBuilder, R.layout.list_channel_grid_item, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,19 +31,23 @@ import org.schabi.newpipe.util.Localization;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
|
public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
|
||||||
public final TextView itemChannelDescriptionView;
|
private final TextView itemChannelDescriptionView;
|
||||||
|
|
||||||
public ChannelInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public ChannelInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
super(infoItemBuilder, R.layout.list_channel_item, parent);
|
super(infoItemBuilder, R.layout.list_channel_item, parent);
|
||||||
itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView);
|
itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
|
final HistoryRecordManager historyRecordManager) {
|
||||||
super.updateFromItem(infoItem, historyRecordManager);
|
super.updateFromItem(infoItem, historyRecordManager);
|
||||||
|
|
||||||
if (!(infoItem instanceof ChannelInfoItem)) return;
|
if (!(infoItem instanceof ChannelInfoItem)) {
|
||||||
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
return;
|
||||||
|
}
|
||||||
|
final ChannelInfoItem item;
|
||||||
|
item = (ChannelInfoItem) infoItem;
|
||||||
|
|
||||||
itemChannelDescriptionView.setText(item.getDescription());
|
itemChannelDescriptionView.setText(item.getDescription());
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,10 @@ import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||||
public final CircleImageView itemThumbnailView;
|
public final CircleImageView itemThumbnailView;
|
||||||
public final TextView itemTitleView;
|
public final TextView itemTitleView;
|
||||||
public final TextView itemAdditionalDetailView;
|
private final TextView itemAdditionalDetailView;
|
||||||
|
|
||||||
ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
|
|
||||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||||
|
@ -26,13 +27,17 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails);
|
itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
||||||
|
final ViewGroup parent) {
|
||||||
this(infoItemBuilder, R.layout.list_channel_mini_item, parent);
|
this(infoItemBuilder, R.layout.list_channel_mini_item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
if (!(infoItem instanceof ChannelInfoItem)) return;
|
final HistoryRecordManager historyRecordManager) {
|
||||||
|
if (!(infoItem instanceof ChannelInfoItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
||||||
|
|
||||||
itemTitleView.setText(item.getName());
|
itemTitleView.setText(item.getName());
|
||||||
|
|
|
@ -30,23 +30,24 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||||
|
|
||||||
public final TextView itemTitleView;
|
public final TextView itemTitleView;
|
||||||
|
|
||||||
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
super(infoItemBuilder, R.layout.list_comments_item, parent);
|
super(infoItemBuilder, R.layout.list_comments_item, parent);
|
||||||
|
|
||||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
|
final HistoryRecordManager historyRecordManager) {
|
||||||
super.updateFromItem(infoItem, historyRecordManager);
|
super.updateFromItem(infoItem, historyRecordManager);
|
||||||
|
|
||||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
if (!(infoItem instanceof CommentsInfoItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||||
|
|
||||||
itemTitleView.setText(item.getAuthorName());
|
itemTitleView.setText(item.getAuthorName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,35 +29,41 @@ import java.util.regex.Pattern;
|
||||||
import de.hdodenhof.circleimageview.CircleImageView;
|
import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
|
|
||||||
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
private static final int COMMENT_DEFAULT_LINES = 2;
|
||||||
|
private static final int COMMENT_EXPANDED_LINES = 1000;
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
|
||||||
|
|
||||||
public final CircleImageView itemThumbnailView;
|
public final CircleImageView itemThumbnailView;
|
||||||
private final TextView itemContentView;
|
private final TextView itemContentView;
|
||||||
private final TextView itemLikesCountView;
|
private final TextView itemLikesCountView;
|
||||||
private final TextView itemDislikesCountView;
|
private final TextView itemDislikesCountView;
|
||||||
private final TextView itemPublishedTime;
|
private final TextView itemPublishedTime;
|
||||||
|
|
||||||
private static final int commentDefaultLines = 2;
|
|
||||||
private static final int commentExpandedLines = 1000;
|
|
||||||
|
|
||||||
private String commentText;
|
private String commentText;
|
||||||
private String streamUrl;
|
private String streamUrl;
|
||||||
|
|
||||||
private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
|
|
||||||
|
|
||||||
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
|
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
|
||||||
@Override
|
@Override
|
||||||
public String transformUrl(Matcher match, String url) {
|
public String transformUrl(final Matcher match, final String url) {
|
||||||
int timestamp = 0;
|
int timestamp = 0;
|
||||||
String hours = match.group(1);
|
String hours = match.group(1);
|
||||||
String minutes = match.group(2);
|
String minutes = match.group(2);
|
||||||
String seconds = match.group(3);
|
String seconds = match.group(3);
|
||||||
if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600);
|
if (hours != null) {
|
||||||
if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60);
|
timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
|
||||||
if(seconds != null) timestamp += (Integer.parseInt(seconds));
|
}
|
||||||
|
if (minutes != null) {
|
||||||
|
timestamp += (Integer.parseInt(minutes.replace(":", "")) * 60);
|
||||||
|
}
|
||||||
|
if (seconds != null) {
|
||||||
|
timestamp += (Integer.parseInt(seconds));
|
||||||
|
}
|
||||||
return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp);
|
return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
|
|
||||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||||
|
@ -67,13 +73,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
|
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
||||||
|
final ViewGroup parent) {
|
||||||
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
|
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
final HistoryRecordManager historyRecordManager) {
|
||||||
|
if (!(infoItem instanceof CommentsInfoItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||||
|
|
||||||
itemBuilder.getImageLoader()
|
itemBuilder.getImageLoader()
|
||||||
|
@ -82,7 +92,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||||
|
|
||||||
itemThumbnailView.setOnClickListener(view -> {
|
itemThumbnailView.setOnClickListener(view -> {
|
||||||
if(StringUtil.isBlank(item.getAuthorEndpoint())) return;
|
if (StringUtil.isBlank(item.getAuthorEndpoint())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||||
NavigationHelper.openChannelFragment(
|
NavigationHelper.openChannelFragment(
|
||||||
|
@ -97,7 +109,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
|
||||||
streamUrl = item.getUrl();
|
streamUrl = item.getUrl();
|
||||||
|
|
||||||
itemContentView.setLines(commentDefaultLines);
|
itemContentView.setLines(COMMENT_DEFAULT_LINES);
|
||||||
commentText = item.getCommentText();
|
commentText = item.getCommentText();
|
||||||
itemContentView.setText(commentText);
|
itemContentView.setText(commentText);
|
||||||
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
||||||
|
@ -130,12 +142,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
|
||||||
itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View view) {
|
public boolean onLongClick(final View view) {
|
||||||
|
|
||||||
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
|
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
|
||||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null,commentText));
|
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
|
||||||
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -144,10 +156,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ellipsize() {
|
private void ellipsize() {
|
||||||
if (itemContentView.getLineCount() > commentDefaultLines){
|
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
||||||
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1);
|
int endOfLastLine = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
|
||||||
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2);
|
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
|
||||||
if(end == -1) end = Math.max(endOfLastLine -2, 0);
|
if (end == -1) {
|
||||||
|
end = Math.max(endOfLastLine - 2, 0);
|
||||||
|
}
|
||||||
String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||||
itemContentView.setText(newVal);
|
itemContentView.setText(newVal);
|
||||||
}
|
}
|
||||||
|
@ -156,21 +170,23 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
|
||||||
private void toggleEllipsize() {
|
private void toggleEllipsize() {
|
||||||
if (itemContentView.getText().toString().equals(commentText)) {
|
if (itemContentView.getText().toString().equals(commentText)) {
|
||||||
if (itemContentView.getLineCount() > commentDefaultLines) ellipsize();
|
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
||||||
|
ellipsize();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
expand();
|
expand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expand() {
|
private void expand() {
|
||||||
itemContentView.setMaxLines(commentExpandedLines);
|
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
|
||||||
itemContentView.setText(commentText);
|
itemContentView.setText(commentText);
|
||||||
linkify();
|
linkify();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void linkify(){
|
private void linkify() {
|
||||||
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
||||||
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
|
Linkify.addLinks(itemContentView, PATTERN, null, null, timestampLink);
|
||||||
itemContentView.setMovementMethod(null);
|
itemContentView.setMovementMethod(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package org.schabi.newpipe.info_list.holder;
|
package org.schabi.newpipe.info_list.holder;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
@ -31,13 +32,15 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
public abstract class InfoItemHolder extends RecyclerView.ViewHolder {
|
public abstract class InfoItemHolder extends RecyclerView.ViewHolder {
|
||||||
protected final InfoItemBuilder itemBuilder;
|
protected final InfoItemBuilder itemBuilder;
|
||||||
|
|
||||||
public InfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
public InfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(LayoutInflater.from(infoItemBuilder.getContext()).inflate(layoutId, parent, false));
|
super(LayoutInflater.from(infoItemBuilder.getContext()).inflate(layoutId, parent, false));
|
||||||
this.itemBuilder = infoItemBuilder;
|
this.itemBuilder = infoItemBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager);
|
public abstract void updateFromItem(InfoItem infoItem,
|
||||||
|
HistoryRecordManager historyRecordManager);
|
||||||
|
|
||||||
public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateState(final InfoItem infoItem,
|
||||||
}
|
final HistoryRecordManager historyRecordManager) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
|
|
||||||
public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder {
|
public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder {
|
||||||
|
public PlaylistGridInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
||||||
public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,7 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
|
|
||||||
public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder {
|
public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder {
|
||||||
|
public PlaylistInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
|
||||||
super(infoItemBuilder, R.layout.list_playlist_item, parent);
|
super(infoItemBuilder, R.layout.list_playlist_item, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,12 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
|
|
||||||
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||||
public final ImageView itemThumbnailView;
|
public final ImageView itemThumbnailView;
|
||||||
public final TextView itemStreamCountView;
|
private final TextView itemStreamCountView;
|
||||||
public final TextView itemTitleView;
|
public final TextView itemTitleView;
|
||||||
public final TextView itemUploaderView;
|
public final TextView itemUploaderView;
|
||||||
|
|
||||||
public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
|
|
||||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||||
|
@ -26,13 +27,17 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
||||||
|
final ViewGroup parent) {
|
||||||
this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
|
this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
if (!(infoItem instanceof PlaylistInfoItem)) return;
|
final HistoryRecordManager historyRecordManager) {
|
||||||
|
if (!(infoItem instanceof PlaylistInfoItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
|
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
|
||||||
|
|
||||||
itemTitleView.setText(item.getName());
|
itemTitleView.setText(item.getName());
|
||||||
|
@ -41,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
|
||||||
itemBuilder.getImageLoader()
|
itemBuilder.getImageLoader()
|
||||||
.displayImage(item.getThumbnailUrl(), itemThumbnailView,
|
.displayImage(item.getThumbnailUrl(), itemThumbnailView,
|
||||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
|
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
|
||||||
|
|
|
@ -6,8 +6,7 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
|
|
||||||
public class StreamGridInfoItemHolder extends StreamInfoItemHolder {
|
public class StreamGridInfoItemHolder extends StreamInfoItemHolder {
|
||||||
|
public StreamGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
|
||||||
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,39 +20,46 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
* <p>
|
* <p>
|
||||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||||
* StreamInfoItemHolder.java is part of NewPipe.
|
* StreamInfoItemHolder.java is part of NewPipe.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
* </p?
|
||||||
* <p>
|
* <p>
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||||
|
|
||||||
public final TextView itemAdditionalDetails;
|
public final TextView itemAdditionalDetails;
|
||||||
|
|
||||||
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
this(infoItemBuilder, R.layout.list_stream_item, parent);
|
this(infoItemBuilder, R.layout.list_stream_item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
|
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
|
final HistoryRecordManager historyRecordManager) {
|
||||||
super.updateFromItem(infoItem, historyRecordManager);
|
super.updateFromItem(infoItem, historyRecordManager);
|
||||||
|
|
||||||
if (!(infoItem instanceof StreamInfoItem)) return;
|
if (!(infoItem instanceof StreamInfoItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||||
|
|
||||||
itemAdditionalDetails.setText(getStreamInfoDetailLine(item));
|
itemAdditionalDetails.setText(getStreamInfoDetailLine(item));
|
||||||
|
@ -62,11 +69,14 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||||
String viewsAndDate = "";
|
String viewsAndDate = "";
|
||||||
if (infoItem.getViewCount() >= 0) {
|
if (infoItem.getViewCount() >= 0) {
|
||||||
if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
|
if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
|
||||||
viewsAndDate = Localization.listeningCount(itemBuilder.getContext(), infoItem.getViewCount());
|
viewsAndDate = Localization
|
||||||
|
.listeningCount(itemBuilder.getContext(), infoItem.getViewCount());
|
||||||
} else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) {
|
} else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) {
|
||||||
viewsAndDate = Localization.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount());
|
viewsAndDate = Localization
|
||||||
|
.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount());
|
||||||
} else {
|
} else {
|
||||||
viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount());
|
viewsAndDate = Localization
|
||||||
|
.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +94,12 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||||
|
|
||||||
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
|
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
|
||||||
if (infoItem.getUploadDate() != null) {
|
if (infoItem.getUploadDate() != null) {
|
||||||
String formattedRelativeTime = Localization.relativeTime(infoItem.getUploadDate().date());
|
String formattedRelativeTime = Localization
|
||||||
|
.relativeTime(infoItem.getUploadDate().date());
|
||||||
|
|
||||||
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
|
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
|
||||||
.getBoolean(itemBuilder.getContext().getString(R.string.show_original_time_ago_key), false)) {
|
.getBoolean(itemBuilder.getContext()
|
||||||
|
.getString(R.string.show_original_time_ago_key), false)) {
|
||||||
formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
|
formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
|
||||||
}
|
}
|
||||||
return formattedRelativeTime;
|
return formattedRelativeTime;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package org.schabi.newpipe.info_list.holder;
|
package org.schabi.newpipe.info_list.holder;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
@ -21,14 +22,14 @@ import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
|
||||||
public final ImageView itemThumbnailView;
|
public final ImageView itemThumbnailView;
|
||||||
public final TextView itemVideoTitleView;
|
public final TextView itemVideoTitleView;
|
||||||
public final TextView itemUploaderView;
|
public final TextView itemUploaderView;
|
||||||
public final TextView itemDurationView;
|
public final TextView itemDurationView;
|
||||||
public final AnimatedProgressBar itemProgressView;
|
private final AnimatedProgressBar itemProgressView;
|
||||||
|
|
||||||
StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
|
|
||||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||||
|
@ -38,13 +39,16 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
public StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
this(infoItemBuilder, R.layout.list_stream_mini_item, parent);
|
this(infoItemBuilder, R.layout.list_stream_mini_item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
if (!(infoItem instanceof StreamInfoItem)) return;
|
final HistoryRecordManager historyRecordManager) {
|
||||||
|
if (!(infoItem instanceof StreamInfoItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||||
|
|
||||||
itemVideoTitleView.setText(item.getName());
|
itemVideoTitleView.setText(item.getName());
|
||||||
|
@ -56,11 +60,13 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||||
R.color.duration_background_color));
|
R.color.duration_background_color));
|
||||||
itemDurationView.setVisibility(View.VISIBLE);
|
itemDurationView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
||||||
|
.blockingGet()[0];
|
||||||
if (state2 != null) {
|
if (state2 != null) {
|
||||||
itemProgressView.setVisibility(View.VISIBLE);
|
itemProgressView.setVisibility(View.VISIBLE);
|
||||||
itemProgressView.setMax((int) item.getDuration());
|
itemProgressView.setMax((int) item.getDuration());
|
||||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state2.getProgressTime()));
|
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
|
||||||
|
.toSeconds(state2.getProgressTime()));
|
||||||
} else {
|
} else {
|
||||||
itemProgressView.setVisibility(View.GONE);
|
itemProgressView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -103,16 +109,20 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
public void updateState(final InfoItem infoItem,
|
||||||
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
||||||
if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) {
|
if (state != null && item.getDuration() > 0
|
||||||
|
&& item.getStreamType() != StreamType.LIVE_STREAM) {
|
||||||
itemProgressView.setMax((int) item.getDuration());
|
itemProgressView.setMax((int) item.getDuration());
|
||||||
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||||
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
|
||||||
|
.toSeconds(state.getProgressTime()));
|
||||||
} else {
|
} else {
|
||||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
|
||||||
|
.toSeconds(state.getProgressTime()));
|
||||||
AnimationUtils.animateView(itemProgressView, true, 500);
|
AnimationUtils.animateView(itemProgressView, true, 500);
|
||||||
}
|
}
|
||||||
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||||
|
|
|
@ -5,16 +5,17 @@ import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||||
import org.schabi.newpipe.fragments.list.ListViewContract;
|
import org.schabi.newpipe.fragments.list.ListViewContract;
|
||||||
|
@ -25,10 +26,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
* This fragment is design to be used with persistent data such as
|
* This fragment is design to be used with persistent data such as
|
||||||
* {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained
|
* {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained
|
||||||
* in the list adapter to avoid extra writes when the it exits or re-enters its lifecycle.
|
* in the list adapter to avoid extra writes when the it exits or re-enters its lifecycle.
|
||||||
*
|
* <p>
|
||||||
* This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is
|
* This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is
|
||||||
* called and is memory efficient when in backstack.
|
* called and is memory efficient when in backstack.
|
||||||
* */
|
* </p>
|
||||||
|
*
|
||||||
|
* @param <I> List of {@link org.schabi.newpipe.database.LocalItem}s
|
||||||
|
* @param <N> {@link Void}
|
||||||
|
*/
|
||||||
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
|
implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
@ -36,21 +41,19 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
// Views
|
// Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected View headerRootView;
|
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
||||||
protected View footerRootView;
|
private View headerRootView;
|
||||||
|
private View footerRootView;
|
||||||
protected LocalItemListAdapter itemListAdapter;
|
protected LocalItemListAdapter itemListAdapter;
|
||||||
protected RecyclerView itemsList;
|
protected RecyclerView itemsList;
|
||||||
private int updateFlags = 0;
|
private int updateFlags = 0;
|
||||||
|
|
||||||
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Lifecycle - Creation
|
// Lifecycle - Creation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
@ -70,8 +73,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
if (updateFlags != 0) {
|
if (updateFlags != 0) {
|
||||||
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
||||||
final boolean useGrid = isGridLayout();
|
final boolean useGrid = isGridLayout();
|
||||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
itemsList.setLayoutManager(
|
||||||
itemListAdapter.setGridItemVariants(useGrid);
|
useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||||
|
itemListAdapter.setUseGridVariant(useGrid);
|
||||||
itemListAdapter.notifyDataSetChanged();
|
itemListAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
updateFlags = 0;
|
updateFlags = 0;
|
||||||
|
@ -94,7 +98,8 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
final Resources resources = activity.getResources();
|
final Resources resources = activity.getResources();
|
||||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||||
width += (24 * resources.getDisplayMetrics().density);
|
width += (24 * resources.getDisplayMetrics().density);
|
||||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
|
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
|
||||||
|
/ (double) width);
|
||||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||||
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
|
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
|
||||||
return lm;
|
return lm;
|
||||||
|
@ -105,7 +110,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
itemListAdapter = new LocalItemListAdapter(activity);
|
itemListAdapter = new LocalItemListAdapter(activity);
|
||||||
|
@ -114,9 +119,11 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
itemsList = rootView.findViewById(R.id.items_list);
|
itemsList = rootView.findViewById(R.id.items_list);
|
||||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||||
|
|
||||||
itemListAdapter.setGridItemVariants(useGrid);
|
itemListAdapter.setUseGridVariant(useGrid);
|
||||||
itemListAdapter.setHeader(headerRootView = getListHeader());
|
headerRootView = getListHeader();
|
||||||
itemListAdapter.setFooter(footerRootView = getListFooter());
|
itemListAdapter.setHeader(headerRootView);
|
||||||
|
footerRootView = getListFooter();
|
||||||
|
itemListAdapter.setFooter(footerRootView);
|
||||||
|
|
||||||
itemsList.setAdapter(itemListAdapter);
|
itemsList.setAdapter(itemListAdapter);
|
||||||
}
|
}
|
||||||
|
@ -131,13 +138,17 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu +
|
if (DEBUG) {
|
||||||
"], inflater = [" + inflater + "]");
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
|
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||||
|
}
|
||||||
|
|
||||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||||
if (supportActionBar == null) return;
|
if (supportActionBar == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +169,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startLoading(boolean forceLoad) {
|
public void startLoading(final boolean forceLoad) {
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
resetFragment();
|
resetFragment();
|
||||||
}
|
}
|
||||||
|
@ -166,24 +177,36 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
@Override
|
@Override
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
if (itemsList != null) animateView(itemsList, false, 200);
|
if (itemsList != null) {
|
||||||
if (headerRootView != null) animateView(headerRootView, false, 200);
|
animateView(itemsList, false, 200);
|
||||||
|
}
|
||||||
|
if (headerRootView != null) {
|
||||||
|
animateView(headerRootView, false, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideLoading() {
|
public void hideLoading() {
|
||||||
super.hideLoading();
|
super.hideLoading();
|
||||||
if (itemsList != null) animateView(itemsList, true, 200);
|
if (itemsList != null) {
|
||||||
if (headerRootView != null) animateView(headerRootView, true, 200);
|
animateView(itemsList, true, 200);
|
||||||
|
}
|
||||||
|
if (headerRootView != null) {
|
||||||
|
animateView(headerRootView, true, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showError(String message, boolean showRetryButton) {
|
public void showError(final String message, final boolean showRetryButton) {
|
||||||
super.showError(message, showRetryButton);
|
super.showError(message, showRetryButton);
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
|
|
||||||
if (itemsList != null) animateView(itemsList, false, 200);
|
if (itemsList != null) {
|
||||||
if (headerRootView != null) animateView(headerRootView, false, 200);
|
animateView(itemsList, false, 200);
|
||||||
|
}
|
||||||
|
if (headerRootView != null) {
|
||||||
|
animateView(headerRootView, false, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,14 +217,18 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showListFooter(final boolean show) {
|
public void showListFooter(final boolean show) {
|
||||||
if (itemsList == null) return;
|
if (itemsList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
itemsList.post(() -> {
|
itemsList.post(() -> {
|
||||||
if (itemListAdapter != null) itemListAdapter.showFooter(show);
|
if (itemListAdapter != null) {
|
||||||
|
itemListAdapter.showFooter(show);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(N result) {
|
public void handleNextItems(final N result) {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,30 +237,35 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void resetFragment() {
|
protected void resetFragment() {
|
||||||
if (itemListAdapter != null) itemListAdapter.clearStreamItemList();
|
if (itemListAdapter != null) {
|
||||||
|
itemListAdapter.clearStreamItemList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
resetFragment();
|
resetFragment();
|
||||||
return super.onError(exception);
|
return super.onError(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||||
|
final String key) {
|
||||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||||
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isGridLayout() {
|
protected boolean isGridLayout() {
|
||||||
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
|
final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
if ("auto".equals(list_mode)) {
|
.getString(getString(R.string.list_view_mode_key),
|
||||||
|
getString(R.string.list_view_mode_value));
|
||||||
|
if ("auto".equals(listMode)) {
|
||||||
final Configuration configuration = getResources().getConfiguration();
|
final Configuration configuration = getResources().getConfiguration();
|
||||||
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
||||||
} else {
|
} else {
|
||||||
return "grid".equals(list_mode);
|
return "grid".equals(listMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package org.schabi.newpipe.local;
|
package org.schabi.newpipe.local;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
public class HeaderFooterHolder extends RecyclerView.ViewHolder {
|
public class HeaderFooterHolder extends RecyclerView.ViewHolder {
|
||||||
public View view;
|
public View view;
|
||||||
|
|
||||||
public HeaderFooterHolder(View v) {
|
public HeaderFooterHolder(final View v) {
|
||||||
super(v);
|
super(v);
|
||||||
view = v;
|
view = v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,12 @@ import org.schabi.newpipe.util.OnClickGesture;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class LocalItemBuilder {
|
public class LocalItemBuilder {
|
||||||
private static final String TAG = LocalItemBuilder.class.toString();
|
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
|
|
||||||
private OnClickGesture<LocalItem> onSelectedListener;
|
private OnClickGesture<LocalItem> onSelectedListener;
|
||||||
|
|
||||||
public LocalItemBuilder(Context context) {
|
public LocalItemBuilder(final Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +52,7 @@ public class LocalItemBuilder {
|
||||||
return onSelectedListener;
|
return onSelectedListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnItemSelectedListener(OnClickGesture<LocalItem> listener) {
|
public void setOnItemSelectedListener(final OnClickGesture<LocalItem> listener) {
|
||||||
this.onSelectedListener = listener;
|
this.onSelectedListener = listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package org.schabi.newpipe.local;
|
package org.schabi.newpipe.local;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.LocalItem;
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
|
@ -50,7 +51,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
private static final String TAG = LocalItemListAdapter.class.getSimpleName();
|
private static final String TAG = LocalItemListAdapter.class.getSimpleName();
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
|
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
|
||||||
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
|
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
|
||||||
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
|
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
|
||||||
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
|
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
|
||||||
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
|
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
|
||||||
|
|
||||||
private final LocalItemBuilder localItemBuilder;
|
private final LocalItemBuilder localItemBuilder;
|
||||||
private final ArrayList<LocalItem> localItems;
|
private final ArrayList<LocalItem> localItems;
|
||||||
|
@ -76,7 +76,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
private View header = null;
|
private View header = null;
|
||||||
private View footer = null;
|
private View footer = null;
|
||||||
|
|
||||||
public LocalItemListAdapter(Context context) {
|
public LocalItemListAdapter(final Context context) {
|
||||||
recordManager = new HistoryRecordManager(context);
|
recordManager = new HistoryRecordManager(context);
|
||||||
localItemBuilder = new LocalItemBuilder(context);
|
localItemBuilder = new LocalItemBuilder(context);
|
||||||
localItems = new ArrayList<>();
|
localItems = new ArrayList<>();
|
||||||
|
@ -84,7 +84,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
Localization.getPreferredLocale(context));
|
Localization.getPreferredLocale(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedListener(OnClickGesture<LocalItem> listener) {
|
public void setSelectedListener(final OnClickGesture<LocalItem> listener) {
|
||||||
localItemBuilder.setOnItemSelectedListener(listener);
|
localItemBuilder.setOnItemSelectedListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,28 +92,34 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
localItemBuilder.setOnItemSelectedListener(null);
|
localItemBuilder.setOnItemSelectedListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addItems(@Nullable List<? extends LocalItem> data) {
|
public void addItems(@Nullable final List<? extends LocalItem> data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (DEBUG) Log.d(TAG, "addItems() before > localItems.size() = " +
|
if (DEBUG) {
|
||||||
localItems.size() + ", data.size() = " + data.size());
|
Log.d(TAG, "addItems() before > localItems.size() = "
|
||||||
|
+ localItems.size() + ", data.size() = " + data.size());
|
||||||
|
}
|
||||||
|
|
||||||
int offsetStart = sizeConsideringHeader();
|
int offsetStart = sizeConsideringHeader();
|
||||||
localItems.addAll(data);
|
localItems.addAll(data);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
|
if (DEBUG) {
|
||||||
", localItems.size() = " + localItems.size() +
|
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", "
|
||||||
", header = " + header + ", footer = " + footer +
|
+ "localItems.size() = " + localItems.size() + ", "
|
||||||
", showFooter = " + showFooter);
|
+ "header = " + header + ", footer = " + footer + ", "
|
||||||
|
+ "showFooter = " + showFooter);
|
||||||
|
}
|
||||||
notifyItemRangeInserted(offsetStart, data.size());
|
notifyItemRangeInserted(offsetStart, data.size());
|
||||||
|
|
||||||
if (footer != null && showFooter) {
|
if (footer != null && showFooter) {
|
||||||
int footerNow = sizeConsideringHeader();
|
int footerNow = sizeConsideringHeader();
|
||||||
notifyItemMoved(offsetStart, footerNow);
|
notifyItemMoved(offsetStart, footerNow);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
|
if (DEBUG) {
|
||||||
" to " + footerNow);
|
Log.d(TAG, "addItems() footer from " + offsetStart
|
||||||
|
+ " to " + footerNow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,12 +129,16 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
notifyItemRemoved(index + (header != null ? 1 : 0));
|
notifyItemRemoved(index + (header != null ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean swapItems(int fromAdapterPosition, int toAdapterPosition) {
|
public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) {
|
||||||
final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition);
|
final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition);
|
||||||
final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition);
|
final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition);
|
||||||
|
|
||||||
if (actualFrom < 0 || actualTo < 0) return false;
|
if (actualFrom < 0 || actualTo < 0) {
|
||||||
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) return false;
|
return false;
|
||||||
|
}
|
||||||
|
if (actualFrom >= localItems.size() || actualTo >= localItems.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
localItems.add(actualTo, localItems.remove(actualFrom));
|
localItems.add(actualTo, localItems.remove(actualFrom));
|
||||||
notifyItemMoved(fromAdapterPosition, toAdapterPosition);
|
notifyItemMoved(fromAdapterPosition, toAdapterPosition);
|
||||||
|
@ -143,27 +153,36 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGridItemVariants(boolean useGridVariant) {
|
public void setUseGridVariant(final boolean useGridVariant) {
|
||||||
this.useGridVariant = useGridVariant;
|
this.useGridVariant = useGridVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeader(View header) {
|
public void setHeader(final View header) {
|
||||||
boolean changed = header != this.header;
|
boolean changed = header != this.header;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
if (changed) notifyDataSetChanged();
|
if (changed) {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFooter(View view) {
|
public void setFooter(final View view) {
|
||||||
this.footer = view;
|
this.footer = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showFooter(boolean show) {
|
public void showFooter(final boolean show) {
|
||||||
if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]");
|
if (DEBUG) {
|
||||||
if (show == showFooter) return;
|
Log.d(TAG, "showFooter() called with: show = [" + show + "]");
|
||||||
|
}
|
||||||
|
if (show == showFooter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
showFooter = show;
|
showFooter = show;
|
||||||
if (show) notifyItemInserted(sizeConsideringHeader());
|
if (show) {
|
||||||
else notifyItemRemoved(sizeConsideringHeader());
|
notifyItemInserted(sizeConsideringHeader());
|
||||||
|
} else {
|
||||||
|
notifyItemRemoved(sizeConsideringHeader());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int adapterOffsetWithoutHeader(final int offset) {
|
private int adapterOffsetWithoutHeader(final int offset) {
|
||||||
|
@ -181,21 +200,27 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
int count = localItems.size();
|
int count = localItems.size();
|
||||||
if (header != null) count++;
|
if (header != null) {
|
||||||
if (footer != null && showFooter) count++;
|
count++;
|
||||||
|
}
|
||||||
|
if (footer != null && showFooter) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "getItemCount() called, count = " + count +
|
Log.d(TAG, "getItemCount() called, count = " + count + ", "
|
||||||
", localItems.size() = " + localItems.size() +
|
+ "localItems.size() = " + localItems.size() + ", "
|
||||||
", header = " + header + ", footer = " + footer +
|
+ "header = " + header + ", footer = " + footer + ", "
|
||||||
", showFooter = " + showFooter);
|
+ "showFooter = " + showFooter);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
|
||||||
|
}
|
||||||
|
|
||||||
if (header != null && position == 0) {
|
if (header != null && position == 0) {
|
||||||
return HEADER_TYPE;
|
return HEADER_TYPE;
|
||||||
|
@ -208,23 +233,34 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
final LocalItem item = localItems.get(position);
|
final LocalItem item = localItems.get(position);
|
||||||
|
|
||||||
switch (item.getLocalItemType()) {
|
switch (item.getLocalItemType()) {
|
||||||
case PLAYLIST_LOCAL_ITEM: return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
|
case PLAYLIST_LOCAL_ITEM:
|
||||||
case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
|
return useGridVariant
|
||||||
|
? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
|
||||||
|
case PLAYLIST_REMOTE_ITEM:
|
||||||
|
return useGridVariant
|
||||||
|
? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
|
||||||
|
|
||||||
case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
|
case PLAYLIST_STREAM_ITEM:
|
||||||
case STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
|
return useGridVariant
|
||||||
|
? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
|
||||||
|
case STATISTIC_STREAM_ITEM:
|
||||||
|
return useGridVariant
|
||||||
|
? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "No holder type has been considered for item: [" +
|
Log.e(TAG, "No holder type has been considered for item: ["
|
||||||
item.getLocalItemType() + "]");
|
+ item.getLocalItemType() + "]");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
|
||||||
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" +
|
final int type) {
|
||||||
parent + "], type = [" + type + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreateViewHolder() called with: "
|
||||||
|
+ "parent = [" + parent + "], type = [" + type + "]");
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case HEADER_TYPE:
|
case HEADER_TYPE:
|
||||||
return new HeaderFooterHolder(header);
|
return new HeaderFooterHolder(header);
|
||||||
|
@ -253,15 +289,21 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
|
||||||
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" +
|
if (DEBUG) {
|
||||||
holder.getClass().getSimpleName() + "], position = [" + position + "]");
|
Log.d(TAG, "onBindViewHolder() called with: "
|
||||||
|
+ "holder = [" + holder.getClass().getSimpleName() + "], "
|
||||||
|
+ "position = [" + position + "]");
|
||||||
|
}
|
||||||
|
|
||||||
if (holder instanceof LocalItemHolder) {
|
if (holder instanceof LocalItemHolder) {
|
||||||
// If header isn't null, offset the items by -1
|
// If header isn't null, offset the items by -1
|
||||||
if (header != null) position--;
|
if (header != null) {
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
|
||||||
((LocalItemHolder) holder).updateFromItem(localItems.get(position), recordManager, dateFormat);
|
((LocalItemHolder) holder)
|
||||||
|
.updateFromItem(localItems.get(position), recordManager, dateFormat);
|
||||||
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
|
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
|
||||||
((HeaderFooterHolder) holder).view = header;
|
((HeaderFooterHolder) holder).view = header;
|
||||||
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
|
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
|
||||||
|
@ -271,13 +313,16 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||||
|
@NonNull final List<Object> payloads) {
|
||||||
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
||||||
for (Object payload : payloads) {
|
for (Object payload : payloads) {
|
||||||
if (payload instanceof StreamStateEntity) {
|
if (payload instanceof StreamStateEntity) {
|
||||||
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager);
|
((LocalItemHolder) holder).updateState(localItems
|
||||||
|
.get(header == null ? position : position - 1), recordManager);
|
||||||
} else if (payload instanceof Boolean) {
|
} else if (payload instanceof Boolean) {
|
||||||
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager);
|
((LocalItemHolder) holder).updateState(localItems
|
||||||
|
.get(header == null ? position : position - 1), recordManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -288,7 +333,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
||||||
return new GridLayoutManager.SpanSizeLookup() {
|
return new GridLayoutManager.SpanSizeLookup() {
|
||||||
@Override
|
@Override
|
||||||
public int getSpanSize(int position) {
|
public int getSpanSize(final int position) {
|
||||||
final int type = getItemViewType(position);
|
final int type = getItemViewType(position);
|
||||||
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
|
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,15 @@ import android.app.AlertDialog.Builder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.EditText;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
|
@ -39,10 +39,9 @@ import io.reactivex.Flowable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
public final class BookmarkFragment
|
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
|
||||||
extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected Parcelable itemsListState;
|
protected Parcelable itemsListState;
|
||||||
|
|
||||||
|
@ -55,10 +54,26 @@ public final class BookmarkFragment
|
||||||
// Fragment LifeCycle - Creation
|
// Fragment LifeCycle - Creation
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private static List<PlaylistLocalItem> merge(
|
||||||
|
final List<PlaylistMetadataEntry> localPlaylists,
|
||||||
|
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||||
|
List<PlaylistLocalItem> items = new ArrayList<>(
|
||||||
|
localPlaylists.size() + remotePlaylists.size());
|
||||||
|
items.addAll(localPlaylists);
|
||||||
|
items.addAll(remotePlaylists);
|
||||||
|
|
||||||
|
Collections.sort(items, (left, right) ->
|
||||||
|
left.getOrderingName().compareToIgnoreCase(right.getOrderingName()));
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (activity == null) return;
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final AppDatabase database = NewPipeDatabase.getInstance(activity);
|
final AppDatabase database = NewPipeDatabase.getInstance(activity);
|
||||||
localPlaylistManager = new LocalPlaylistManager(database);
|
localPlaylistManager = new LocalPlaylistManager(database);
|
||||||
remotePlaylistManager = new RemotePlaylistManager(database);
|
remotePlaylistManager = new RemotePlaylistManager(database);
|
||||||
|
@ -67,41 +82,44 @@ public final class BookmarkFragment
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
@Nullable ViewGroup container,
|
@Nullable final ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
|
|
||||||
if(!useAsFrontPage) {
|
if (!useAsFrontPage) {
|
||||||
setTitle(activity.getString(R.string.tab_bookmarks));
|
setTitle(activity.getString(R.string.tab_bookmarks));
|
||||||
}
|
}
|
||||||
return inflater.inflate(R.layout.fragment_bookmarks, container, false);
|
return inflater.inflate(R.layout.fragment_bookmarks, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
|
||||||
if (activity != null && isVisibleToUser) {
|
|
||||||
setTitle(activity.getString(R.string.tab_bookmarks));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle - Views
|
// Fragment LifeCycle - Views
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
|
if (activity != null && isVisibleToUser) {
|
||||||
|
setTitle(activity.getString(R.string.tab_bookmarks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment LifeCycle - Loading
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
super.initListeners();
|
super.initListeners();
|
||||||
|
|
||||||
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(LocalItem selectedItem) {
|
public void selected(final LocalItem selectedItem) {
|
||||||
final FragmentManager fragmentManager = getFM();
|
final FragmentManager fragmentManager = getFM();
|
||||||
|
|
||||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||||
|
@ -120,7 +138,7 @@ public final class BookmarkFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void held(LocalItem selectedItem) {
|
public void held(final LocalItem selectedItem) {
|
||||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||||
showLocalDialog((PlaylistMetadataEntry) selectedItem);
|
showLocalDialog((PlaylistMetadataEntry) selectedItem);
|
||||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||||
|
@ -131,26 +149,20 @@ public final class BookmarkFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle - Loading
|
// Fragment LifeCycle - Destruction
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startLoading(boolean forceLoad) {
|
public void startLoading(final boolean forceLoad) {
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
|
|
||||||
Flowable.combineLatest(
|
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||||
localPlaylistManager.getPlaylists(),
|
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
|
||||||
remotePlaylistManager.getPlaylists(),
|
.onBackpressureLatest()
|
||||||
BookmarkFragment::merge
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
).onBackpressureLatest()
|
.subscribe(getPlaylistsSubscriber());
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(getPlaylistsSubscriber());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Fragment LifeCycle - Destruction
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
@ -161,16 +173,26 @@ public final class BookmarkFragment
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|
||||||
if (disposables != null) disposables.clear();
|
if (disposables != null) {
|
||||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
disposables.clear();
|
||||||
|
}
|
||||||
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
databaseSubscription = null;
|
databaseSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Subscriptions Loader
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (disposables != null) disposables.dispose();
|
if (disposables != null) {
|
||||||
|
disposables.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
disposables = null;
|
disposables = null;
|
||||||
localPlaylistManager = null;
|
localPlaylistManager = null;
|
||||||
|
@ -178,39 +200,41 @@ public final class BookmarkFragment
|
||||||
itemsListState = null;
|
itemsListState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Subscriptions Loader
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private Subscriber<List<PlaylistLocalItem>> getPlaylistsSubscriber() {
|
private Subscriber<List<PlaylistLocalItem>> getPlaylistsSubscriber() {
|
||||||
return new Subscriber<List<PlaylistLocalItem>>() {
|
return new Subscriber<List<PlaylistLocalItem>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Subscription s) {
|
public void onSubscribe(final Subscription s) {
|
||||||
showLoading();
|
showLoading();
|
||||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.cancel();
|
||||||
|
}
|
||||||
databaseSubscription = s;
|
databaseSubscription = s;
|
||||||
databaseSubscription.request(1);
|
databaseSubscription.request(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(List<PlaylistLocalItem> subscriptions) {
|
public void onNext(final List<PlaylistLocalItem> subscriptions) {
|
||||||
handleResult(subscriptions);
|
handleResult(subscriptions);
|
||||||
if (databaseSubscription != null) databaseSubscription.request(1);
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.request(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
BookmarkFragment.this.onError(exception);
|
BookmarkFragment.this.onError(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() { }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment Error Handling
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull List<PlaylistLocalItem> result) {
|
public void handleResult(@NonNull final List<PlaylistLocalItem> result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
itemListAdapter.clearStreamItemList();
|
itemListAdapter.clearStreamItemList();
|
||||||
|
@ -227,55 +251,58 @@ public final class BookmarkFragment
|
||||||
}
|
}
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Fragment Error Handling
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(Throwable exception) {
|
protected boolean onError(final Throwable exception) {
|
||||||
if (super.onError(exception)) return true;
|
if (super.onError(exception)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
||||||
"none", "Bookmark", R.string.general_error);
|
"none", "Bookmark", R.string.general_error);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void resetFragment() {
|
|
||||||
super.resetFragment();
|
|
||||||
if (disposables != null) disposables.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetFragment() {
|
||||||
|
super.resetFragment();
|
||||||
|
if (disposables != null) {
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
|
private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
|
||||||
showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid()));
|
showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLocalDialog(PlaylistMetadataEntry selectedItem) {
|
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
|
||||||
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
||||||
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||||
editText.setText(selectedItem.name);
|
editText.setText(selectedItem.name);
|
||||||
|
|
||||||
Builder builder = new AlertDialog.Builder(activity);
|
Builder builder = new AlertDialog.Builder(activity);
|
||||||
builder.setView(dialogView)
|
builder.setView(dialogView)
|
||||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
|
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
|
||||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
|
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
||||||
showDeleteDialog(selectedItem.name,
|
showDeleteDialog(selectedItem.name,
|
||||||
localPlaylistManager.deletePlaylist(selectedItem.uid));
|
localPlaylistManager.deletePlaylist(selectedItem.uid));
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
})
|
})
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) {
|
private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) {
|
||||||
if (activity == null || disposables == null) return;
|
if (activity == null || disposables == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle(name)
|
.setTitle(name)
|
||||||
|
@ -284,40 +311,27 @@ public final class BookmarkFragment
|
||||||
.setPositiveButton(R.string.delete, (dialog, i) ->
|
.setPositiveButton(R.string.delete, (dialog, i) ->
|
||||||
disposables.add(deleteReactor
|
disposables.add(deleteReactor
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> {/*Do nothing on success*/}, this::onError))
|
.subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
|
||||||
)
|
)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeLocalPlaylistName(long id, String name) {
|
private void changeLocalPlaylistName(final long id, final String name) {
|
||||||
if (localPlaylistManager == null) {
|
if (localPlaylistManager == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Updating playlist id=[" + id +
|
Log.d(TAG, "Updating playlist id=[" + id + "] "
|
||||||
"] with new name=[" + name + "] items");
|
+ "with new name=[" + name + "] items");
|
||||||
}
|
}
|
||||||
|
|
||||||
localPlaylistManager.renamePlaylist(id, name);
|
localPlaylistManager.renamePlaylist(id, name);
|
||||||
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
|
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(longs -> {/*Do nothing on success*/}, this::onError);
|
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<PlaylistLocalItem> merge(final List<PlaylistMetadataEntry> localPlaylists,
|
|
||||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
|
||||||
List<PlaylistLocalItem> items = new ArrayList<>(
|
|
||||||
localPlaylists.size() + remotePlaylists.size());
|
|
||||||
items.addAll(localPlaylists);
|
|
||||||
items.addAll(remotePlaylists);
|
|
||||||
|
|
||||||
Collections.sort(items, (left, right) ->
|
|
||||||
left.getOrderingName().compareToIgnoreCase(right.getOrderingName()));
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,13 +69,13 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.dialog_playlists, container);
|
return inflater.inflate(R.layout.dialog_playlists, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
final LocalPlaylistManager playlistManager =
|
final LocalPlaylistManager playlistManager =
|
||||||
|
@ -84,9 +84,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||||
playlistAdapter = new LocalItemListAdapter(getActivity());
|
playlistAdapter = new LocalItemListAdapter(getActivity());
|
||||||
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(LocalItem selectedItem) {
|
public void selected(final LocalItem selectedItem) {
|
||||||
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null)
|
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem,
|
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem,
|
||||||
getStreams());
|
getStreams());
|
||||||
}
|
}
|
||||||
|
@ -126,7 +127,9 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public void openCreatePlaylistDialog() {
|
public void openCreatePlaylistDialog() {
|
||||||
if (getStreams() == null || getFragmentManager() == null) return;
|
if (getStreams() == null || getFragmentManager() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG);
|
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG);
|
||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
|
@ -145,16 +148,19 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPlaylistSelected(@NonNull LocalPlaylistManager manager,
|
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
|
||||||
@NonNull PlaylistMetadataEntry playlist,
|
@NonNull final PlaylistMetadataEntry playlist,
|
||||||
@NonNull List<StreamEntity> streams) {
|
@NonNull final List<StreamEntity> streams) {
|
||||||
if (getStreams() == null) return;
|
if (getStreams() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Toast successToast = Toast.makeText(getContext(),
|
final Toast successToast = Toast.makeText(getContext(),
|
||||||
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
|
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
|
||||||
|
|
||||||
if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) {
|
if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) {
|
||||||
playlistDisposables.add(manager.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl())
|
playlistDisposables.add(manager
|
||||||
|
.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> successToast.show()));
|
.subscribe(ignored -> successToast.show()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@ package org.schabi.newpipe.local.dialog;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
@ -19,8 +20,6 @@ import java.util.List;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
|
||||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||||
private static final String TAG = PlaylistCreationDialog.class.getCanonicalName();
|
|
||||||
|
|
||||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
||||||
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||||
dialog.setInfo(streams);
|
dialog.setInfo(streams);
|
||||||
|
@ -33,8 +32,10 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||||
if (getStreams() == null) return super.onCreateDialog(savedInstanceState);
|
if (getStreams() == null) {
|
||||||
|
return super.onCreateDialog(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||||
EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||||
|
|
|
@ -2,10 +2,11 @@ package org.schabi.newpipe.local.dialog;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.Window;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import android.view.Window;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
|
@ -14,7 +15,6 @@ import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
|
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
|
||||||
|
|
||||||
private List<StreamEntity> streamEntities;
|
private List<StreamEntity> streamEntities;
|
||||||
|
|
||||||
private StateSaver.SavedState savedState;
|
private StateSaver.SavedState savedState;
|
||||||
|
@ -32,7 +32,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
savedState = StateSaver.tryToRestore(savedInstanceState, this);
|
savedState = StateSaver.tryToRestore(savedInstanceState, this);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||||
final Dialog dialog = super.onCreateDialog(savedInstanceState);
|
final Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||||
//remove title
|
//remove title
|
||||||
final Window window = dialog.getWindow();
|
final Window window = dialog.getWindow();
|
||||||
|
@ -66,18 +66,18 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(Queue<Object> objectsToSave) {
|
public void writeTo(final Queue<Object> objectsToSave) {
|
||||||
objectsToSave.add(streamEntities);
|
objectsToSave.add(streamEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void readFrom(@NonNull Queue<Object> savedObjects) {
|
public void readFrom(@NonNull final Queue<Object> savedObjects) {
|
||||||
streamEntities = (List<StreamEntity>) savedObjects.poll();
|
streamEntities = (List<StreamEntity>) savedObjects.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
if (getActivity() != null) {
|
if (getActivity() != null) {
|
||||||
savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(),
|
savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(),
|
||||||
|
|
|
@ -41,7 +41,9 @@ import java.util.*
|
||||||
|
|
||||||
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
private lateinit var viewModel: FeedViewModel
|
private lateinit var viewModel: FeedViewModel
|
||||||
@State @JvmField var listState: Parcelable? = null
|
@State
|
||||||
|
@JvmField
|
||||||
|
var listState: Parcelable? = null
|
||||||
|
|
||||||
private var groupId = FeedGroupEntity.GROUP_ALL_ID
|
private var groupId = FeedGroupEntity.GROUP_ALL_ID
|
||||||
private var groupName = ""
|
private var groupName = ""
|
||||||
|
@ -49,13 +51,14 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
useDefaultStateSaving(false)
|
setUseDefaultStateSaving(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) ?: FeedGroupEntity.GROUP_ALL_ID
|
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
|
||||||
|
?: FeedGroupEntity.GROUP_ALL_ID
|
||||||
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
|
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +110,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
inflater.inflate(R.menu.menu_feed_fragment, menu)
|
inflater.inflate(R.menu.menu_feed_fragment, menu)
|
||||||
|
|
||||||
if (useAsFrontPage) {
|
if (useAsFrontPage) {
|
||||||
menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.schabi.newpipe.local.history;
|
package org.schabi.newpipe.local.history;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -14,19 +15,19 @@ import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter for history entries
|
* This is an adapter for history entries.
|
||||||
* @param <E> the type of the entries
|
*
|
||||||
|
* @param <E> the type of the entries
|
||||||
* @param <VH> the type of the view holder
|
* @param <VH> the type of the view holder
|
||||||
*/
|
*/
|
||||||
public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
|
public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
|
||||||
|
extends RecyclerView.Adapter<VH> {
|
||||||
private final ArrayList<E> mEntries;
|
private final ArrayList<E> mEntries;
|
||||||
private final DateFormat mDateFormat;
|
private final DateFormat mDateFormat;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private OnHistoryItemClickListener<E> onHistoryItemClickListener = null;
|
private OnHistoryItemClickListener<E> onHistoryItemClickListener = null;
|
||||||
|
|
||||||
|
public HistoryEntryAdapter(final Context context) {
|
||||||
public HistoryEntryAdapter(Context context) {
|
|
||||||
super();
|
super();
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mEntries = new ArrayList<>();
|
mEntries = new ArrayList<>();
|
||||||
|
@ -34,7 +35,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
|
||||||
Localization.getPreferredLocale(context));
|
Localization.getPreferredLocale(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEntries(@NonNull Collection<E> historyEntries) {
|
public void setEntries(@NonNull final Collection<E> historyEntries) {
|
||||||
mEntries.clear();
|
mEntries.clear();
|
||||||
mEntries.addAll(historyEntries);
|
mEntries.addAll(historyEntries);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
|
@ -49,7 +50,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getFormattedDate(Date date) {
|
protected String getFormattedDate(final Date date) {
|
||||||
return mDateFormat.format(date);
|
return mDateFormat.format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +64,10 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(VH holder, int position) {
|
public void onBindViewHolder(final VH holder, final int position) {
|
||||||
final E entry = mEntries.get(position);
|
final E entry = mEntries.get(position);
|
||||||
holder.itemView.setOnClickListener(v -> {
|
holder.itemView.setOnClickListener(v -> {
|
||||||
if(onHistoryItemClickListener != null) {
|
if (onHistoryItemClickListener != null) {
|
||||||
onHistoryItemClickListener.onHistoryItemClick(entry);
|
onHistoryItemClickListener.onHistoryItemClick(entry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -83,14 +84,15 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewRecycled(VH holder) {
|
public void onViewRecycled(final VH holder) {
|
||||||
super.onViewRecycled(holder);
|
super.onViewRecycled(holder);
|
||||||
holder.itemView.setOnClickListener(null);
|
holder.itemView.setOnClickListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void onBindViewHolder(VH holder, E entry, int position);
|
abstract void onBindViewHolder(VH holder, E entry, int position);
|
||||||
|
|
||||||
public void setOnHistoryItemClickListener(@Nullable OnHistoryItemClickListener<E> onHistoryItemClickListener) {
|
public void setOnHistoryItemClickListener(
|
||||||
|
@Nullable final OnHistoryItemClickListener<E> onHistoryItemClickListener) {
|
||||||
this.onHistoryItemClickListener = onHistoryItemClickListener;
|
this.onHistoryItemClickListener = onHistoryItemClickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +102,7 @@ public abstract class HistoryEntryAdapter<E, VH extends RecyclerView.ViewHolder>
|
||||||
|
|
||||||
public interface OnHistoryItemClickListener<E> {
|
public interface OnHistoryItemClickListener<E> {
|
||||||
void onHistoryItemClick(E item);
|
void onHistoryItemClick(E item);
|
||||||
|
|
||||||
void onHistoryItemLongClick(E item);
|
void onHistoryItemLongClick(E item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -21,6 +21,7 @@ package org.schabi.newpipe.local.history;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
|
@ -55,7 +56,6 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
public class HistoryRecordManager {
|
public class HistoryRecordManager {
|
||||||
|
|
||||||
private final AppDatabase database;
|
private final AppDatabase database;
|
||||||
private final StreamDAO streamTable;
|
private final StreamDAO streamTable;
|
||||||
private final StreamHistoryDAO streamHistoryTable;
|
private final StreamHistoryDAO streamHistoryTable;
|
||||||
|
@ -81,7 +81,9 @@ public class HistoryRecordManager {
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Maybe<Long> onViewed(final StreamInfo info) {
|
public Maybe<Long> onViewed(final StreamInfo info) {
|
||||||
if (!isStreamHistoryEnabled()) return Maybe.empty();
|
if (!isStreamHistoryEnabled()) {
|
||||||
|
return Maybe.empty();
|
||||||
|
}
|
||||||
|
|
||||||
final Date currentTime = new Date();
|
final Date currentTime = new Date();
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
|
@ -149,7 +151,9 @@ public class HistoryRecordManager {
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Maybe<Long> onSearched(final int serviceId, final String search) {
|
public Maybe<Long> onSearched(final int serviceId, final String search) {
|
||||||
if (!isSearchHistoryEnabled()) return Maybe.empty();
|
if (!isSearchHistoryEnabled()) {
|
||||||
|
return Maybe.empty();
|
||||||
|
}
|
||||||
|
|
||||||
final Date currentTime = new Date();
|
final Date currentTime = new Date();
|
||||||
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
||||||
|
@ -231,11 +235,13 @@ public class HistoryRecordManager {
|
||||||
|
|
||||||
public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) {
|
public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
final List<StreamEntity> entities = streamTable
|
||||||
|
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||||
if (entities.isEmpty()) {
|
if (entities.isEmpty()) {
|
||||||
return new StreamStateEntity[]{null};
|
return new StreamStateEntity[]{null};
|
||||||
}
|
}
|
||||||
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
|
final List<StreamStateEntity> states = streamStateTable
|
||||||
|
.getState(entities.get(0).getUid()).blockingFirst();
|
||||||
if (states.isEmpty()) {
|
if (states.isEmpty()) {
|
||||||
return new StreamStateEntity[]{null};
|
return new StreamStateEntity[]{null};
|
||||||
}
|
}
|
||||||
|
@ -247,12 +253,14 @@ public class HistoryRecordManager {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
||||||
for (InfoItem info : infos) {
|
for (InfoItem info : infos) {
|
||||||
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
final List<StreamEntity> entities = streamTable
|
||||||
|
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||||
if (entities.isEmpty()) {
|
if (entities.isEmpty()) {
|
||||||
result.add(null);
|
result.add(null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
|
final List<StreamStateEntity> states = streamStateTable
|
||||||
|
.getState(entities.get(0).getUid()).blockingFirst();
|
||||||
if (states.isEmpty()) {
|
if (states.isEmpty()) {
|
||||||
result.add(null);
|
result.add(null);
|
||||||
continue;
|
continue;
|
||||||
|
@ -263,7 +271,8 @@ public class HistoryRecordManager {
|
||||||
}).subscribeOn(Schedulers.io());
|
}).subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(final List<? extends LocalItem> items) {
|
public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(
|
||||||
|
final List<? extends LocalItem> items) {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
||||||
for (LocalItem item : items) {
|
for (LocalItem item : items) {
|
||||||
|
@ -278,7 +287,8 @@ public class HistoryRecordManager {
|
||||||
result.add(null);
|
result.add(null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final List<StreamStateEntity> states = streamStateTable.getState(streamId).blockingFirst();
|
final List<StreamStateEntity> states = streamStateTable.getState(streamId)
|
||||||
|
.blockingFirst();
|
||||||
if (states.isEmpty()) {
|
if (states.isEmpty()) {
|
||||||
result.add(null);
|
result.add(null);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -4,10 +4,6 @@ import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -18,6 +14,12 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -48,7 +50,10 @@ import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
public class StatisticsPlaylistFragment
|
public class StatisticsPlaylistFragment
|
||||||
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
|
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
|
||||||
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
@State
|
||||||
|
Parcelable itemsListState;
|
||||||
|
private StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
|
||||||
private View headerPlayAllButton;
|
private View headerPlayAllButton;
|
||||||
private View headerPopupButton;
|
private View headerPopupButton;
|
||||||
private View headerBackgroundButton;
|
private View headerBackgroundButton;
|
||||||
|
@ -56,55 +61,44 @@ public class StatisticsPlaylistFragment
|
||||||
private View sortButton;
|
private View sortButton;
|
||||||
private ImageView sortButtonIcon;
|
private ImageView sortButtonIcon;
|
||||||
private TextView sortButtonText;
|
private TextView sortButtonText;
|
||||||
|
|
||||||
@State
|
|
||||||
protected Parcelable itemsListState;
|
|
||||||
|
|
||||||
/* Used for independent events */
|
/* Used for independent events */
|
||||||
private Subscription databaseSubscription;
|
private Subscription databaseSubscription;
|
||||||
private HistoryRecordManager recordManager;
|
private HistoryRecordManager recordManager;
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
|
||||||
|
|
||||||
private enum StatisticSortMode {
|
private List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
|
||||||
LAST_PLAYED,
|
|
||||||
MOST_PLAYED,
|
|
||||||
}
|
|
||||||
|
|
||||||
StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
|
|
||||||
|
|
||||||
protected List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
|
|
||||||
switch (sortMode) {
|
switch (sortMode) {
|
||||||
case LAST_PLAYED:
|
case LAST_PLAYED:
|
||||||
Collections.sort(results, (left, right) ->
|
Collections.sort(results, (left, right) ->
|
||||||
right.getLatestAccessDate().compareTo(left.getLatestAccessDate()));
|
right.getLatestAccessDate().compareTo(left.getLatestAccessDate()));
|
||||||
return results;
|
return results;
|
||||||
case MOST_PLAYED:
|
case MOST_PLAYED:
|
||||||
Collections.sort(results, (left, right) ->
|
Collections.sort(results, (left, right) ->
|
||||||
Long.compare(right.getWatchCount(), left.getWatchCount()));
|
Long.compare(right.getWatchCount(), left.getWatchCount()));
|
||||||
return results;
|
return results;
|
||||||
default: return null;
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
recordManager = new HistoryRecordManager(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle - Creation
|
// Fragment LifeCycle - Creation
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
super.onCreate(savedInstanceState);
|
@Nullable final ViewGroup container,
|
||||||
recordManager = new HistoryRecordManager(getContext());
|
@Nullable final Bundle savedInstanceState) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
if (activity != null && isVisibleToUser) {
|
if (activity != null && isVisibleToUser) {
|
||||||
setTitle(activity.getString(R.string.title_activity_history));
|
setTitle(activity.getString(R.string.title_activity_history));
|
||||||
|
@ -112,27 +106,27 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
inflater.inflate(R.menu.menu_history, menu);
|
inflater.inflate(R.menu.menu_history, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
if (!useAsFrontPage) {
|
||||||
|
setTitle(getString(R.string.title_last_played));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle - Views
|
// Fragment LifeCycle - Views
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
|
||||||
super.initViews(rootView, savedInstanceState);
|
|
||||||
if(!useAsFrontPage) {
|
|
||||||
setTitle(getString(R.string.title_last_played));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getListHeader() {
|
protected View getListHeader() {
|
||||||
final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.statistic_playlist_control,
|
final View headerRootLayout = activity.getLayoutInflater()
|
||||||
itemsList, false);
|
.inflate(R.layout.statistic_playlist_control, itemsList, false);
|
||||||
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||||
|
@ -149,7 +143,7 @@ public class StatisticsPlaylistFragment
|
||||||
|
|
||||||
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(LocalItem selectedItem) {
|
public void selected(final LocalItem selectedItem) {
|
||||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
|
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
|
||||||
NavigationHelper.openVideoDetailFragment(getFM(),
|
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||||
|
@ -160,7 +154,7 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void held(LocalItem selectedItem) {
|
public void held(final LocalItem selectedItem) {
|
||||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||||
showStreamDialog((StreamStatisticsEntry) selectedItem);
|
showStreamDialog((StreamStatisticsEntry) selectedItem);
|
||||||
}
|
}
|
||||||
|
@ -169,7 +163,7 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_history_clear:
|
case R.id.action_history_clear:
|
||||||
new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
|
@ -194,7 +188,8 @@ public class StatisticsPlaylistFragment
|
||||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
howManyDeleted -> {},
|
howManyDeleted -> {
|
||||||
|
},
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
throwable -> ErrorActivity.reportError(getContext(),
|
||||||
throwable,
|
throwable,
|
||||||
SettingsActivity.class, null,
|
SettingsActivity.class, null,
|
||||||
|
@ -215,12 +210,8 @@ public class StatisticsPlaylistFragment
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Fragment LifeCycle - Loading
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startLoading(boolean forceLoad) {
|
public void startLoading(final boolean forceLoad) {
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
recordManager.getStreamStatistics()
|
recordManager.getStreamStatistics()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -228,7 +219,7 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle - Destruction
|
// Fragment LifeCycle - Loading
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -237,16 +228,30 @@ public class StatisticsPlaylistFragment
|
||||||
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Fragment LifeCycle - Destruction
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|
||||||
if (itemListAdapter != null) itemListAdapter.unsetSelectedListener();
|
if (itemListAdapter != null) {
|
||||||
if (headerBackgroundButton != null) headerBackgroundButton.setOnClickListener(null);
|
itemListAdapter.unsetSelectedListener();
|
||||||
if (headerPlayAllButton != null) headerPlayAllButton.setOnClickListener(null);
|
}
|
||||||
if (headerPopupButton != null) headerPopupButton.setOnClickListener(null);
|
if (headerBackgroundButton != null) {
|
||||||
|
headerBackgroundButton.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
if (headerPlayAllButton != null) {
|
||||||
|
headerPlayAllButton.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
if (headerPopupButton != null) {
|
||||||
|
headerPopupButton.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
|
||||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.cancel();
|
||||||
|
}
|
||||||
databaseSubscription = null;
|
databaseSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,29 +262,29 @@ public class StatisticsPlaylistFragment
|
||||||
itemsListState = null;
|
itemsListState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Statistics Loader
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() {
|
private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() {
|
||||||
return new Subscriber<List<StreamStatisticsEntry>>() {
|
return new Subscriber<List<StreamStatisticsEntry>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(Subscription s) {
|
public void onSubscribe(final Subscription s) {
|
||||||
showLoading();
|
showLoading();
|
||||||
|
|
||||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.cancel();
|
||||||
|
}
|
||||||
databaseSubscription = s;
|
databaseSubscription = s;
|
||||||
databaseSubscription.request(1);
|
databaseSubscription.request(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(List<StreamStatisticsEntry> streams) {
|
public void onNext(final List<StreamStatisticsEntry> streams) {
|
||||||
handleResult(streams);
|
handleResult(streams);
|
||||||
if (databaseSubscription != null) databaseSubscription.request(1);
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.request(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
StatisticsPlaylistFragment.this.onError(exception);
|
StatisticsPlaylistFragment.this.onError(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,10 +294,16 @@ public class StatisticsPlaylistFragment
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Statistics Loader
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull List<StreamStatisticsEntry> result) {
|
public void handleResult(@NonNull final List<StreamStatisticsEntry> result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
if (itemListAdapter == null) return;
|
if (itemListAdapter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
playlistCtrl.setVisibility(View.VISIBLE);
|
playlistCtrl.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
@ -319,52 +330,60 @@ public class StatisticsPlaylistFragment
|
||||||
|
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetFragment() {
|
||||||
|
super.resetFragment();
|
||||||
|
if (databaseSubscription != null) {
|
||||||
|
databaseSubscription.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment Error Handling
|
// Fragment Error Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetFragment() {
|
protected boolean onError(final Throwable exception) {
|
||||||
super.resetFragment();
|
if (super.onError(exception)) {
|
||||||
if (databaseSubscription != null) databaseSubscription.cancel();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(Throwable exception) {
|
|
||||||
if (super.onError(exception)) return true;
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
||||||
"none", "History Statistics", R.string.general_error);
|
"none", "History Statistics", R.string.general_error);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Utils
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private void toggleSortMode() {
|
private void toggleSortMode() {
|
||||||
if(sortMode == StatisticSortMode.LAST_PLAYED) {
|
if (sortMode == StatisticSortMode.LAST_PLAYED) {
|
||||||
sortMode = StatisticSortMode.MOST_PLAYED;
|
sortMode = StatisticSortMode.MOST_PLAYED;
|
||||||
setTitle(getString(R.string.title_most_played));
|
setTitle(getString(R.string.title_most_played));
|
||||||
sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
|
sortButtonIcon
|
||||||
|
.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
|
||||||
sortButtonText.setText(R.string.title_last_played);
|
sortButtonText.setText(R.string.title_last_played);
|
||||||
} else {
|
} else {
|
||||||
sortMode = StatisticSortMode.LAST_PLAYED;
|
sortMode = StatisticSortMode.LAST_PLAYED;
|
||||||
setTitle(getString(R.string.title_last_played));
|
setTitle(getString(R.string.title_last_played));
|
||||||
sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
|
sortButtonIcon
|
||||||
|
.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
|
||||||
sortButtonText.setText(R.string.title_most_played);
|
sortButtonText.setText(R.string.title_most_played);
|
||||||
}
|
}
|
||||||
startLoading(true);
|
startLoading(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayQueue getPlayQueueStartingAt(StreamStatisticsEntry infoItem) {
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private PlayQueue getPlayQueueStartingAt(final StreamStatisticsEntry infoItem) {
|
||||||
return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0));
|
return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showStreamDialog(final StreamStatisticsEntry item) {
|
private void showStreamDialog(final StreamStatisticsEntry item) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (context == null || context.getResources() == null || activity == null) return;
|
if (context == null || context.getResources() == null || activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||||
|
|
||||||
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
|
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||||
|
@ -384,29 +403,31 @@ public class StatisticsPlaylistFragment
|
||||||
StreamDialogEntry.append_playlist,
|
StreamDialogEntry.append_playlist,
|
||||||
StreamDialogEntry.share);
|
StreamDialogEntry.share);
|
||||||
|
|
||||||
StreamDialogEntry.start_here_on_popup.setCustomAction(
|
StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItemDuplicate) ->
|
||||||
(fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
|
NavigationHelper
|
||||||
|
.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamDialogEntry.start_here_on_background.setCustomAction(
|
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
|
||||||
(fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
|
NavigationHelper
|
||||||
|
.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
|
||||||
StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) ->
|
StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) ->
|
||||||
deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0)));
|
deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0)));
|
||||||
|
|
||||||
new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context),
|
||||||
StreamDialogEntry.clickOn(which, this, infoItem)).show();
|
(dialog, which) -> StreamDialogEntry.clickOn(which, this, infoItem)).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteEntry(final int index) {
|
private void deleteEntry(final int index) {
|
||||||
final LocalItem infoItem = itemListAdapter.getItemsList()
|
final LocalItem infoItem = itemListAdapter.getItemsList()
|
||||||
.get(index);
|
.get(index);
|
||||||
if(infoItem instanceof StreamStatisticsEntry) {
|
if (infoItem instanceof StreamStatisticsEntry) {
|
||||||
final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem;
|
final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem;
|
||||||
final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId())
|
final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
howManyDeleted -> {
|
howManyDeleted -> {
|
||||||
if(getView() != null) {
|
if (getView() != null) {
|
||||||
Snackbar.make(getView(), R.string.one_item_deleted,
|
Snackbar.make(getView(), R.string.one_item_deleted,
|
||||||
Snackbar.LENGTH_SHORT).show();
|
Snackbar.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
|
@ -441,5 +462,10 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
return new SinglePlayQueue(streamInfoItems, index);
|
return new SinglePlayQueue(streamInfoItems, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum StatisticSortMode {
|
||||||
|
LAST_PLAYED,
|
||||||
|
MOST_PLAYED,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package org.schabi.newpipe.local.holder;
|
package org.schabi.newpipe.local.holder;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.LocalItem;
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
@ -33,14 +34,15 @@ import java.text.DateFormat;
|
||||||
public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
|
public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
|
||||||
protected final LocalItemBuilder itemBuilder;
|
protected final LocalItemBuilder itemBuilder;
|
||||||
|
|
||||||
public LocalItemHolder(LocalItemBuilder itemBuilder, int layoutId, ViewGroup parent) {
|
public LocalItemHolder(final LocalItemBuilder itemBuilder, final int layoutId,
|
||||||
super(LayoutInflater.from(itemBuilder.getContext())
|
final ViewGroup parent) {
|
||||||
.inflate(layoutId, parent, false));
|
super(LayoutInflater.from(itemBuilder.getContext()).inflate(layoutId, parent, false));
|
||||||
this.itemBuilder = itemBuilder;
|
this.itemBuilder = itemBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void updateFromItem(final LocalItem item, HistoryRecordManager historyRecordManager, final DateFormat dateFormat);
|
public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager,
|
||||||
|
DateFormat dateFormat);
|
||||||
|
|
||||||
public void updateState(final LocalItem localItem, HistoryRecordManager historyRecordManager) {
|
public void updateState(final LocalItem localItem,
|
||||||
}
|
final HistoryRecordManager historyRecordManager) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
|
|
||||||
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
|
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
|
||||||
|
public LocalPlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||||
public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,22 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
|
|
||||||
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||||
|
public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
|
||||||
public LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
|
||||||
super(infoItemBuilder, parent);
|
super(infoItemBuilder, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
public void updateFromItem(final LocalItem localItem,
|
||||||
if (!(localItem instanceof PlaylistMetadataEntry)) return;
|
final HistoryRecordManager historyRecordManager,
|
||||||
|
final DateFormat dateFormat) {
|
||||||
|
if (!(localItem instanceof PlaylistMetadataEntry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
||||||
|
|
||||||
itemTitleView.setText(item.name);
|
itemTitleView.setText(item.name);
|
||||||
|
|
|
@ -6,8 +6,8 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
|
|
||||||
public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder {
|
public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder {
|
||||||
|
public LocalPlaylistStreamGridItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||||
public LocalPlaylistStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); //TODO
|
super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package org.schabi.newpipe.local.holder;
|
package org.schabi.newpipe.local.holder;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.LocalItem;
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||||
|
@ -24,15 +25,15 @@ import java.util.ArrayList;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
|
|
||||||
public final ImageView itemThumbnailView;
|
public final ImageView itemThumbnailView;
|
||||||
public final TextView itemVideoTitleView;
|
public final TextView itemVideoTitleView;
|
||||||
public final TextView itemAdditionalDetailsView;
|
private final TextView itemAdditionalDetailsView;
|
||||||
public final TextView itemDurationView;
|
public final TextView itemDurationView;
|
||||||
public final View itemHandleView;
|
private final View itemHandleView;
|
||||||
public final AnimatedProgressBar itemProgressView;
|
private final AnimatedProgressBar itemProgressView;
|
||||||
|
|
||||||
LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
|
||||||
|
final ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
|
|
||||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||||
|
@ -43,30 +44,41 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
public LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder,
|
||||||
|
final ViewGroup parent) {
|
||||||
this(infoItemBuilder, R.layout.list_stream_playlist_item, parent);
|
this(infoItemBuilder, R.layout.list_stream_playlist_item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
public void updateFromItem(final LocalItem localItem,
|
||||||
if (!(localItem instanceof PlaylistStreamEntry)) return;
|
final HistoryRecordManager historyRecordManager,
|
||||||
|
final DateFormat dateFormat) {
|
||||||
|
if (!(localItem instanceof PlaylistStreamEntry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||||
|
|
||||||
itemVideoTitleView.setText(item.getStreamEntity().getTitle());
|
itemVideoTitleView.setText(item.getStreamEntity().getTitle());
|
||||||
itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getStreamEntity().getUploader(),
|
itemAdditionalDetailsView.setText(Localization
|
||||||
NewPipe.getNameOfService(item.getStreamEntity().getServiceId())));
|
.concatenateStrings(item.getStreamEntity().getUploader(),
|
||||||
|
NewPipe.getNameOfService(item.getStreamEntity().getServiceId())));
|
||||||
|
|
||||||
if (item.getStreamEntity().getDuration() > 0) {
|
if (item.getStreamEntity().getDuration() > 0) {
|
||||||
itemDurationView.setText(Localization.getDurationString(item.getStreamEntity().getDuration()));
|
itemDurationView.setText(Localization
|
||||||
|
.getDurationString(item.getStreamEntity().getDuration()));
|
||||||
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
||||||
R.color.duration_background_color));
|
R.color.duration_background_color));
|
||||||
itemDurationView.setVisibility(View.VISIBLE);
|
itemDurationView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0);
|
StreamStateEntity state = historyRecordManager
|
||||||
|
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||||
|
add(localItem);
|
||||||
|
}}).blockingGet().get(0);
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
itemProgressView.setVisibility(View.VISIBLE);
|
itemProgressView.setVisibility(View.VISIBLE);
|
||||||
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
|
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
|
||||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
|
||||||
|
.toSeconds(state.getProgressTime()));
|
||||||
} else {
|
} else {
|
||||||
itemProgressView.setVisibility(View.GONE);
|
itemProgressView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -97,17 +109,25 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) {
|
public void updateState(final LocalItem localItem,
|
||||||
if (!(localItem instanceof PlaylistStreamEntry)) return;
|
final HistoryRecordManager historyRecordManager) {
|
||||||
|
if (!(localItem instanceof PlaylistStreamEntry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||||
|
|
||||||
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0);
|
StreamStateEntity state = historyRecordManager
|
||||||
|
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||||
|
add(localItem);
|
||||||
|
}}).blockingGet().get(0);
|
||||||
if (state != null && item.getStreamEntity().getDuration() > 0) {
|
if (state != null && item.getStreamEntity().getDuration() > 0) {
|
||||||
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
|
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
|
||||||
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||||
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
|
||||||
|
.toSeconds(state.getProgressTime()));
|
||||||
} else {
|
} else {
|
||||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
|
||||||
|
.toSeconds(state.getProgressTime()));
|
||||||
AnimationUtils.animateView(itemProgressView, true, 500);
|
AnimationUtils.animateView(itemProgressView, true, 500);
|
||||||
}
|
}
|
||||||
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||||
|
@ -118,8 +138,8 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) {
|
private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) {
|
||||||
return (view, motionEvent) -> {
|
return (view, motionEvent) -> {
|
||||||
view.performClick();
|
view.performClick();
|
||||||
if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null &&
|
if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
|
||||||
motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
&& motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||||
itemBuilder.getOnItemSelectedListener().drag(item,
|
itemBuilder.getOnItemSelectedListener().drag(item,
|
||||||
LocalPlaylistStreamItemHolder.this);
|
LocalPlaylistStreamItemHolder.this);
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue