Merged the latest changes

This commit is contained in:
Avently 2020-07-13 04:17:21 +03:00
commit aef78ace88
1254 changed files with 39193 additions and 18652 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,9 @@
package org.schabi.newpipe.fragments;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@ -16,7 +18,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
@ -30,6 +32,8 @@ import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.ScrollableTabLayout;
import java.util.ArrayList;
import java.util.List;
@ -37,26 +41,29 @@ import java.util.List;
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
private ViewPager viewPager;
private SelectedTabsPagerAdapter pagerAdapter;
private TabLayout tabLayout;
private ScrollableTabLayout tabLayout;
private List<Tab> tabsList = new ArrayList<>();
private TabsManager tabsManager;
private boolean hasTabsChanged = false;
private boolean previousYoutubeRestrictedModeEnabled;
private String youtubeRestrictedModeEnabledKey;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
tabsManager = TabsManager.getManager(activity);
tabsManager.setSavedTabsListener(() -> {
if (DEBUG) {
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed());
Log.d(TAG, "TabsManager.SavedTabsChangeListener: "
+ "onTabsChanged called, isResumed = " + isResumed());
}
if (isResumed()) {
setupTabs();
@ -64,20 +71,29 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
hasTabsChanged = true;
}
});
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
previousYoutubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
tabLayout = rootView.findViewById(R.id.main_tab_layout);
viewPager = rootView.findViewById(R.id.pager);
tabLayout.setTabIconTint(ColorStateList.valueOf(
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
tabLayout.setupWithViewPager(viewPager);
tabLayout.addOnTabSelectedListener(this);
@ -88,14 +104,24 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onResume() {
super.onResume();
if (hasTabsChanged) setupTabs();
boolean youtubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
setupTabs();
} else if (hasTabsChanged) {
setupTabs();
}
}
@Override
public void onDestroy() {
super.onDestroy();
tabsManager.unsetSavedTabsListener();
if (viewPager != null) viewPager.setAdapter(null);
if (viewPager != null) {
viewPager.setAdapter(null);
}
}
/*//////////////////////////////////////////////////////////////////////////
@ -103,9 +129,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
inflater.inflate(R.menu.main_fragment_menu, menu);
ActionBar supportActionBar = activity.getSupportActionBar();
@ -115,7 +144,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
try {
@ -135,16 +164,18 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
// Tabs
//////////////////////////////////////////////////////////////////////////*/
public void setupTabs() {
private void setupTabs() {
tabsList.clear();
tabsList.addAll(tabsManager.getTabs());
if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) {
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), getChildFragmentManager(), tabsList);
pagerAdapter = new SelectedTabsPagerAdapter(requireContext(),
getChildFragmentManager(), tabsList);
}
// Clear previous tabs/fragments and set new adapter
viewPager.setAdapter(pagerAdapter);
viewPager.setAdapter(null);
viewPager.setOffscreenPageLimit(tabsList.size());
viewPager.setAdapter(pagerAdapter);
updateTabsIconAndDescription();
updateTitleForTab(viewPager.getCurrentItem());
@ -163,38 +194,45 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
}
private void updateTitleForTab(int tabPosition) {
private void updateTitleForTab(final int tabPosition) {
setTitle(tabsList.get(tabPosition).getTabName(requireContext()));
}
@Override
public void onTabSelected(TabLayout.Tab selectedTab) {
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
public void onTabSelected(final TabLayout.Tab selectedTab) {
if (DEBUG) {
Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
}
updateTitleForTab(selectedTab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
public void onTabUnselected(final TabLayout.Tab tab) { }
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
public void onTabReselected(final TabLayout.Tab tab) {
if (DEBUG) {
Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
}
updateTitleForTab(tab.getPosition());
}
private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapter {
private static final class SelectedTabsPagerAdapter
extends FragmentStatePagerAdapterMenuWorkaround {
private final Context context;
private final List<Tab> internalTabsList;
private SelectedTabsPagerAdapter(Context context, FragmentManager fragmentManager, List<Tab> tabsList) {
private SelectedTabsPagerAdapter(final Context context,
final FragmentManager fragmentManager,
final List<Tab> tabsList) {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.context = context;
this.internalTabsList = new ArrayList<>(tabsList);
}
@NonNull
@Override
public Fragment getItem(int position) {
public Fragment getItem(final int position) {
final Tab tab = internalTabsList.get(position);
Throwable throwable = null;
@ -206,8 +244,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
if (throwable != null) {
ErrorActivity.reportError(context, throwable, null, null,
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
return new BlankFragment();
}
@ -219,7 +257,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
@Override
public int getItemPosition(Object object) {
public int getItemPosition(final Object object) {
// Causes adapter to reload all Fragments when
// notifyDataSetChanged is called
return POSITION_NONE;
@ -230,7 +268,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
return internalTabsList.size();
}
public boolean sameTabs(List<Tab> tabsToCompare) {
public boolean sameTabs(final List<Tab> tabsToCompare) {
return internalTabsList.equals(tabsToCompare);
}
}

View file

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

View file

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

View file

@ -6,8 +6,8 @@ import java.io.Serializable;
class StackItem implements Serializable {
private final int serviceId;
private String title;
private String url;
private String title;
private PlayQueue playQueue;
StackItem(final int serviceId, final String url, final String title, final PlayQueue playQueue) {
@ -17,10 +17,6 @@ class StackItem implements Serializable {
this.playQueue = playQueue;
}
public void setTitle(String title) {
this.title = title;
}
public void setUrl(String url) {
this.url = url;
}
@ -37,6 +33,10 @@ class StackItem implements Serializable {
return title;
}
public void setTitle(final String title) {
this.title = title;
}
public String getUrl() {
return url;
}

View file

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

View file

@ -5,6 +5,7 @@ import android.app.Activity;
import android.content.*;
import android.content.pm.ActivityInfo;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@ -31,11 +32,13 @@ import androidx.viewpager.widget.ViewPager;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
@ -46,11 +49,10 @@ import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
@ -63,20 +65,34 @@ import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.*;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.*;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.*;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.InfoCache;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.AnimatedProgressBar;
import org.schabi.newpipe.views.LargeTextMovementMethod;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import icepick.State;
import io.noties.markwon.Markwon;
import io.noties.markwon.linkify.LinkifyPlugin;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
@ -86,6 +102,7 @@ import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired;
import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class VideoDetailFragment
@ -95,7 +112,8 @@ public class VideoDetailFragment
View.OnClickListener,
View.OnLongClickListener,
PlayerEventListener,
PlayerServiceEventListener {
PlayerServiceEventListener,
OnKeyDownListener {
public static final String AUTO_PLAY = "auto_play";
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
@ -112,6 +130,9 @@ public class VideoDetailFragment
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
private static final String INFO_KEY = "info_key";
private static final String STACK_KEY = "stack_key";
private boolean showRelatedStreams;
private boolean showComments;
private String selectedTabTag;
@ -174,6 +195,8 @@ public class VideoDetailFragment
private View uploaderRootLayout;
private TextView uploaderTextView;
private ImageView uploaderThumb;
private TextView subChannelTextView;
private ImageView subChannelThumb;
private TextView thumbsUpTextView;
private ImageView thumbsUpImageView;
@ -300,13 +323,13 @@ public class VideoDetailFragment
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void
onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity)
@ -336,19 +359,22 @@ public class VideoDetailFragment
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_video_detail, container, false);
}
@Override
public void onPause() {
super.onPause();
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
setupBrightness(true);
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit()
.putString(getString(R.string.stream_info_selected_tab_key), pageAdapter.getItemTitle(viewPager.getCurrentItem()))
.putString(getString(R.string.stream_info_selected_tab_key),
pageAdapter.getItemTitle(viewPager.getCurrentItem()))
.apply();
}
@ -395,8 +421,12 @@ public class VideoDetailFragment
activity.unregisterReceiver(broadcastReceiver);
activity.getContentResolver().unregisterContentObserver(settingsContentObserver);
if (positionSubscriber != null) positionSubscriber.dispose();
if (currentWorker != null) currentWorker.dispose();
if (positionSubscriber != null) {
positionSubscriber.dispose();
}
if (currentWorker != null) {
currentWorker.dispose();
}
disposables.clear();
positionSubscriber = null;
currentWorker = null;
@ -404,13 +434,16 @@ public class VideoDetailFragment
}
@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);
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK) {
NavigationHelper.openVideoDetailFragment(getFragmentManager(), serviceId, url, name);
} else Log.e(TAG, "ReCaptcha failed");
NavigationHelper
.openVideoDetailFragment(getFragmentManager(), serviceId, url, name);
} else {
Log.e(TAG, "ReCaptcha failed");
}
break;
default:
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
@ -419,7 +452,8 @@ public class VideoDetailFragment
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.show_next_video_key))) {
showRelatedStreams = sharedPreferences.getBoolean(key, true);
updateFlags |= RELATED_STREAMS_UPDATE_FLAG;
@ -433,11 +467,8 @@ public class VideoDetailFragment
// State Saving
//////////////////////////////////////////////////////////////////////////*/
private static final String INFO_KEY = "info_key";
private static final String STACK_KEY = "stack_key";
@Override
public void onSaveInstanceState(Bundle outState) {
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
// Check if the next video label and video is visible,
@ -453,7 +484,7 @@ public class VideoDetailFragment
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
Serializable serializable = savedState.getSerializable(INFO_KEY);
@ -476,8 +507,10 @@ public class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onClick(View v) {
if (isLoading.get() || currentInfo == null) return;
public void onClick(final View v) {
if (isLoading.get() || currentInfo == null) {
return;
}
switch (v.getId()) {
case R.id.detail_controls_background:
@ -499,7 +532,18 @@ public class VideoDetailFragment
}
break;
case R.id.detail_uploader_root_layout:
openChannel();
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
if (DEBUG) {
Log.i(TAG, "Can't open sub-channel because we got no channel URL");
}
} else {
openChannel(currentInfo.getSubChannelUrl(),
currentInfo.getSubChannelName());
}
break;
case R.id.detail_thumbnail_root_layout:
openVideoPlayer();
@ -527,9 +571,23 @@ public class VideoDetailFragment
}
}
private void openChannel(final String subChannelUrl, final String subChannelName) {
try {
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
subChannelUrl,
subChannelName);
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
@Override
public boolean onLongClick(View v) {
if (isLoading.get() || currentInfo == null) return false;
public boolean onLongClick(final View v) {
if (isLoading.get() || currentInfo == null) {
return false;
}
switch (v.getId()) {
case R.id.detail_controls_background:
@ -543,7 +601,19 @@ public class VideoDetailFragment
break;
case R.id.overlay_thumbnail:
case R.id.overlay_metadata_layout:
openChannel();
if (currentInfo != null)
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
break;
case R.id.detail_uploader_root_layout:
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
Log.w(TAG,
"Can't open parent channel because we got no parent channel URL");
} else {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
break;
case R.id.detail_title_root_layout:
ShareUtils.copyToClipboard(getContext(), videoTitleTextView.getText().toString());
break;
}
@ -554,11 +624,16 @@ public class VideoDetailFragment
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
videoTitleTextView.setMaxLines(1);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionView.setFocusable(false);
videoTitleToggleArrow.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
} else {
videoTitleTextView.setMaxLines(10);
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
videoDescriptionView.setFocusable(true);
videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
videoTitleToggleArrow.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less));
}
}
@ -567,7 +642,7 @@ public class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout);
thumbnailImageView = rootView.findViewById(R.id.detail_thumbnail_image_view);
@ -592,8 +667,6 @@ public class VideoDetailFragment
videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout);
videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view);
videoDescriptionView = rootView.findViewById(R.id.detail_description_view);
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS);
thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view);
thumbsUpImageView = rootView.findViewById(R.id.detail_thumbs_up_img_view);
@ -604,6 +677,8 @@ public class VideoDetailFragment
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
subChannelTextView = rootView.findViewById(R.id.detail_sub_channel_text_view);
subChannelThumb = rootView.findViewById(R.id.detail_sub_channel_thumbnail_view);
overlay = rootView.findViewById(R.id.overlay_layout);
overlayMetadata = rootView.findViewById(R.id.overlay_metadata_layout);
@ -624,14 +699,28 @@ public class VideoDetailFragment
relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout);
setHeightThumbnail();
thumbnailBackgroundButton.requestFocus();
if (AndroidTvUtils.isTv(getContext())) {
// remove ripple effects from detail controls
final int transparent = getResources().getColor(R.color.transparent_background_color);
detailControlsAddToPlaylist.setBackgroundColor(transparent);
detailControlsBackground.setBackgroundColor(transparent);
detailControlsPopup.setBackgroundColor(transparent);
detailControlsDownload.setBackgroundColor(transparent);
}
}
@Override
protected void initListeners() {
super.initListeners();
videoTitleRoot.setOnClickListener(this);
videoTitleRoot.setOnLongClickListener(this);
uploaderRootLayout.setOnClickListener(this);
uploaderRootLayout.setOnLongClickListener(this);
videoTitleRoot.setOnClickListener(this);
thumbnailBackgroundButton.setOnClickListener(this);
detailControlsBackground.setOnClickListener(this);
detailControlsPopup.setOnClickListener(this);
@ -674,25 +763,31 @@ public class VideoDetailFragment
};
}
private void initThumbnailViews(@NonNull StreamInfo info) {
private void initThumbnailViews(@NonNull final StreamInfo info) {
thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
if (!TextUtils.isEmpty(info.getThumbnailUrl())) {
final String infoServiceName = NewPipe.getNameOfService(info.getServiceId());
final ImageLoadingListener loadingListener = new SimpleImageLoadingListener() {
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
public void onLoadingFailed(final String imageUri, final View view,
final FailReason failReason) {
showSnackBarError(failReason.getCause(), UserAction.LOAD_IMAGE,
infoServiceName, imageUri, R.string.could_not_load_thumbnails);
}
};
imageLoader.displayImage(info.getThumbnailUrl(), thumbnailImageView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, loadingListener);
IMAGE_LOADER.displayImage(info.getThumbnailUrl(), thumbnailImageView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
}
if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
imageLoader.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
}
@ -707,20 +802,16 @@ public class VideoDetailFragment
*/
protected final LinkedList<StackItem> stack = new LinkedList<>();
/*public void setTitleToUrl(int serviceId, String videoUrl, String name) {
if (name != null && !name.isEmpty()) {
for (StackItem stackItem : stack) {
if (stack.peek().getServiceId() == serviceId
&& stackItem.getUrl().equals(videoUrl)) {
stackItem.setTitle(name);
}
}
}
}*/
@Override
public boolean onKeyDown(final int keyCode) {
return player != null && player.onKeyDown(keyCode);
}
@Override
public boolean onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed() called");
if (DEBUG) {
Log.d(TAG, "onBackPressed() called");
}
// If we are in fullscreen mode just exit from it via first back press
if (player != null && player.isFullscreen()) {
@ -782,23 +873,28 @@ public class VideoDetailFragment
protected void doInitialLoadLogic() {
if (wasCleared()) return;
if (currentInfo == null) prepareAndLoadInfo();
else prepareAndHandleInfo(currentInfo, false);
if (currentInfo == null) {
prepareAndLoadInfo();
} else {
prepareAndHandleInfo(currentInfo, false);
}
}
public void selectAndLoadVideo(final int serviceId, final String videoUrl, final String name, final PlayQueue playQueue) {
public void selectAndLoadVideo(final int sid, final String videoUrl, final String title, final PlayQueue playQueue) {
// Situation when user switches from players to main player. All needed data is here, we can start watching
if (this.playQueue != null && this.playQueue.equals(playQueue)) {
openVideoPlayer();
return;
}
setInitialData(serviceId, videoUrl, name, playQueue);
setInitialData(sid, videoUrl, title, playQueue);
startLoading(false, true);
}
public void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) {
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = ["
+ info + "], scrollToTop = [" + scrollToTop + "]");
private void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) {
if (DEBUG) {
Log.d(TAG, "prepareAndHandleInfo() called with: "
+ "info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
}
showLoading();
initTabs();
@ -820,7 +916,11 @@ public class VideoDetailFragment
initTabs();
currentInfo = null;
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
runWorker(forceLoad, stack.isEmpty());
}
@ -836,20 +936,27 @@ public class VideoDetailFragment
}
private void runWorker(final boolean forceLoad, final boolean addToBackStack) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
.subscribe((@NonNull final StreamInfo result) -> {
isLoading.set(false);
hideMainPlayer();
handleResult(result);
showContent();
if (addToBackStack) {
if (playQueue == null) playQueue = new SinglePlayQueue(result);
stack.push(new StackItem(serviceId, url, name, playQueue));
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
getString(R.string.show_age_restricted_content), false)) {
hideAgeRestrictedContent();
} else {
currentInfo = result;
handleResult(result);
showContent();
if (addToBackStack) {
if (playQueue == null) playQueue = new SinglePlayQueue(result);
stack.push(new StackItem(serviceId, url, name, playQueue));
}
if (isAutoplayEnabled()) openVideoPlayer();
}
if (isAutoplayEnabled()) openVideoPlayer();
}, (@NonNull Throwable throwable) -> {
}, (@NonNull final Throwable throwable) -> {
isLoading.set(false);
onError(throwable);
});
@ -879,8 +986,10 @@ public class VideoDetailFragment
if (pageAdapter.getCount() < 2) {
tabLayout.setVisibility(View.GONE);
} else {
final int position = pageAdapter.getItemPositionByTitle(selectedTabTag);
if (position != -1) viewPager.setCurrentItem(position);
int position = pageAdapter.getItemPositionByTitle(selectedTabTag);
if (position != -1) {
viewPager.setCurrentItem(position);
}
tabLayout.setVisibility(View.VISIBLE);
}
}
@ -1001,22 +1110,6 @@ public class VideoDetailFragment
return queue;
}
private void openChannel() {
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
Log.w(TAG, "Can't open channel because we got no channel URL");
} else {
try {
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
currentInfo.getUploaderUrl(),
currentInfo.getUploaderName());
} catch (Exception e) {
ErrorActivity.reportUiError(activity, e);
}
}
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@ -1029,12 +1122,12 @@ public class VideoDetailFragment
@NonNull final StreamInfo info,
@NonNull final Stream selectedStream) {
NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(),
currentInfo.getUploaderName(), selectedStream);
currentInfo.getSubChannelName(), selectedStream);
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
disposables.add(recordManager.onViewed(info).onErrorComplete()
.subscribe(
ignored -> {/* successful */},
ignored -> { /* successful */ },
error -> Log.e(TAG, "Register view failure: ", error)
));
}
@ -1101,28 +1194,42 @@ public class VideoDetailFragment
return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
}
private void prepareDescription(final String descriptionHtml) {
if (TextUtils.isEmpty(descriptionHtml)) {
private void prepareDescription(final Description description) {
if (TextUtils.isEmpty(description.getContent())
|| description == Description.emptyDescription) {
return;
}
disposables.add(Single.just(descriptionHtml)
.map((@io.reactivex.annotations.NonNull String description) -> {
final Spanned parsedDescription;
if (Build.VERSION.SDK_INT >= 24) {
parsedDescription = Html.fromHtml(description, 0);
} else {
//noinspection deprecation
parsedDescription = Html.fromHtml(description);
}
return parsedDescription;
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@io.reactivex.annotations.NonNull Spanned spanned) -> {
videoDescriptionView.setText(spanned);
videoDescriptionView.setVisibility(View.VISIBLE);
}));
if (description.getType() == Description.HTML) {
disposables.add(Single.just(description.getContent())
.map((@io.reactivex.annotations.NonNull String descriptionText) -> {
Spanned parsedDescription;
if (Build.VERSION.SDK_INT >= 24) {
parsedDescription = Html.fromHtml(descriptionText, 0);
} else {
//noinspection deprecation
parsedDescription = Html.fromHtml(descriptionText);
}
return parsedDescription;
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@io.reactivex.annotations.NonNull Spanned spanned) -> {
videoDescriptionView.setText(spanned);
videoDescriptionView.setVisibility(View.VISIBLE);
}));
} else if (description.getType() == Description.MARKDOWN) {
final Markwon markwon = Markwon.builder(getContext())
.usePlugin(LinkifyPlugin.create())
.build();
markwon.setMarkdown(videoDescriptionView, description.getContent());
videoDescriptionView.setVisibility(View.VISIBLE);
} else {
//== Description.PLAIN_TEXT
videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS);
videoDescriptionView.setText(description.getContent(), TextView.BufferType.SPANNABLE);
videoDescriptionView.setVisibility(View.VISIBLE);
}
}
/**
@ -1154,27 +1261,31 @@ public class VideoDetailFragment
contentRootLayoutHiding.setVisibility(View.VISIBLE);
}
protected void setInitialData(final int serviceId, final String url, final String name, final PlayQueue playQueue) {
this.serviceId = serviceId;
this.url = url;
protected void setInitialData(final int sid, final String u, final String name, final PlayQueue playQueue) {
this.serviceId = sid;
this.url = u;
this.name = !TextUtils.isEmpty(name) ? name : "";
this.playQueue = playQueue;
}
private void setErrorImage(final int imageResource) {
if (thumbnailImageView == null || activity == null) return;
if (thumbnailImageView == null || activity == null) {
return;
}
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource));
thumbnailImageView.setImageDrawable(
AppCompatResources.getDrawable(requireContext(), imageResource));
animateView(thumbnailImageView, false, 0, 0,
() -> animateView(thumbnailImageView, true, 500));
}
@Override
public void showError(String message, boolean showRetryButton) {
public void showError(final String message, final boolean showRetryButton) {
showError(message, showRetryButton, R.drawable.not_available_monkey);
}
protected void showError(String message, boolean showRetryButton, @DrawableRes int imageError) {
protected void showError(final String message, final boolean showRetryButton,
@DrawableRes final int imageError) {
super.showError(message, showRetryButton);
setErrorImage(imageError);
}
@ -1236,7 +1347,6 @@ public class VideoDetailFragment
animateView(videoTitleTextView, true, 0);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.GONE);
videoTitleRoot.setClickable(false);
@ -1248,14 +1358,14 @@ public class VideoDetailFragment
}
}
imageLoader.cancelDisplayTask(thumbnailImageView);
imageLoader.cancelDisplayTask(uploaderThumb);
IMAGE_LOADER.cancelDisplayTask(thumbnailImageView);
IMAGE_LOADER.cancelDisplayTask(subChannelThumb);
thumbnailImageView.setImageBitmap(null);
uploaderThumb.setImageBitmap(null);
subChannelThumb.setImageBitmap(null);
}
@Override
public void handleResult(@NonNull StreamInfo info) {
public void handleResult(@NonNull final StreamInfo info) {
super.handleResult(info);
currentInfo = info;
@ -1263,11 +1373,13 @@ public class VideoDetailFragment
if (showRelatedStreams) {
if (null == relatedStreamsLayout) { //phone
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info));
pageAdapter.updateItem(RELATED_TAB_TAG,
RelatedVideosFragment.getInstance(info));
pageAdapter.notifyDataSetUpdate();
} else { //tablet
getChildFragmentManager().beginTransaction()
.replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(info))
.replace(R.id.relatedStreamsLayout,
RelatedVideosFragment.getInstance(info))
.commitNow();
relatedStreamsLayout.setVisibility(player != null && player.isFullscreen() ? View.GONE : View.VISIBLE);
}
@ -1276,22 +1388,28 @@ public class VideoDetailFragment
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(name);
if (!TextUtils.isEmpty(info.getUploaderName())) {
uploaderTextView.setText(info.getUploaderName());
uploaderTextView.setVisibility(View.VISIBLE);
uploaderTextView.setSelected(true);
if (!TextUtils.isEmpty(info.getSubChannelName())) {
displayBothUploaderAndSubChannel(info);
} else if (!TextUtils.isEmpty(info.getUploaderName())) {
displayUploaderAsSubChannel(info);
} else {
uploaderTextView.setVisibility(View.GONE);
uploaderThumb.setVisibility(View.GONE);
}
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy);
subChannelThumb.setImageDrawable(buddyDrawable);
uploaderThumb.setImageDrawable(buddyDrawable);
if (info.getViewCount() >= 0) {
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
videoCountView.setText(Localization.listeningCount(activity, info.getViewCount()));
} else if (info.getStreamType().equals(StreamType.LIVE_STREAM)) {
videoCountView.setText(Localization.watchingCount(activity, info.getViewCount()));
videoCountView.setText(Localization
.localizeWatchingCount(activity, info.getViewCount()));
} else {
videoCountView.setText(Localization.localizeViewCount(activity, info.getViewCount()));
videoCountView.setText(Localization
.localizeViewCount(activity, info.getViewCount()));
}
videoCountView.setVisibility(View.VISIBLE);
} else {
@ -1307,7 +1425,8 @@ public class VideoDetailFragment
thumbsDisabledTextView.setVisibility(View.VISIBLE);
} else {
if (info.getDislikeCount() >= 0) {
thumbsDownTextView.setText(Localization.shortCount(activity, info.getDislikeCount()));
thumbsDownTextView.setText(Localization
.shortCount(activity, info.getDislikeCount()));
thumbsDownTextView.setVisibility(View.VISIBLE);
thumbsDownImageView.setVisibility(View.VISIBLE);
} else {
@ -1342,12 +1461,14 @@ public class VideoDetailFragment
videoDescriptionView.setVisibility(View.GONE);
videoTitleRoot.setClickable(true);
videoTitleToggleArrow.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
videoTitleToggleArrow.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionRootLayout.setVisibility(View.GONE);
if (info.getUploadDate() != null) {
videoUploadDateView.setText(Localization.localizeUploadDate(activity, info.getUploadDate().date().getTime()));
videoUploadDateView.setText(Localization
.localizeUploadDate(activity, info.getUploadDate().date().getTime()));
videoUploadDateView.setVisibility(View.VISIBLE);
} else {
videoUploadDateView.setText(null);
@ -1382,16 +1503,54 @@ public class VideoDetailFragment
detailControlsDownload.setVisibility(View.GONE);
break;
default:
if (info.getAudioStreams().isEmpty()) detailControlsBackground.setVisibility(View.GONE);
if (!info.getVideoStreams().isEmpty()
|| !info.getVideoOnlyStreams().isEmpty()) break;
if (info.getAudioStreams().isEmpty()) {
detailControlsBackground.setVisibility(View.GONE);
}
if (!info.getVideoStreams().isEmpty() || !info.getVideoOnlyStreams().isEmpty()) {
break;
}
detailControlsPopup.setVisibility(View.GONE);
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_shadow);
break;
}
}
private void hideAgeRestrictedContent() {
showError(getString(R.string.restricted_video), false);
if (relatedStreamsLayout != null) { // tablet
relatedStreamsLayout.setVisibility(View.INVISIBLE);
}
viewPager.setVisibility(View.GONE);
tabLayout.setVisibility(View.GONE);
}
private void displayUploaderAsSubChannel(final StreamInfo info) {
subChannelTextView.setText(info.getUploaderName());
subChannelTextView.setVisibility(View.VISIBLE);
subChannelTextView.setSelected(true);
uploaderTextView.setVisibility(View.GONE);
}
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
subChannelTextView.setText(info.getSubChannelName());
subChannelTextView.setVisibility(View.VISIBLE);
subChannelTextView.setSelected(true);
subChannelThumb.setVisibility(View.VISIBLE);
if (!TextUtils.isEmpty(info.getUploaderName())) {
uploaderTextView.setText(
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
uploaderTextView.setVisibility(View.VISIBLE);
uploaderTextView.setSelected(true);
} else {
uploaderTextView.setVisibility(View.GONE);
}
}
public void openDownloadDialog() {
try {
@ -1423,24 +1582,20 @@ public class VideoDetailFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
else if (exception instanceof ContentNotAvailableException) {
showError(getString(R.string.content_not_available), false);
} else {
final int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
? R.string.youtube_signature_decryption_error
: exception instanceof ParsingException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception,
UserAction.REQUESTED_STREAM,
NewPipe.getNameOfService(serviceId),
url,
errorId);
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
? R.string.youtube_signature_decryption_error
: exception instanceof ExtractionException
? R.string.parsing_error
: R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_STREAM,
NewPipe.getNameOfService(serviceId), url, errorId);
return true;
}
@ -1449,9 +1604,9 @@ public class VideoDetailFragment
positionSubscriber.dispose();
}
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
final boolean playbackResumeEnabled =
prefs.getBoolean(activity.getString(R.string.enable_watch_history_key), true)
&& prefs.getBoolean(activity.getString(R.string.enable_playback_resume_key), true);
final boolean playbackResumeEnabled = prefs
.getBoolean(activity.getString(R.string.enable_watch_history_key), true)
&& prefs.getBoolean(activity.getString(R.string.enable_playback_resume_key), true);
final boolean showPlaybackPosition = prefs.getBoolean(
activity.getString(R.string.enable_playback_state_lists_key), true);
if (!playbackResumeEnabled) {
@ -1459,6 +1614,12 @@ public class VideoDetailFragment
|| playQueue.getItem().getRecoveryPosition() == RECOVERY_UNSET || !showPlaybackPosition) {
positionView.setVisibility(View.INVISIBLE);
detailPositionView.setVisibility(View.GONE);
// TODO: Remove this check when separation of concerns is done.
// (live streams weren't getting updated because they are mixed)
if (!info.getStreamType().equals(StreamType.LIVE_STREAM)
&& !info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
return;
}
} else {
// Show saved position from backStack if user allows it
showPlaybackProgress(playQueue.getItem().getRecoveryPosition(),
@ -1466,9 +1627,12 @@ public class VideoDetailFragment
animateView(positionView, true, 500);
animateView(detailPositionView, true, 500);
}
return;
}
return;
}
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
// TODO: Separate concerns when updating database data.
// (move the updating part to when the loading happens)
positionSubscriber = recordManager.loadStreamState(info)
.subscribeOn(Schedulers.io())
.onErrorComplete()
@ -1478,7 +1642,9 @@ public class VideoDetailFragment
animateView(positionView, true, 500);
animateView(detailPositionView, true, 500);
}, e -> {
if (DEBUG) e.printStackTrace();
if (DEBUG) {
e.printStackTrace();
}
}, () -> {
positionView.setVisibility(View.GONE);
detailPositionView.setVisibility(View.GONE);
@ -1489,7 +1655,7 @@ public class VideoDetailFragment
final int progressSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(progress);
final int durationSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(duration);
positionView.setMax(durationSeconds);
positionView.setProgress(progressSeconds);
positionView.setProgressAnimated(progressSeconds);
detailPositionView.setText(Localization.getDurationString(progressSeconds));
if (positionView.getVisibility() != View.VISIBLE) {
animateView(positionView, true, 100);
@ -1862,12 +2028,12 @@ public class VideoDetailFragment
overlayChannelTextView.setText(TextUtils.isEmpty(uploader) ? "" : uploader);
overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
if (!TextUtils.isEmpty(thumbnailUrl))
imageLoader.displayImage(thumbnailUrl, overlayThumbnailImageView,
IMAGE_LOADER.displayImage(thumbnailUrl, overlayThumbnailImageView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null);
}
private void setOverlayPlayPauseImage() {
final int attr = player != null && player.getPlayer().getPlayWhenReady() ? R.attr.pause : R.attr.play;
final int attr = player != null && player.getPlayer().getPlayWhenReady() ? R.attr.ic_pause : R.attr.ic_play_arrow;
overlayPlayPauseButton.setImageResource(ThemeHelper.resolveResourceIdFromAttr(activity, attr));
}

View file

@ -7,17 +7,17 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
@ -34,13 +34,21 @@ import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.StreamDialogEntry;
import org.schabi.newpipe.views.SuperScrollLayoutManager;
import java.util.List;
import java.util.Queue;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener {
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N>, StateSaver.WriteRead,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected StateSaver.SavedState savedState;
private boolean useDefaultStateSaving = true;
private int updateFlags = 0;
/*//////////////////////////////////////////////////////////////////////////
// Views
@ -48,18 +56,19 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
protected InfoListAdapter infoListAdapter;
protected RecyclerView itemsList;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
private int focusedPosition = -1;
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
infoListAdapter = new InfoListAdapter(activity);
if (infoListAdapter == null) {
infoListAdapter = new InfoListAdapter(activity);
}
}
@Override
@ -68,7 +77,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
@ -78,7 +87,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
@Override
public void onDestroy() {
super.onDestroy();
StateSaver.onDestroy(savedState);
if (useDefaultStateSaving) {
StateSaver.onDestroy(savedState);
}
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
}
@ -90,8 +101,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
itemsList.setLayoutManager(useGrid
? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
@ -102,7 +114,15 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
// State Saving
//////////////////////////////////////////////////////////////////////////*/
protected StateSaver.SavedState savedState;
/**
* If the default implementation of {@link StateSaver.WriteRead} should be used.
*
* @see StateSaver
* @param useDefaultStateSaving Whether the default implementation should be used
*/
public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
this.useDefaultStateSaving = useDefaultStateSaving;
}
@Override
public String generateSuffix() {
@ -110,28 +130,81 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
return "." + infoListAdapter.getItemsList().size() + ".list";
}
private int getFocusedPosition() {
try {
final View focusedItem = itemsList.getFocusedChild();
final RecyclerView.ViewHolder itemHolder =
itemsList.findContainingViewHolder(focusedItem);
return itemHolder.getAdapterPosition();
} catch (NullPointerException e) {
return -1;
}
}
@Override
public void writeTo(Queue<Object> objectsToSave) {
public void writeTo(final Queue<Object> objectsToSave) {
if (!useDefaultStateSaving) {
return;
}
objectsToSave.add(infoListAdapter.getItemsList());
objectsToSave.add(getFocusedPosition());
}
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
if (!useDefaultStateSaving) {
return;
}
infoListAdapter.getItemsList().clear();
infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll());
restoreFocus((Integer) savedObjects.poll());
}
private void restoreFocus(final Integer position) {
if (position == null || position < 0) {
return;
}
itemsList.post(() -> {
RecyclerView.ViewHolder focusedHolder =
itemsList.findViewHolderForAdapterPosition(position);
if (focusedHolder != null) {
focusedHolder.itemView.requestFocus();
}
});
}
@Override
public void onSaveInstanceState(Bundle bundle) {
public void onSaveInstanceState(final Bundle bundle) {
super.onSaveInstanceState(bundle);
savedState = StateSaver.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this);
if (useDefaultStateSaving) {
savedState = StateSaver
.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this);
}
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
protected void onRestoreInstanceState(@NonNull final Bundle bundle) {
super.onRestoreInstanceState(bundle);
savedState = StateSaver.tryToRestore(bundle, this);
if (useDefaultStateSaving) {
savedState = StateSaver.tryToRestore(bundle, this);
}
}
@Override
public void onStop() {
focusedPosition = getFocusedPosition();
super.onStop();
}
@Override
public void onStart() {
super.onStart();
restoreFocus(focusedPosition);
}
/*//////////////////////////////////////////////////////////////////////////
@ -147,36 +220,39 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
protected RecyclerView.LayoutManager getListLayoutManager() {
return new LinearLayoutManager(activity);
return new SuperScrollLayoutManager(activity);
}
protected RecyclerView.LayoutManager getGridLayoutManager() {
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
/ (double) width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setGridItemVariants(useGrid);
infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.setFooter(getListFooter());
infoListAdapter.setHeader(getListHeader());
itemsList.setAdapter(infoListAdapter);
}
protected void onItemSelected(InfoItem selectedItem) {
if (DEBUG) Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
protected void onItemSelected(final InfoItem selectedItem) {
if (DEBUG) {
Log.d(TAG, "onItemSelected() called with: selectedItem = [" + selectedItem + "]");
}
}
@Override
@ -184,19 +260,19 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
super.initListeners();
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<StreamInfoItem>() {
@Override
public void selected(StreamInfoItem selectedItem) {
public void selected(final StreamInfoItem selectedItem) {
onStreamSelected(selectedItem);
}
@Override
public void held(StreamInfoItem selectedItem) {
public void held(final StreamInfoItem selectedItem) {
showStreamDialog(selectedItem);
}
});
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
@Override
public void selected(ChannelInfoItem selectedItem) {
public void selected(final ChannelInfoItem selectedItem) {
try {
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(getFM(),
@ -211,7 +287,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
@Override
public void selected(PlaylistInfoItem selectedItem) {
public void selected(final PlaylistInfoItem selectedItem) {
try {
onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(getFM(),
@ -226,7 +302,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
@Override
public void selected(CommentsInfoItem selectedItem) {
public void selected(final CommentsInfoItem selectedItem) {
onItemSelected(selectedItem);
}
});
@ -234,13 +310,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
itemsList.clearOnScrollListeners();
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
@Override
public void onScrolledDown(RecyclerView recyclerView) {
public void onScrolledDown(final RecyclerView recyclerView) {
onScrollToBottom();
}
});
}
private void onStreamSelected(StreamInfoItem selectedItem) {
private void onStreamSelected(final StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
@ -253,12 +329,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) return;
if (context == null || context.getResources() == null || activity == null) {
return;
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries(
@ -276,8 +352,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
StreamDialogEntry.share);
}
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
StreamDialogEntry.clickOn(which, this, item)).show();
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
(dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();
}
/*//////////////////////////////////////////////////////////////////////////
@ -285,8 +361,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
@ -324,7 +403,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void showError(String message, boolean showRetryButton) {
public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton);
showListFooter(false);
animateView(itemsList, false, 200);
@ -346,25 +425,28 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
}
@Override
public void handleNextItems(N result) {
public void handleNextItems(final N result) {
isLoading.set(false);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
if ("auto".equals(list_mode)) {
final String listMode = PreferenceManager.getDefaultSharedPreferences(activity)
.getString(getString(R.string.list_view_mode_key),
getString(R.string.list_view_mode_value));
if ("auto".equals(listMode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else {
return "grid".equals(list_mode);
return "grid".equals(listMode);
}
}
}

View file

@ -9,7 +9,9 @@ import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView;
import java.util.Queue;
@ -21,7 +23,6 @@ import io.reactivex.schedulers.Schedulers;
public abstract class BaseListInfoFragment<I extends ListInfo>
extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
@ -30,11 +31,11 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
protected String url;
protected I currentInfo;
protected String currentNextPageUrl;
protected Page currentNextPage;
protected Disposable currentWorker;
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
setTitle(name);
showListFooter(hasMoreItems());
@ -43,7 +44,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
public void onPause() {
super.onPause();
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
}
@Override
@ -73,18 +76,18 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/
@Override
public void writeTo(Queue<Object> objectsToSave) {
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentInfo);
objectsToSave.add(currentNextPageUrl);
objectsToSave.add(currentNextPage);
}
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentInfo = (I) savedObjects.poll();
currentNextPageUrl = (String) savedObjects.poll();
currentNextPage = (Page) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
@ -92,10 +95,14 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/
protected void doInitialLoadLogic() {
if (DEBUG) Log.d(TAG, "doInitialLoadLogic() called");
if (DEBUG) {
Log.d(TAG, "doInitialLoadLogic() called");
}
if (currentInfo == null) {
startLoading(false);
} else handleResult(currentInfo);
} else {
handleResult(currentInfo);
}
}
/**
@ -103,43 +110,56 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}.
*
* @param forceLoad allow or disallow the result to come from the cache
* @return Rx {@link Single} containing the {@link ListInfo}
*/
protected abstract Single<I> loadResult(boolean forceLoad);
@Override
public void startLoading(boolean forceLoad) {
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
showListFooter(false);
infoListAdapter.clearStreamItemList();
currentInfo = null;
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
currentWorker = loadResult(forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull I result) -> {
isLoading.set(false);
currentInfo = result;
currentNextPageUrl = result.getNextPageUrl();
currentNextPage = result.getNextPage();
handleResult(result);
}, (@NonNull Throwable throwable) -> onError(throwable));
}
/**
* Implement the logic to load more items<br/>
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper}
* Implement the logic to load more items.
* <p>You can use the default implementations
* from {@link org.schabi.newpipe.util.ExtractorHelper}.</p>
*
* @return Rx {@link Single} containing the {@link ListExtractor.InfoItemsPage}
*/
protected abstract Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic();
protected void loadMoreItems() {
isLoading.set(true);
if (currentWorker != null) currentWorker.dispose();
if (currentWorker != null) {
currentWorker.dispose();
}
forbidDownwardFocusScroll();
currentWorker = loadMoreItemsLogic()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
.doFinally(this::allowDownwardFocusScroll)
.subscribe((@io.reactivex.annotations.NonNull
ListExtractor.InfoItemsPage InfoItemsPage) -> {
isLoading.set(false);
handleNextItems(InfoItemsPage);
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
@ -148,10 +168,22 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
});
}
private void forbidDownwardFocusScroll() {
if (itemsList instanceof NewPipeRecyclerView) {
((NewPipeRecyclerView) itemsList).setFocusScrollAllowed(false);
}
}
private void allowDownwardFocusScroll() {
if (itemsList instanceof NewPipeRecyclerView) {
((NewPipeRecyclerView) itemsList).setFocusScrollAllowed(true);
}
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
currentNextPageUrl = result.getNextPageUrl();
currentNextPage = result.getNextPage();
infoListAdapter.addInfoItemList(result.getItems());
showListFooter(hasMoreItems());
@ -159,7 +191,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
protected boolean hasMoreItems() {
return !TextUtils.isEmpty(currentNextPageUrl);
return Page.isValid(currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////
@ -167,7 +199,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
//////////////////////////////////////////////////////////////////////////*/
@Override
public void handleResult(@NonNull I result) {
public void handleResult(@NonNull final I result) {
super.handleResult(result);
name = result.getName();
@ -188,9 +220,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void setInitialData(int serviceId, String url, String name) {
this.serviceId = serviceId;
this.url = url;
this.name = !TextUtils.isEmpty(name) ? name : "";
protected void setInitialData(final int sid, final String u, final String title) {
this.serviceId = sid;
this.url = u;
this.name = !TextUtils.isEmpty(title) ? title : "";
}
}

View file

@ -4,12 +4,9 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.ActionBar;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -21,6 +18,12 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.jakewharton.rxbinding2.view.RxView;
import org.schabi.newpipe.R;
@ -29,13 +32,14 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.local.subscription.SubscriptionService;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@ -43,8 +47,10 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -62,42 +68,43 @@ import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
implements View.OnClickListener {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
private SubscriptionService subscriptionService;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private SubscriptionManager subscriptionManager;
private View headerRootLayout;
private ImageView headerChannelBanner;
private ImageView headerAvatarView;
private TextView headerTitleView;
private ImageView headerSubChannelAvatarView;
private TextView headerSubChannelTitleView;
private TextView headerSubscribersTextView;
private Button headerSubscribeButton;
private View playlistCtrl;
private LinearLayout headerPlayAllButton;
private LinearLayout headerPopupButton;
private LinearLayout headerBackgroundButton;
private MenuItem menuRssButton;
private TextView contentNotSupportedTextView;
private TextView kaomojiTextView;
private TextView noVideosTextView;
public static ChannelFragment getInstance(int serviceId, String url, String name) {
public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) {
ChannelFragment instance = new ChannelFragment();
instance.setInitialData(serviceId, url, name);
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (activity != null
&& useAsFrontPage
@ -106,22 +113,40 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
subscriptionService = SubscriptionService.getInstance(activity);
subscriptionManager = new SubscriptionManager(activity);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_channel, container, false);
}
@Override
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
contentNotSupportedTextView = rootView.findViewById(R.id.error_content_not_supported);
kaomojiTextView = rootView.findViewById(R.id.channel_kaomoji);
noVideosTextView = rootView.findViewById(R.id.channel_no_videos);
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposables != null) disposables.clear();
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
if (disposables != null) {
disposables.clear();
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
}
/*//////////////////////////////////////////////////////////////////////////
@ -129,14 +154,18 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/
protected View getListHeader() {
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, itemsList, false);
headerRootLayout = activity.getLayoutInflater()
.inflate(R.layout.channel_header, itemsList, false);
headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image);
headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view);
headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
headerSubChannelAvatarView =
headerRootLayout.findViewById(R.id.sub_channel_avatar_view);
headerSubChannelTitleView =
headerRootLayout.findViewById(R.id.sub_channel_title_view);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
@ -145,12 +174,20 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return headerRootLayout;
}
@Override
protected void initListeners() {
super.initListeners();
headerSubChannelTitleView.setOnClickListener(this);
headerSubChannelAvatarView.setOnClickListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
if (useAsFrontPage && supportActionBar != null) {
@ -158,8 +195,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
} else {
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu +
"], inflater = [" + inflater + "]");
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
menuRssButton = menu.findItem(R.id.menu_item_rss);
}
}
@ -173,19 +212,22 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_rss:
openRssFeed();
break;
case R.id.menu_item_openInBrowser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getOriginalUrl());
}
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl());
ShareUtils.shareUrl(requireContext(), name, currentInfo.getOriginalUrl());
}
break;
default:
@ -198,19 +240,17 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private void monitorSubscription(final ChannelInfo info) {
final Consumer<Throwable> onError = (Throwable throwable) -> {
animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status",
0);
animateView(headerSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status", 0);
};
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
.getSubscription(info.getServiceId(), info.getUrl())
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
.subscriptionTable()
.getSubscriptionFlowable(info.getServiceId(), info.getUrl())
.toObservable();
disposables.add(observable
@ -218,34 +258,40 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.subscribe(getSubscribeUpdateMonitor(info), onError));
disposables.add(observable
// Some updates are very rapid (when calling the updateSubscription(info), for example)
// so only update the UI for the latest emission ("sync" the subscribe button's state)
// Some updates are very rapid
// (for example when calling the updateSubscription(info))
// so only update the UI for the latest emission
// ("sync" the subscribe button's state)
.debounce(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
updateSubscribeButton(!subscriptionEntities.isEmpty())
, onError));
updateSubscribeButton(!subscriptionEntities.isEmpty()), onError));
}
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription,
final ChannelInfo info) {
return (@NonNull Object o) -> {
subscriptionService.subscriptionTable().insert(subscription);
subscriptionManager.insertSubscription(subscription, info);
return o;
};
}
private Function<Object, Object> mapOnUnsubscribe(final SubscriptionEntity subscription) {
return (@NonNull Object o) -> {
subscriptionService.subscriptionTable().delete(subscription);
subscriptionManager.deleteSubscription(subscription);
return o;
};
}
private void updateSubscription(final ChannelInfo info) {
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
if (DEBUG) {
Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
}
final Action onComplete = () -> {
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
if (DEBUG) {
Log.d(TAG, "Updated subscription: " + info.getUrl());
}
};
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
@ -255,15 +301,18 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
"Updating Subscription for " + info.getUrl(),
R.string.subscription_update_failed);
disposables.add(subscriptionService.updateChannelInfo(info)
disposables.add(subscriptionManager.updateChannelInfo(info)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onComplete, onError));
}
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) {
private Disposable monitorSubscribeButton(final Button subscribeButton,
final Function<Object, Object> action) {
final Consumer<Object> onNext = (@NonNull Object o) -> {
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!");
if (DEBUG) {
Log.d(TAG, "Changed subscription status to this channel!");
}
};
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
@ -284,12 +333,18 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
return (List<SubscriptionEntity> subscriptionEntities) -> {
if (DEBUG)
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
if (DEBUG) {
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: "
+ "subscriptionEntities = [" + subscriptionEntities + "]");
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
if (subscriptionEntities.isEmpty()) {
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
if (DEBUG) {
Log.d(TAG, "No subscription to this channel!");
}
SubscriptionEntity channel = new SubscriptionEntity();
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
@ -297,34 +352,45 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
info.getAvatarUrl(),
info.getDescription(),
info.getSubscriberCount());
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
mapOnSubscribe(channel, info));
} else {
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
if (DEBUG) {
Log.d(TAG, "Found subscription to this channel!");
}
final SubscriptionEntity subscription = subscriptionEntities.get(0);
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
mapOnUnsubscribe(subscription));
}
};
}
private void updateSubscribeButton(boolean isSubscribed) {
if (DEBUG) Log.d(TAG, "updateSubscribeButton() called with: isSubscribed = [" + isSubscribed + "]");
private void updateSubscribeButton(final boolean isSubscribed) {
if (DEBUG) {
Log.d(TAG, "updateSubscribeButton() called with: "
+ "isSubscribed = [" + isSubscribed + "]");
}
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
int backgroundDuration = isButtonVisible ? 300 : 0;
int textDuration = isButtonVisible ? 200 : 0;
int subscribeBackground = ContextCompat.getColor(activity, R.color.subscribe_background_color);
int subscribeBackground = ThemeHelper
.resolveColorFromAttr(activity, R.attr.colorPrimary);
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
int subscribedBackground = ContextCompat.getColor(activity, R.color.subscribed_background_color);
int subscribedBackground = ContextCompat
.getColor(activity, R.color.subscribed_background_color);
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
if (!isSubscribed) {
headerSubscribeButton.setText(R.string.subscribe_button_title);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground, subscribeBackground);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground,
subscribeBackground);
animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText);
} else {
headerSubscribeButton.setText(R.string.subscribed_button_title);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground, subscribedBackground);
animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground,
subscribedBackground);
animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText);
}
@ -337,14 +403,42 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
}
@Override
protected Single<ChannelInfo> loadResult(boolean forceLoad) {
protected Single<ChannelInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
}
/*//////////////////////////////////////////////////////////////////////////
// OnClick
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onClick(final View v) {
if (isLoading.get() || currentInfo == null) {
return;
}
switch (v.getId()) {
case R.id.sub_channel_avatar_view:
case R.id.sub_channel_title_view:
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
try {
NavigationHelper.openChannelFragment(getFragmentManager(),
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
} else if (DEBUG) {
Log.i(TAG, "Can't open parent channel because we got no channel URL");
}
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@ -353,47 +447,100 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
public void showLoading() {
super.showLoading();
imageLoader.cancelDisplayTask(headerChannelBanner);
imageLoader.cancelDisplayTask(headerAvatarView);
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView);
animateView(headerSubscribeButton, false, 100);
}
@Override
public void handleResult(@NonNull ChannelInfo result) {
public void handleResult(@NonNull final ChannelInfo result) {
super.handleResult(result);
headerRootLayout.setVisibility(View.VISIBLE);
imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner,
IMAGE_LOADER.displayImage(result.getBannerUrl(), headerChannelBanner,
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerSubscribersTextView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
headerSubscribersTextView.setText(Localization
.shortSubscriberCount(activity, result.getSubscriberCount()));
} else {
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
}
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
headerSubChannelTitleView.setText(String.format(
getString(R.string.channel_created_by),
currentInfo.getParentChannelName())
);
headerSubChannelTitleView.setVisibility(View.VISIBLE);
headerSubChannelAvatarView.setVisibility(View.VISIBLE);
} else {
headerSubChannelTitleView.setVisibility(View.GONE);
}
if (menuRssButton != null) {
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
}
playlistCtrl.setVisibility(View.VISIBLE);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
List<Throwable> errors = new ArrayList<>(result.getErrors());
if (!errors.isEmpty()) {
// handling ContentNotSupportedException not to show the error but an appropriate string
// so that crashes won't be sent uselessly and the user will understand what happened
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
Throwable throwable = it.next();
if (throwable instanceof ContentNotSupportedException) {
showContentNotSupported();
it.remove();
}
}
if (!errors.isEmpty()) {
showSnackBarError(errors, UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
}
if (disposables != null) disposables.clear();
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
if (disposables != null) {
disposables.clear();
}
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
updateSubscription(result);
monitorSubscription(result);
headerPlayAllButton.setOnClickListener(
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
.playOnMainPlayer(activity, getPlayQueue(), true));
headerPopupButton.setOnClickListener(view -> NavigationHelper
.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
.playOnBackgroundPlayer(activity, getPlayQueue(), false));
headerPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
return true;
});
headerBackgroundButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
return true;
});
}
private void showContentNotSupported() {
contentNotSupportedTextView.setVisibility(View.VISIBLE);
kaomojiTextView.setText("(︶︹︺)");
kaomojiTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
noVideosTextView.setVisibility(View.GONE);
}
private PlayQueue getPlayQueue() {
@ -407,17 +554,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
streamItems.add((StreamInfoItem) i);
}
}
return new ChannelPlayQueue(
currentInfo.getServiceId(),
currentInfo.getUrl(),
currentInfo.getNextPageUrl(),
streamItems,
index
);
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getNextPage(), streamItems, index);
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
if (!result.getErrors().isEmpty()) {
@ -434,19 +576,17 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
if (exception instanceof ContentNotAvailableException) {
showError(getString(R.string.content_not_available), false);
} else {
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception,
UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(serviceId),
url,
errorId);
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
int errorId = exception instanceof ExtractionException
? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(serviceId), url, errorId);
return true;
}
@ -455,8 +595,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(String title) {
public void setTitle(final String title) {
super.setTitle(title);
if (!useAsFrontPage) headerTitleView.setText(title);
if (!useAsFrontPage) {
headerTitleView.setText(title);
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -6,14 +6,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.TooltipCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
@ -30,31 +24,37 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.util.FireTvUtils;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -73,14 +73,13 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import static android.text.Html.escapeHtml;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SearchFragment
extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
implements BackPressable {
/*//////////////////////////////////////////////////////////////////////////
// Search
//////////////////////////////////////////////////////////////////////////*/
@ -92,44 +91,51 @@ public class SearchFragment
private static final int THRESHOLD_NETWORK_SUGGESTION = 1;
/**
* How much time have to pass without emitting a item (i.e. the user stop typing) to fetch/show the suggestions, in milliseconds.
* How much time have to pass without emitting a item (i.e. the user stop typing)
* to fetch/show the suggestions, in milliseconds.
*/
private static final int SUGGESTIONS_DEBOUNCE = 120; //ms
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
@State
protected int filterItemCheckedId = -1;
int filterItemCheckedId = -1;
@State
protected int serviceId = Constants.NO_SERVICE_ID;
// this three represet the current search query
// these three represents the current search query
@State
protected String searchString;
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.
*/
@State
protected String[] contentFilter = new String[0];
String[] contentFilter = new String[0];
@State
protected String sortFilter;
// these represtent the last search
String sortFilter;
// these represents the last search
@State
protected String lastSearchedString;
String lastSearchedString;
@State
protected boolean wasSearchFocused = false;
String searchSuggestion;
@State
boolean isCorrectedSearch;
@State
boolean wasSearchFocused = false;
private Map<Integer, String> menuItemToFilterName;
private StreamingService service;
private String currentPageUrl;
private String nextPageUrl;
private Page nextPage;
private String contentCountry;
private boolean isSuggestionsEnabled = true;
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
private Disposable searchDisposable;
private Disposable suggestionDisposable;
private final CompositeDisposable disposables = new CompositeDisposable();
@ -145,12 +151,16 @@ public class SearchFragment
private EditText searchEditText;
private View searchClear;
private TextView correctSuggestion;
private View suggestionsPanel;
private RecyclerView suggestionsRecyclerView;
/*////////////////////////////////////////////////////////////////////////*/
public static SearchFragment getInstance(int serviceId, String searchString) {
private TextWatcher textWatcher;
public static SearchFragment getInstance(final int serviceId, final String searchString) {
SearchFragment searchFragment = new SearchFragment();
searchFragment.setQuery(serviceId, searchString, new String[0], "");
@ -173,33 +183,37 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
public void onAttach(final Context context) {
super.onAttach(context);
suggestionListAdapter = new SuggestionListAdapter(activity);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
boolean isSearchHistoryEnabled = preferences
.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
historyRecordManager = new HistoryRecordManager(context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true);
contentCountry = preferences.getString(getString(R.string.content_country_key), getString(R.string.default_country_value));
isSuggestionsEnabled = preferences
.getBoolean(getString(R.string.show_search_suggestions_key), true);
contentCountry = preferences.getString(getString(R.string.content_country_key),
getString(R.string.default_localization_key));
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_search, container, false);
}
@Override
public void onViewCreated(View rootView, Bundle savedInstanceState) {
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
showSearchOnStart();
initSearchListeners();
@ -211,15 +225,23 @@ public class SearchFragment
wasSearchFocused = searchEditText.hasFocus();
if (searchDisposable != null) searchDisposable.dispose();
if (suggestionDisposable != null) suggestionDisposable.dispose();
if (disposables != null) disposables.clear();
if (searchDisposable != null) {
searchDisposable.dispose();
}
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
if (disposables != null) {
disposables.clear();
}
hideKeyboardSearch();
}
@Override
public void onResume() {
if (DEBUG) Log.d(TAG, "onResume() called");
if (DEBUG) {
Log.d(TAG, "onResume() called");
}
super.onResume();
try {
@ -245,7 +267,11 @@ public class SearchFragment
}
}
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
handleSearchSuggestion();
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
initSuggestionObserver();
}
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
showKeyboardSearch();
@ -259,7 +285,9 @@ public class SearchFragment
@Override
public void onDestroyView() {
if (DEBUG) Log.d(TAG, "onDestroyView() called");
if (DEBUG) {
Log.d(TAG, "onDestroyView() called");
}
unsetSearchListeners();
super.onDestroyView();
}
@ -267,19 +295,27 @@ public class SearchFragment
@Override
public void onDestroy() {
super.onDestroy();
if (searchDisposable != null) searchDisposable.dispose();
if (suggestionDisposable != null) suggestionDisposable.dispose();
if (disposables != null) disposables.clear();
if (searchDisposable != null) {
searchDisposable.dispose();
}
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
if (disposables != null) {
disposables.clear();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK
&& !TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter);
} else Log.e(TAG, "ReCaptcha failed");
} else {
Log.e(TAG, "ReCaptcha failed");
}
break;
default:
@ -293,25 +329,27 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
public int getMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
return getSuggestionMovementFlags(recyclerView, viewHolder);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder viewHolder1) {
public boolean onMove(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder,
@NonNull final RecyclerView.ViewHolder viewHolder1) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int i) {
onSuggestionItemSwiped(viewHolder, i);
}
}).attachToRecyclerView(suggestionsRecyclerView);
@ -319,6 +357,8 @@ public class SearchFragment
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
}
/*//////////////////////////////////////////////////////////////////////////
@ -326,21 +366,19 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void writeTo(Queue<Object> objectsToSave) {
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentPageUrl);
objectsToSave.add(nextPageUrl);
objectsToSave.add(nextPage);
}
@Override
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentPageUrl = (String) savedObjects.poll();
nextPageUrl = (String) savedObjects.poll();
nextPage = (Page) savedObjects.poll();
}
@Override
public void onSaveInstanceState(Bundle bundle) {
public void onSaveInstanceState(final Bundle bundle) {
searchString = searchEditText != null
? searchEditText.getText().toString()
: searchString;
@ -372,7 +410,7 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = activity.getSupportActionBar();
@ -386,13 +424,20 @@ public class SearchFragment
int itemId = 0;
boolean isFirstItem = true;
final Context c = getContext();
for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
if (filter.equals("music_songs")) {
MenuItem musicItem = menu.add(2,
itemId++,
0,
"YouTube Music");
musicItem.setEnabled(false);
}
menuItemToFilterName.put(itemId, filter);
MenuItem item = menu.add(1,
itemId++,
0,
ServiceHelper.getTranslatedFilterString(filter, c));
if(isFirstItem) {
if (isFirstItem) {
item.setChecked(true);
isFirstItem = false;
}
@ -403,19 +448,20 @@ public class SearchFragment
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
List<String> contentFilter = new ArrayList<>(1);
contentFilter.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, contentFilter);
public boolean onOptionsItemSelected(final MenuItem item) {
List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf);
return true;
}
private void restoreFilterChecked(Menu menu, int itemId) {
private void restoreFilterChecked(final Menu menu, final int itemId) {
if (itemId != -1) {
MenuItem item = menu.findItem(itemId);
if (item == null) return;
if (item == null) {
return;
}
item.setChecked(true);
}
@ -425,13 +471,13 @@ public class SearchFragment
// Search
//////////////////////////////////////////////////////////////////////////*/
private TextWatcher textWatcher;
private void showSearchOnStart() {
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → "
+ searchString
+ ", lastSearchedQuery → "
+ lastSearchedString);
if (DEBUG) {
Log.d(TAG, "showSearchOnStart() called, searchQuery → "
+ searchString
+ ", lastSearchedQuery → "
+ lastSearchedString);
}
searchEditText.setText(searchString);
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
@ -451,14 +497,20 @@ public class SearchFragment
}
private void initSearchListeners() {
if (DEBUG) Log.d(TAG, "initSearchListeners() called");
if (DEBUG) {
Log.d(TAG, "initSearchListeners() called");
}
searchClear.setOnClickListener(v -> {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.gotoMainFragment(getFragmentManager());
return;
}
correctSuggestion.setVisibility(View.GONE);
searchEditText.setText("");
suggestionListAdapter.setItems(new ArrayList<>());
showKeyboardSearch();
@ -467,53 +519,65 @@ public class SearchFragment
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
searchEditText.setOnClickListener(v -> {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
if(FireTvUtils.isFireTv()){
if (AndroidTvUtils.isTv(getContext())) {
showKeyboardSearch();
}
});
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
if (DEBUG) {
Log.d(TAG, "onFocusChange() called with: "
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
}
if (isSuggestionsEnabled && hasFocus
&& errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
});
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
@Override
public void onSuggestionItemSelected(SuggestionItem item) {
public void onSuggestionItemSelected(final SuggestionItem item) {
search(item.query, new String[0], "");
searchEditText.setText(item.query);
}
@Override
public void onSuggestionItemInserted(SuggestionItem item) {
public void onSuggestionItemInserted(final SuggestionItem item) {
searchEditText.setText(item.query);
searchEditText.setSelection(searchEditText.getText().length());
}
@Override
public void onSuggestionItemLongClick(SuggestionItem item) {
if (item.fromHistory) showDeleteSuggestionDialog(item);
public void onSuggestionItemLongClick(final SuggestionItem item) {
if (item.fromHistory) {
showDeleteSuggestionDialog(item);
}
}
});
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
if (textWatcher != null) {
searchEditText.removeTextChangedListener(textWatcher);
}
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) {
}
@Override
public void afterTextChanged(Editable s) {
public void afterTextChanged(final Editable s) {
String newText = searchEditText.getText().toString();
suggestionPublisher.onNext(newText);
}
@ -522,48 +586,62 @@ public class SearchFragment
searchEditText.setOnEditorActionListener(
(TextView v, int actionId, KeyEvent event) -> {
if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], "
+ "actionId = [" + actionId + "], event = [" + event + "]");
}
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){
if (actionId == EditorInfo.IME_ACTION_PREVIOUS) {
hideKeyboardSearch();
} else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString(), new String[0], "");
return true;
}
return false;
});
if (suggestionDisposable == null || suggestionDisposable.isDisposed())
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
initSuggestionObserver();
}
}
private void unsetSearchListeners() {
if (DEBUG) Log.d(TAG, "unsetSearchListeners() called");
if (DEBUG) {
Log.d(TAG, "unsetSearchListeners() called");
}
searchClear.setOnClickListener(null);
searchClear.setOnLongClickListener(null);
searchEditText.setOnClickListener(null);
searchEditText.setOnFocusChangeListener(null);
searchEditText.setOnEditorActionListener(null);
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
if (textWatcher != null) {
searchEditText.removeTextChangedListener(textWatcher);
}
textWatcher = null;
}
private void showSuggestionsPanel() {
if (DEBUG) Log.d(TAG, "showSuggestionsPanel() called");
if (DEBUG) {
Log.d(TAG, "showSuggestionsPanel() called");
}
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
}
private void hideSuggestionsPanel() {
if (DEBUG) Log.d(TAG, "hideSuggestionsPanel() called");
if (DEBUG) {
Log.d(TAG, "hideSuggestionsPanel() called");
}
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
}
private void showKeyboardSearch() {
if (DEBUG) Log.d(TAG, "showKeyboardSearch() called");
if (searchEditText == null) return;
if (DEBUG) {
Log.d(TAG, "showKeyboardSearch() called");
}
if (searchEditText == null) {
return;
}
if (searchEditText.requestFocus()) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
@ -573,19 +651,26 @@ public class SearchFragment
}
private void hideKeyboardSearch() {
if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called");
if (searchEditText == null) return;
if (DEBUG) {
Log.d(TAG, "hideKeyboardSearch() called");
}
if (searchEditText == null) {
return;
}
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
InputMethodManager imm = (InputMethodManager) activity
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
InputMethodManager.RESULT_UNCHANGED_SHOWN);
searchEditText.clearFocus();
}
private void showDeleteSuggestionDialog(final SuggestionItem item) {
if (activity == null || historyRecordManager == null || suggestionPublisher == null ||
searchEditText == null || disposables == null) return;
if (activity == null || historyRecordManager == null || suggestionPublisher == null
|| searchEditText == null || disposables == null) {
return;
}
final String query = item.query;
new AlertDialog.Builder(activity)
.setTitle(query)
@ -619,20 +704,20 @@ public class SearchFragment
return false;
}
public void giveSearchEditTextFocus() {
showKeyboardSearch();
}
private void initSuggestionObserver() {
if (DEBUG) Log.d(TAG, "initSuggestionObserver() called");
if (suggestionDisposable != null) suggestionDisposable.dispose();
if (DEBUG) {
Log.d(TAG, "initSuggestionObserver() called");
}
if (suggestionDisposable != null) {
suggestionDisposable.dispose();
}
final Observable<String> observable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWith(searchString != null
? searchString
: "")
.filter(searchString -> isSuggestionsEnabled);
.filter(ss -> isSuggestionsEnabled);
suggestionDisposable = observable
.switchMap(query -> {
@ -641,13 +726,15 @@ public class SearchFragment
final Observable<List<SuggestionItem>> local = flowable.toObservable()
.map(searchHistoryEntries -> {
List<SuggestionItem> result = new ArrayList<>();
for (SearchHistoryEntry entry : searchHistoryEntries)
for (SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return result;
});
if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
// Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION
// Only pass through if the query length
// is equal or greater than THRESHOLD_NETWORK_SUGGESTION
return local.materialize();
}
@ -664,7 +751,9 @@ public class SearchFragment
return Observable.zip(local, network, (localResult, networkResult) -> {
List<SuggestionItem> result = new ArrayList<>();
if (localResult.size() > 0) result.addAll(localResult);
if (localResult.size() > 0) {
result.addAll(localResult);
}
// Remove duplicates
final Iterator<SuggestionItem> iterator = networkResult.iterator();
@ -678,7 +767,9 @@ public class SearchFragment
}
}
if (networkResult.size() > 0) result.addAll(networkResult);
if (networkResult.size() > 0) {
result.addAll(networkResult);
}
return result;
}).materialize();
})
@ -688,12 +779,7 @@ public class SearchFragment
if (listNotification.isOnNext()) {
handleSuggestions(listNotification.getValue());
} else if (listNotification.isOnError()) {
Throwable error = listNotification.getError();
if (!ExtractorHelper.hasAssignableCauseThrowable(error,
IOException.class, SocketException.class,
InterruptedException.class, InterruptedIOException.class)) {
onSuggestionError(error);
}
onSuggestionError(listNotification.getError());
}
});
}
@ -703,17 +789,21 @@ public class SearchFragment
// no-op
}
private void search(final String searchString, String[] contentFilter, String sortFilter) {
if (DEBUG) Log.d(TAG, "search() called with: query = [" + searchString + "]");
if (searchString.isEmpty()) return;
private void search(final String ss, final String[] cf, final String sf) {
if (DEBUG) {
Log.d(TAG, "search() called with: query = [" + ss + "]");
}
if (ss.isEmpty()) {
return;
}
try {
final StreamingService service = NewPipe.getServiceByUrl(searchString);
if (service != null) {
final StreamingService streamingService = NewPipe.getServiceByUrl(ss);
if (streamingService != null) {
showLoading();
disposables.add(Observable
.fromCallable(() ->
NavigationHelper.getIntentByLink(activity, service, searchString))
NavigationHelper.getIntentByLink(activity, streamingService, ss))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
@ -723,36 +813,41 @@ public class SearchFragment
showError(getString(R.string.url_not_supported_toast), false)));
return;
}
} catch (Exception e) {
} catch (Exception ignored) {
// Exception occurred, it's not a url
}
lastSearchedString = this.searchString;
this.searchString = searchString;
this.searchString = ss;
infoListAdapter.clearStreamItemList();
hideSuggestionsPanel();
hideKeyboardSearch();
historyRecordManager.onSearched(serviceId, searchString)
historyRecordManager.onSearched(serviceId, ss)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {},
ignored -> {
},
error -> showSnackBarError(error, UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), searchString, 0)
NewPipe.getNameOfService(serviceId), ss, 0)
);
suggestionPublisher.onNext(searchString);
suggestionPublisher.onNext(ss);
startLoading(false);
}
@Override
public void startLoading(boolean forceLoad) {
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
if (disposables != null) disposables.clear();
if (searchDisposable != null) searchDisposable.dispose();
if (disposables != null) {
disposables.clear();
}
if (searchDisposable != null) {
searchDisposable.dispose();
}
searchDisposable = ExtractorHelper.searchFor(serviceId,
searchString,
Arrays.asList(contentFilter),
sortFilter)
searchString,
Arrays.asList(contentFilter),
sortFilter)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
@ -762,16 +857,20 @@ public class SearchFragment
@Override
protected void loadMoreItems() {
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
if (!Page.isValid(nextPage)) {
return;
}
isLoading.set(true);
showListFooter(true);
if (searchDisposable != null) searchDisposable.dispose();
if (searchDisposable != null) {
searchDisposable.dispose();
}
searchDisposable = ExtractorHelper.getMoreSearchItems(
serviceId,
searchString,
asList(contentFilter),
sortFilter,
nextPageUrl)
serviceId,
searchString,
asList(contentFilter),
sortFilter,
nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
@ -785,7 +884,7 @@ public class SearchFragment
}
@Override
protected void onItemSelected(InfoItem selectedItem) {
protected void onItemSelected(final InfoItem selectedItem) {
super.onItemSelected(selectedItem);
hideKeyboardSearch();
}
@ -794,22 +893,22 @@ public class SearchFragment
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeContentFilter(MenuItem item, List<String> contentFilter) {
private void changeContentFilter(final MenuItem item, final List<String> cf) {
this.filterItemCheckedId = item.getItemId();
item.setChecked(true);
this.contentFilter = new String[] {contentFilter.get(0)};
this.contentFilter = new String[]{cf.get(0)};
if (!TextUtils.isEmpty(searchString)) {
search(searchString, this.contentFilter, sortFilter);
}
}
private void setQuery(int serviceId, String searchString, String[] contentfilter, String sortFilter) {
this.serviceId = serviceId;
private void setQuery(final int sid, final String ss, final String[] cf, final String sf) {
this.serviceId = sid;
this.searchString = searchString;
this.contentFilter = contentfilter;
this.sortFilter = sortFilter;
this.contentFilter = cf;
this.sortFilter = sf;
}
/*//////////////////////////////////////////////////////////////////////////
@ -817,7 +916,9 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) {
if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]");
if (DEBUG) {
Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]");
}
suggestionsRecyclerView.smoothScrollToPosition(0);
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
@ -826,9 +927,13 @@ public class SearchFragment
}
}
public void onSuggestionError(Throwable exception) {
if (DEBUG) Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
if (super.onError(exception)) return;
public void onSuggestionError(final Throwable exception) {
if (DEBUG) {
Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
}
if (super.onError(exception)) {
return;
}
int errorId = exception instanceof ParsingException
? R.string.parsing_error
@ -848,7 +953,7 @@ public class SearchFragment
}
@Override
public void showError(String message, boolean showRetryButton) {
public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton);
hideSuggestionsPanel();
hideKeyboardSearch();
@ -859,18 +964,22 @@ public class SearchFragment
//////////////////////////////////////////////////////////////////////////*/
@Override
public void handleResult(@NonNull SearchInfo result) {
public void handleResult(@NonNull final SearchInfo result) {
final List<Throwable> exceptions = result.getErrors();
if (!exceptions.isEmpty()
&& !(exceptions.size() == 1
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){
&& !(exceptions.size() == 1
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId), searchString, 0);
}
searchSuggestion = result.getSearchSuggestion();
isCorrectedSearch = result.isCorrectedSearch();
handleSearchSuggestion();
lastSearchedString = searchString;
nextPageUrl = result.getNextPageUrl();
currentPageUrl = result.getUrl();
nextPage = result.getNextPage();
if (infoListAdapter.getItemsList().size() == 0) {
if (!result.getRelatedItems().isEmpty()) {
@ -885,24 +994,58 @@ public class SearchFragment
super.handleResult(result);
}
private void handleSearchSuggestion() {
if (TextUtils.isEmpty(searchSuggestion)) {
correctSuggestion.setVisibility(View.GONE);
} else {
final String helperText = getString(isCorrectedSearch
? R.string.search_showing_result_for
: R.string.did_you_mean);
final String highlightedSearchSuggestion =
"<b><i>" + escapeHtml(searchSuggestion) + "</i></b>";
correctSuggestion.setText(
Html.fromHtml(String.format(helperText, highlightedSearchSuggestion)));
correctSuggestion.setOnClickListener(v -> {
correctSuggestion.setVisibility(View.GONE);
search(searchSuggestion, contentFilter, sortFilter);
searchEditText.setText(searchSuggestion);
});
correctSuggestion.setOnLongClickListener(v -> {
searchEditText.setText(searchSuggestion);
searchEditText.setSelection(searchSuggestion.length());
showKeyboardSearch();
return true;
});
correctSuggestion.setVisibility(View.VISIBLE);
}
}
@Override
public void handleNextItems(ListExtractor.InfoItemsPage result) {
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
showListFooter(false);
currentPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems());
nextPageUrl = result.getNextPageUrl();
nextPage = result.getNextPage();
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId)
, "\"" + searchString + "\" → page: " + nextPageUrl, 0);
NewPipe.getNameOfService(serviceId),
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
+ "pageIds: " + nextPage.getIds() + ", "
+ "pageCookies: " + nextPage.getCookies(), 0);
}
super.handleNextItems(result);
}
@Override
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
protected boolean onError(final Throwable exception) {
if (super.onError(exception)) {
return true;
}
if (exception instanceof SearchExtractor.NothingFoundException) {
infoListAdapter.clearStreamItemList();
@ -922,13 +1065,20 @@ public class SearchFragment
// Suggestion item touch helper
//////////////////////////////////////////////////////////////////////////*/
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return 0;
}
final SuggestionItem item = suggestionListAdapter.getItem(position);
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
return item.fromHistory ? makeMovementFlags(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
}
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int i) {
final int position = viewHolder.getAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)

View file

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

View file

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

View file

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