Merge pull request Android TV support #2806

Closes #2806
This commit is contained in:
TobiGr 2020-04-23 22:32:09 +02:00
commit 1d9e54b086
37 changed files with 1743 additions and 91 deletions

View file

@ -11,7 +11,6 @@ import android.preference.PreferenceManager;
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;
@ -73,6 +72,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
@ -86,6 +86,7 @@ import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.views.AnimatedProgressBar;
import org.schabi.newpipe.views.LargeTextMovementMethod;
import java.io.Serializable;
import java.util.Collection;
@ -471,10 +472,13 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
videoTitleTextView.setMaxLines(1);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoDescriptionView.setFocusable(false);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
} else {
videoTitleTextView.setMaxLines(10);
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
videoDescriptionView.setFocusable(true);
videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
}
}
@ -511,7 +515,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
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());
thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view);
thumbsUpImageView = rootView.findViewById(R.id.detail_thumbs_up_img_view);
@ -533,6 +536,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout);
setHeightThumbnail();
thumbnailBackgroundButton.requestFocus();
if (AndroidTvUtils.isTv()) {
// 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

View file

@ -16,7 +16,6 @@ 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 org.schabi.newpipe.R;
@ -35,6 +34,7 @@ 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;
@ -56,6 +56,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
protected InfoListAdapter infoListAdapter;
protected RecyclerView itemsList;
private int focusedPosition = -1;
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
@ -129,20 +130,53 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
return "." + infoListAdapter.getItemsList().size() + ".list";
}
private int getFocusedPosition() {
View focusedItem = itemsList.getFocusedChild();
if (focusedItem != null) {
RecyclerView.ViewHolder itemHolder = itemsList.findContainingViewHolder(focusedItem);
if (itemHolder != null) {
return itemHolder.getAdapterPosition();
}
}
return -1;
}
@Override
public void writeTo(final Queue<Object> objectsToSave) {
if (useDefaultStateSaving) {
objectsToSave.add(infoListAdapter.getItemsList());
if (!useDefaultStateSaving) {
return;
}
objectsToSave.add(infoListAdapter.getItemsList());
objectsToSave.add(getFocusedPosition());
}
@Override
@SuppressWarnings("unchecked")
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
if (useDefaultStateSaving) {
infoListAdapter.getItemsList().clear();
infoListAdapter.getItemsList().addAll((List<InfoItem>) savedObjects.poll());
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
@ -162,6 +196,18 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
}
}
@Override
public void onStop() {
focusedPosition = getFocusedPosition();
super.onStop();
}
@Override
public void onStart() {
super.onStart();
restoreFocus(focusedPosition);
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@ -175,7 +221,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
}
protected RecyclerView.LayoutManager getListLayoutManager() {
return new LinearLayoutManager(activity);
return new SuperScrollLayoutManager(activity);
}
protected RecyclerView.LayoutManager getGridLayoutManager() {

View file

@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView;
import java.util.Queue;
@ -149,9 +150,13 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
if (currentWorker != null) {
currentWorker.dispose();
}
forbidDownwardFocusScroll();
currentWorker = loadMoreItemsLogic()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::allowDownwardFocusScroll)
.subscribe((@io.reactivex.annotations.NonNull
ListExtractor.InfoItemsPage InfoItemsPage) -> {
isLoading.set(false);
@ -162,6 +167,18 @@ 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(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);

View file

@ -41,6 +41,7 @@ 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.AndroidTvUtils;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -49,7 +50,6 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.FireTvUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
@ -511,7 +511,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
if (FireTvUtils.isFireTv()) {
if (AndroidTvUtils.isTv()) {
showKeyboardSearch();
}
});