Skip to content

Commit

Permalink
Merge pull request Android TV support #2806
Browse files Browse the repository at this point in the history
Closes #2806
  • Loading branch information
TobiGr committed Apr 23, 2020
2 parents bc4a598 + 651cdec commit 04ab753
Show file tree
Hide file tree
Showing 37 changed files with 1,743 additions and 91 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>

<application
android:banner="@mipmap/newpipe_tv_banner"
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand All @@ -26,6 +29,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,58 @@
package com.google.android.material.appbar;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.OverScroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

import java.lang.reflect.Field;

// See https://stackoverflow.com/questions/56849221#57997489
public final class FlingBehavior extends AppBarLayout.Behavior {
private final Rect focusScrollRect = new Rect();

public FlingBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onRequestChildRectangleOnScreen(
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
@NonNull final Rect rectangle, final boolean immediate) {

focusScrollRect.set(rectangle);

coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);

int height = coordinatorLayout.getHeight();

if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
// the child is too big to fit inside ourselves completely, ignore request
return false;
}

int dy;

if (focusScrollRect.bottom > height) {
dy = focusScrollRect.top;
} else if (focusScrollRect.top < 0) {
// scrolling up
dy = -(height - focusScrollRect.bottom);
} else {
// nothing to do
return false;
}

int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);

return consumed == dy;
}

public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
final MotionEvent ev) {
switch (ev.getActionMasked()) {
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/org/schabi/newpipe/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
Expand All @@ -74,6 +75,7 @@
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -142,6 +144,10 @@ && getSupportFragmentManager().getBackStackEntryCount() == 0) {
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}

if (AndroidTvUtils.isTv()) {
FocusOverlayView.setupFocusObserver(this);
}
}

private void setupDrawer() throws Exception {
Expand Down Expand Up @@ -526,6 +532,14 @@ public void onBackPressed() {
Log.d(TAG, "onBackPressed() called");
}

if (AndroidTvUtils.isTv()) {
View drawerPanel = findViewById(R.id.navigation);
if (drawer.isDrawerOpen(drawerPanel)) {
drawer.closeDrawers();
return;
}
}

Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/org/schabi/newpipe/RouterActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
import org.schabi.newpipe.util.urlfinder.UrlFinder;

import java.io.Serializable;
Expand Down Expand Up @@ -341,6 +343,10 @@ private void showDialog(final List<AdapterChoiceItem> choices) {
selectedPreviously = selectedRadioPosition;

alertDialog.show();

if (AndroidTvUtils.isTv()) {
FocusOverlayView.setupFocusObserver(alertDialog);
}
}

private List<AdapterChoiceItem> getChoicesForService(final StreamingService service,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import androidx.appcompat.widget.Toolbar;

import org.schabi.newpipe.R;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;

import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.MissionsFragment;
Expand Down Expand Up @@ -54,6 +56,10 @@ public void onGlobalLayout() {
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});

if (AndroidTvUtils.isTv()) {
FocusOverlayView.setupFocusObserver(this);
}
}

private void updateFragments() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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;
Expand Down Expand Up @@ -73,6 +72,7 @@
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;
Expand All @@ -86,6 +86,7 @@
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;
Expand Down Expand Up @@ -471,10 +472,13 @@ private void toggleTitleAndDescription() {
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);
}
}
Expand Down Expand Up @@ -511,7 +515,6 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
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);
Expand All @@ -533,6 +536,18 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
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;
Expand All @@ -35,6 +34,7 @@
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;
Expand All @@ -56,6 +56,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>

protected InfoListAdapter infoListAdapter;
protected RecyclerView itemsList;
private int focusedPosition = -1;

/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
Expand Down Expand Up @@ -129,20 +130,53 @@ public String generateSuffix() {
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
Expand All @@ -162,6 +196,18 @@ protected void onRestoreInstanceState(@NonNull final Bundle bundle) {
}
}

@Override
public void onStop() {
focusedPosition = getFocusedPosition();
super.onStop();
}

@Override
public void onStart() {
super.onStart();
restoreFocus(focusedPosition);
}

/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
Expand All @@ -175,7 +221,7 @@ protected View getListFooter() {
}

protected RecyclerView.LayoutManager getListLayoutManager() {
return new LinearLayoutManager(activity);
return new SuperScrollLayoutManager(activity);
}

protected RecyclerView.LayoutManager getGridLayoutManager() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;

Expand Down Expand Up @@ -149,9 +150,13 @@ protected void loadMoreItems() {
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);
Expand All @@ -162,6 +167,18 @@ protected void loadMoreItems() {
});
}

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);
Expand Down
Loading

0 comments on commit 04ab753

Please sign in to comment.