From eaa1179572e68dc264a54677314d39e18808e628 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:02:16 +0700 Subject: [PATCH 001/663] Fix scrolling comments list AppBarLayout mostly gets it, but we still need to uphold our own part - expanding it back after focus returns to it --- .../material/appbar/FlingBehavior.java | 39 ++++++++++++++ .../fragments/list/BaseListFragment.java | 3 +- .../views/SuperScrollLayoutManager.java | 53 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 4a2662f53..ea2857b03 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -1,10 +1,13 @@ package com.google.android.material.appbar; +import android.annotation.SuppressLint; 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; @@ -13,10 +16,46 @@ // check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489 public final class FlingBehavior extends AppBarLayout.Behavior { + private final Rect focusScrollRect = new Rect(); + public FlingBehavior(Context context, AttributeSet attrs) { super(context, attrs); } + @Override + public boolean onRequestChildRectangleOnScreen(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child, @NonNull Rect rectangle, 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 offset = getTopAndBottomOffset(); + + 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 newOffset = offset + dy; + + int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0); + + return consumed == dy; + } + @Override public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) { switch (ev.getActionMasked()) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index d6fd1dd00..a3844a92f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -34,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; @@ -147,7 +148,7 @@ protected View getListFooter() { } protected RecyclerView.LayoutManager getListLayoutManager() { - return new LinearLayoutManager(activity); + return new SuperScrollLayoutManager(activity); } protected RecyclerView.LayoutManager getGridLayoutManager() { diff --git a/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java b/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java new file mode 100644 index 000000000..33fe7b9cc --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) Eltex ltd 2019 + * SuperScrollLayoutManager.java is part of NewPipe. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.graphics.Rect; +import android.view.FocusFinder; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public final class SuperScrollLayoutManager extends LinearLayoutManager { + private final Rect handy = new Rect(); + + public SuperScrollLayoutManager(Context context) { + super(context); + } + + @Override + public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonNull View child, @NonNull Rect rect, boolean immediate, boolean focusedChildVisible) { + if (!parent.isInTouchMode()) { + // only activate when in directional navigation mode (Android TV etc) — fine grained + // touch scrolling is better served by nested scroll system + + if (!focusedChildVisible || getFocusedChild() == child) { + handy.set(rect); + + parent.offsetDescendantRectToMyCoords(child, handy); + + parent.requestRectangleOnScreen(handy, immediate); + } + } + + return super.requestChildRectangleOnScreen(parent, child, rect, immediate, focusedChildVisible); + } +} From 4806ac62ee62719998aa1361df8f3ce7156e9c06 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:04:13 +0700 Subject: [PATCH 002/663] Correctly move focus from toolbar search bar to dropdown We don't hide MainFragment when search is show, so FocusFinder sometimes gives focus to (obscured) main content --- app/src/main/res/layout/toolbar_search_layout.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/toolbar_search_layout.xml b/app/src/main/res/layout/toolbar_search_layout.xml index fdc7e6d6b..bd5b2d5c7 100644 --- a/app/src/main/res/layout/toolbar_search_layout.xml +++ b/app/src/main/res/layout/toolbar_search_layout.xml @@ -19,6 +19,7 @@ android:drawablePadding="8dp" android:focusable="true" android:focusableInTouchMode="true" + android:nextFocusDown="@+id/suggestions_list" android:hint="@string/search" android:imeOptions="actionSearch|flagNoFullscreen" android:inputType="textFilter|textNoSuggestions" From 8952e2b0cdf362a9afb813f2c4ed20f998d474f5 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:07:27 +0700 Subject: [PATCH 003/663] Close DrawerLayout on back button press --- .../main/java/org/schabi/newpipe/MainActivity.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index c24d77d03..3c18c25f6 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -20,6 +20,7 @@ package org.schabi.newpipe; +import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -29,6 +30,8 @@ import android.os.Looper; import android.preference.PreferenceManager; import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -59,6 +62,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.FireTvUtils; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; @@ -408,13 +412,20 @@ protected void onNewIntent(Intent intent) { public void onBackPressed() { if (DEBUG) Log.d(TAG, "onBackPressed() called"); + if (FireTvUtils.isFireTv()) { + View drawerPanel = findViewById(R.id.navigation_layout); + 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 if (fragment instanceof BackPressable) { if (((BackPressable) fragment).onBackPressed()) return; } - if (getSupportFragmentManager().getBackStackEntryCount() == 1) { finish(); } else super.onBackPressed(); From 2b39438eba2f4a78c288fd8a5121ce0672c544d0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:10:57 +0700 Subject: [PATCH 004/663] Fix scrolling in main screen grid GridLayoutManager is buggy - https://issuetracker.google.com/issues/37067220: it randomly loses or incorrectly assigns focus when being scrolled via direction-based navigation. This commit reimplements onFocusSearchFailed() on top of scrollBy() to work around that problem. Ordinary touch-based navigation should not be affected. --- .../fragments/list/BaseListFragment.java | 3 +- .../newpipe/local/BaseLocalListFragment.java | 3 +- .../subscription/SubscriptionFragment.java | 3 +- .../newpipe/views/FixedGridLayoutManager.java | 59 +++++++++++++++ .../newpipe/views/NewPipeRecyclerView.java | 72 +++++++++++++++++++ .../giga/ui/fragment/MissionsFragment.java | 3 +- app/src/main/res/layout/fragment_kiosk.xml | 2 +- 7 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java create mode 100644 app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index a3844a92f..88684f2e7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -35,6 +35,7 @@ import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.views.SuperScrollLayoutManager; +import org.schabi.newpipe.views.FixedGridLayoutManager; import java.util.List; import java.util.Queue; @@ -156,7 +157,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { 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 GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 414a9b6b5..c1293e240 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -18,6 +18,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.list.ListViewContract; +import org.schabi.newpipe.views.FixedGridLayoutManager; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -95,7 +96,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { 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 GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java index bff6c1b3a..ea820b71e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java @@ -57,6 +57,7 @@ import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.CollapsibleView; +import org.schabi.newpipe.views.FixedGridLayoutManager; import java.io.File; import java.text.SimpleDateFormat; @@ -192,7 +193,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { 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 GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java b/app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java new file mode 100644 index 000000000..a3ea5929b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) Eltex ltd 2019 + * FixedGridLayoutManager.java is part of NewPipe. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.FocusFinder; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +// Version of GridLayoutManager that works around https://issuetracker.google.com/issues/37067220 +public class FixedGridLayoutManager extends GridLayoutManager { + public FixedGridLayoutManager(Context context, int spanCount) { + super(context, spanCount); + } + + public FixedGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public FixedGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { + super(context, spanCount, orientation, reverseLayout); + } + + @Override + public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) { + FocusFinder ff = FocusFinder.getInstance(); + + View result = ff.findNextFocus((ViewGroup) focused.getParent(), focused, focusDirection); + if (result != null) { + return super.onFocusSearchFailed(focused, focusDirection, recycler, state); + } + + if (focusDirection == View.FOCUS_DOWN) { + scrollVerticallyBy(10, recycler, state); + return null; + } + + return super.onFocusSearchFailed(focused, focusDirection, recycler, state); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java new file mode 100644 index 000000000..76dee200f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) Eltex ltd 2019 + * NewPipeRecyclerView.java is part of NewPipe. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +public class NewPipeRecyclerView extends RecyclerView { + private static final String TAG = "FixedRecyclerView"; + + public NewPipeRecyclerView(@NonNull Context context) { + super(context); + } + + public NewPipeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public NewPipeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public View focusSearch(int direction) { + return null; + } + + @Override + public View focusSearch(View focused, int direction) { + return null; + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + View found = super.focusSearch(focused, direction); + if (found != null) { + found.requestFocus(direction); + return true; + } + + if (direction == View.FOCUS_UP) { + if (canScrollVertically(-1)) { + scrollBy(0, -10); + return true; + } + + return false; + } + + return super.dispatchUnhandledMove(focused, direction); + } +} diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 26da47b1f..3792f030a 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -30,6 +30,7 @@ import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.views.FixedGridLayoutManager; import java.io.File; import java.io.IOException; @@ -108,7 +109,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, mList = v.findViewById(R.id.mission_recycler); // Init layouts managers - mGridManager = new GridLayoutManager(getActivity(), SPAN_SIZE); + mGridManager = new FixedGridLayoutManager(getActivity(), SPAN_SIZE); mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { diff --git a/app/src/main/res/layout/fragment_kiosk.xml b/app/src/main/res/layout/fragment_kiosk.xml index 01eeb0855..643d7d4f0 100644 --- a/app/src/main/res/layout/fragment_kiosk.xml +++ b/app/src/main/res/layout/fragment_kiosk.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - Date: Fri, 20 Sep 2019 16:13:13 +0700 Subject: [PATCH 005/663] MainPlayer: make title and subtitle non-focusable Focus isn't needed for marquee, only selection --- app/src/main/res/layout-large-land/activity_main_player.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index b535db2b8..902e81f1f 100644 --- a/app/src/main/res/layout-large-land/activity_main_player.xml +++ b/app/src/main/res/layout-large-land/activity_main_player.xml @@ -178,7 +178,6 @@ android:textSize="15sp" android:textStyle="bold" android:clickable="true" - android:focusable="true" tools:ignore="RtlHardcoded" tools:text="The Video Title LONG very LONG"/> @@ -194,7 +193,6 @@ android:textColor="@android:color/white" android:textSize="12sp" android:clickable="true" - android:focusable="true" tools:text="The Video Artist LONG very LONG very Long"/> From 1bb96ef4058994fe2eabaa93cc5fdcf819acfa44 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:16:21 +0700 Subject: [PATCH 006/663] When child of CoordinatorLayout wants focus, show it! The same logic is present in RecyclerView, ScrollView etc. Android really should default to this behavior for all Views with isScrollContainer = true --- .../newpipe/views/FocusAwareCoordinator.java | 63 +++++++++++++++++++ .../fragment_video_detail.xml | 4 +- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java new file mode 100644 index 000000000..778e50e52 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) Eltex ltd 2019 + * FocusAwareCoordinator.java is part of NewPipe. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; + +public final class FocusAwareCoordinator extends CoordinatorLayout { + private final Rect childFocus = new Rect(); + + public FocusAwareCoordinator(@NonNull Context context) { + super(context); + } + + public FocusAwareCoordinator(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public FocusAwareCoordinator(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + + if (!isInTouchMode()) { + if (focused.getHeight() >= getHeight()) { + focused.getFocusedRect(childFocus); + + ((ViewGroup) child).offsetDescendantRectToMyCoords(focused, childFocus); + } else { + focused.getHitRect(childFocus); + + ((ViewGroup) child).offsetDescendantRectToMyCoords((View) focused.getParent(), childFocus); + } + + requestChildRectangleOnScreen(child, childFocus, false); + } + } +} diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 02b0a7b86..186e184f3 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -10,7 +10,7 @@ android:orientation="horizontal" android:baselineAligned="false"> - - + Date: Fri, 20 Sep 2019 16:23:17 +0700 Subject: [PATCH 007/663] Do not discriminate against non-Amazon TV boxes --- .../main/java/org/schabi/newpipe/util/FireTvUtils.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java b/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java index 69666463e..879b54e1f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java @@ -1,10 +1,18 @@ package org.schabi.newpipe.util; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; + import org.schabi.newpipe.App; public class FireTvUtils { + @SuppressLint("InlinedApi") public static boolean isFireTv(){ final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; - return App.getApp().getPackageManager().hasSystemFeature(AMAZON_FEATURE_FIRE_TV); + + PackageManager pm = App.getApp().getPackageManager(); + + return pm.hasSystemFeature(AMAZON_FEATURE_FIRE_TV) + || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); } } From 644ad110c06638c50b4bd3800d3a334b4f976c7d Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:25:30 +0700 Subject: [PATCH 008/663] Make description focusable, so TV users can scroll it --- app/src/main/res/layout-large-land/fragment_video_detail.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 186e184f3..02d330ade 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -490,6 +490,7 @@ android:textAppearance="?android:attr/textAppearanceMedium" android:textIsSelectable="true" android:textSize="@dimen/video_item_detail_description_text_size" + android:focusable="true" tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." /> Date: Fri, 20 Sep 2019 16:36:57 +0700 Subject: [PATCH 009/663] Improve usability of MainVideoActivity with directional navigation * Hide player controls when back is pressed (only on TV devices) * Do not hide control after click unless in touch mode * Show player controls on dpad usage * Notably increase control hide timeout when not in touch mode --- .../newpipe/player/MainVideoPlayer.java | 53 ++++++++++++++++++- .../schabi/newpipe/player/VideoPlayer.java | 15 +++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 7a3e60c66..5663e1ea2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -1,5 +1,6 @@ /* * Copyright 2017 Mauricio Colli + * Copyright 2019 Eltex ltd * MainVideoPlayer.java is part of NewPipe * * License: GPL-3.0+ @@ -45,6 +46,7 @@ import android.util.Log; import android.util.TypedValue; import android.view.GestureDetector; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; @@ -75,6 +77,7 @@ import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.util.AnimationUtils; +import org.schabi.newpipe.util.FireTvUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; @@ -89,6 +92,7 @@ import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING; import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION; import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME; +import static org.schabi.newpipe.player.VideoPlayer.DPAD_CONTROLS_HIDE_TIME; import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA; import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA; import static org.schabi.newpipe.util.AnimationUtils.animateRotation; @@ -187,6 +191,40 @@ protected void onNewIntent(Intent intent) { } } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (event.getKeyCode()) { + default: + break; + case KeyEvent.KEYCODE_BACK: + if (FireTvUtils.isFireTv() && playerImpl.isControlsVisible()) { + playerImpl.hideControls(0, 0); + hideSystemUi(); + return true; + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) { + return true; + } + + if (!playerImpl.isControlsVisible()) { + playerImpl.showControlsThenHide(); + showSystemUi(); + return true; + } else { + playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); + } + break; + } + + return super.onKeyDown(keyCode, event); + } + @Override protected void onResume() { if (DEBUG) Log.d(TAG, "onResume() called"); @@ -692,7 +730,7 @@ public void onClick(View v) { getControlsVisibilityHandler().removeCallbacksAndMessages(null); animateView(getControlsRoot(), true, DEFAULT_CONTROLS_DURATION, 0, () -> { if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) { - hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); + safeHideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); } }); } @@ -898,6 +936,18 @@ public void showControls(long duration) { super.showControls(duration); } + @Override + public void safeHideControls(long duration, long delay) { + if (DEBUG) Log.d(TAG, "safeHideControls() called with: delay = [" + delay + "]"); + + View controlsRoot = getControlsRoot(); + if (controlsRoot.isInTouchMode()) { + getControlsVisibilityHandler().removeCallbacksAndMessages(null); + getControlsVisibilityHandler().postDelayed( + () -> animateView(controlsRoot, false, duration, 0, MainVideoPlayer.this::hideSystemUi), delay); + } + } + @Override public void hideControls(final long duration, long delay) { if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); @@ -1058,6 +1108,7 @@ public boolean onSingleTapConfirmed(MotionEvent e) { playerImpl.showControlsThenHide(); showSystemUi(); } + return true; } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 360475ba2..0d9c14058 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -97,6 +97,7 @@ public abstract class VideoPlayer extends BasePlayer protected static final int RENDERER_UNAVAILABLE = -1; public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds + public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds private List availableStreams; private int selectedStreamIndex; @@ -825,8 +826,11 @@ public boolean isSomePopupMenuVisible() { public void showControlsThenHide() { if (DEBUG) Log.d(TAG, "showControlsThenHide() called"); + + final int hideTime = controlsRoot.isInTouchMode() ? DEFAULT_CONTROLS_HIDE_TIME : DPAD_CONTROLS_HIDE_TIME; + animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0, - () -> hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME)); + () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); } public void showControls(long duration) { @@ -835,6 +839,15 @@ public void showControls(long duration) { animateView(controlsRoot, true, duration); } + public void safeHideControls(final long duration, long delay) { + if (DEBUG) Log.d(TAG, "safeHideControls() called with: delay = [" + delay + "]"); + if (rootView.isInTouchMode()) { + controlsVisibilityHandler.removeCallbacksAndMessages(null); + controlsVisibilityHandler.postDelayed( + () -> animateView(controlsRoot, false, duration), delay); + } + } + public void hideControls(final long duration, long delay) { if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); controlsVisibilityHandler.removeCallbacksAndMessages(null); From d8bd8d87ec6eb6ca7379740be27d7d7545ae31d3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:42:32 +0700 Subject: [PATCH 010/663] Make player screen controls into buttons Buttons are more likely to have "correct" styling and are focusable/clickable out of box --- .../activity_main_player.xml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index 902e81f1f..2859b6c5d 100644 --- a/app/src/main/res/layout-large-land/activity_main_player.xml +++ b/app/src/main/res/layout-large-land/activity_main_player.xml @@ -196,8 +196,9 @@ tools:text="The Video Artist LONG very LONG very Long"/> - - @@ -268,8 +272,9 @@ tools:ignore="RtlHardcoded" tools:visibility="visible"> - - From 7db1ba40ebf530181984c99804b1ab6d392a1742 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:48:34 +0700 Subject: [PATCH 011/663] Do not allow focus to escape from open DrawerLayout Upstream DrawerLayout does override addFocusables, but incorrectly checks for isDrawerOpen instread of isDrawerVisible --- .../newpipe/views/FocusAwareDrawerLayout.java | 69 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 4 +- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java new file mode 100644 index 000000000..0e8097dbe --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) Eltex ltd 2019 + * FocusAwareDrawerLayout.java is part of NewPipe. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.GravityCompat; +import androidx.core.view.ViewCompat; +import androidx.drawerlayout.widget.DrawerLayout; + +import java.util.ArrayList; + +public final class FocusAwareDrawerLayout extends DrawerLayout { + public FocusAwareDrawerLayout(@NonNull Context context) { + super(context); + } + + public FocusAwareDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public FocusAwareDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + boolean hasOpenPanels = false; + View content = null; + + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + + DrawerLayout.LayoutParams lp = (DrawerLayout.LayoutParams) child.getLayoutParams(); + + if (lp.gravity == 0) { + content = child; + } else { + if (isDrawerVisible(child)) { + hasOpenPanels = true; + child.addFocusables(views, direction, focusableMode); + } + } + } + + if (content != null && !hasOpenPanels) { + content.addFocusables(views, direction, focusableMode); + } + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 92e73234f..a965f5f65 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - + From a8a28294d3d43f9720141c561dc1e9fa3b10d421 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 17:42:56 +0700 Subject: [PATCH 012/663] Support for seeking videos in directional navigation mode --- .../newpipe/views/FocusAwareSeekBar.java | 138 ++++++++++++++++++ .../activity_main_player.xml | 2 +- 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java new file mode 100644 index 000000000..3789ea344 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) Eltex ltd 2019 + * FocusAwareDrawerLayout.java is part of NewPipe. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.ViewTreeObserver; +import android.widget.SeekBar; + +import androidx.appcompat.widget.AppCompatSeekBar; + +/** + * SeekBar, adapted for directional navigation. It emulates touch-related callbacks + * (onStartTrackingTouch/onStopTrackingTouch), so existing code does not need to be changed to + * work with it. + */ +public final class FocusAwareSeekBar extends AppCompatSeekBar { + private NestedListener listener; + + private ViewTreeObserver treeObserver; + + public FocusAwareSeekBar(Context context) { + super(context); + } + + public FocusAwareSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FocusAwareSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { + this.listener = l == null ? null : new NestedListener(l); + + super.setOnSeekBarChangeListener(listener); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (!isInTouchMode() && keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + releaseTrack(); + } + + return super.onKeyDown(keyCode, event); + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + + if (!isInTouchMode() && !gainFocus) { + releaseTrack(); + } + } + + private final ViewTreeObserver.OnTouchModeChangeListener touchModeListener = isInTouchMode -> { if (isInTouchMode) releaseTrack(); }; + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + treeObserver = getViewTreeObserver(); + treeObserver.addOnTouchModeChangeListener(touchModeListener); + } + + @Override + protected void onDetachedFromWindow() { + if (treeObserver == null || !treeObserver.isAlive()) { + treeObserver = getViewTreeObserver(); + } + + treeObserver.removeOnTouchModeChangeListener(touchModeListener); + treeObserver = null; + + super.onDetachedFromWindow(); + } + + private void releaseTrack() { + if (listener != null && listener.isSeeking) { + listener.onStopTrackingTouch(this); + } + } + + private final class NestedListener implements OnSeekBarChangeListener { + private final OnSeekBarChangeListener delegate; + + boolean isSeeking; + + private NestedListener(OnSeekBarChangeListener delegate) { + this.delegate = delegate; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!seekBar.isInTouchMode() && !isSeeking && fromUser) { + isSeeking = true; + + onStartTrackingTouch(seekBar); + } + + delegate.onProgressChanged(seekBar, progress, fromUser); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + isSeeking = true; + + delegate.onStartTrackingTouch(seekBar); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + isSeeking = false; + + delegate.onStopTrackingTouch(seekBar); + } + } +} diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index 2859b6c5d..c40931a1a 100644 --- a/app/src/main/res/layout-large-land/activity_main_player.xml +++ b/app/src/main/res/layout-large-land/activity_main_player.xml @@ -401,7 +401,7 @@ tools:text="1:06:29"/> - Date: Mon, 23 Sep 2019 13:50:51 +0700 Subject: [PATCH 013/663] Focus drawer when it opens It is still buggy because of NavigationView (why the hell is NavigationMenuView marked as focusable?) but at least initial opening works as intended --- .../newpipe/views/FocusAwareDrawerLayout.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java index 0e8097dbe..2354427a3 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareDrawerLayout.java @@ -17,8 +17,10 @@ */ package org.schabi.newpipe.views; +import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; import androidx.annotation.NonNull; @@ -66,4 +68,35 @@ public void addFocusables(ArrayList views, int direction, int focusableMod content.addFocusables(views, direction, focusableMode); } } + + // this override isn't strictly necessary, but it is helpful when DrawerLayout isn't the topmost + // view in hierarchy (such as when system or builtin appcompat ActionBar is used) + @Override + @SuppressLint("RtlHardcoded") + public void openDrawer(@NonNull View drawerView, boolean animate) { + super.openDrawer(drawerView, animate); + + LayoutParams params = (LayoutParams) drawerView.getLayoutParams(); + + int gravity = GravityCompat.getAbsoluteGravity(params.gravity, ViewCompat.getLayoutDirection(this)); + + int direction = 0; + + switch (gravity) { + case Gravity.LEFT: + direction = FOCUS_LEFT; + break; + case Gravity.RIGHT: + direction = FOCUS_RIGHT; + break; + case Gravity.TOP: + direction = FOCUS_UP; + break; + case Gravity.BOTTOM: + direction = FOCUS_DOWN; + break; + } + + drawerView.requestFocus(direction); + } } From d23227d427831184f6ae12162593c6112bcd4b11 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 23 Sep 2019 14:17:03 +0700 Subject: [PATCH 014/663] Implement global focus highlight --- .../java/org/schabi/newpipe/MainActivity.java | 5 + .../org/schabi/newpipe/RouterActivity.java | 6 + .../newpipe/download/DownloadActivity.java | 6 + .../newpipe/player/MainVideoPlayer.java | 6 + .../newpipe/views/FocusOverlayView.java | 248 ++++++++++++++++++ 5 files changed, 271 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 3c18c25f6..8d2702d0b 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -69,6 +69,7 @@ import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.views.FocusOverlayView; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @@ -121,6 +122,10 @@ protected void onCreate(Bundle savedInstanceState) { } catch (Exception e) { ErrorActivity.reportUiError(this, e); } + + if (FireTvUtils.isFireTv()) { + FocusOverlayView.setupFocusObserver(this); + } } private void setupDrawer() throws Exception { diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1be6e096a..c5b97f86f 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -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.FireTvUtils; 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 java.io.Serializable; import java.util.ArrayList; @@ -316,6 +318,10 @@ private void showDialog(final List choices) { selectedPreviously = selectedRadioPosition; alertDialog.show(); + + if (FireTvUtils.isFireTv()) { + FocusOverlayView.setupFocusObserver(alertDialog); + } } private List getChoicesForService(StreamingService service, LinkType linkType) { diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java index 449a790e8..56265d321 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java @@ -13,7 +13,9 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.settings.SettingsActivity; +import org.schabi.newpipe.util.FireTvUtils; 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; @@ -50,6 +52,10 @@ public void onGlobalLayout() { getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); + + if (FireTvUtils.isFireTv()) { + FocusOverlayView.setupFocusObserver(this); + } } private void updateFragments() { diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 5663e1ea2..38da4d8b2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -84,6 +84,7 @@ import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.views.FocusOverlayView; import java.util.List; import java.util.Queue; @@ -141,6 +142,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { hideSystemUi(); setContentView(R.layout.activity_main_player); + playerImpl = new VideoPlayerImpl(this); playerImpl.setup(findViewById(android.R.id.content)); @@ -172,6 +174,10 @@ public void onChange(boolean selfChange) { getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, rotationObserver); + + if (FireTvUtils.isFireTv()) { + FocusOverlayView.setupFocusObserver(this); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java b/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java new file mode 100644 index 000000000..b0b9cc421 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java @@ -0,0 +1,248 @@ +/* + * Copyright 2019 Alexander Rvachev + * FocusOverlayView.java is part of NewPipe + * + * License: GPL-3.0+ + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.schabi.newpipe.views; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; + +import androidx.annotation.NonNull; +import androidx.appcompat.view.WindowCallbackWrapper; + +import org.schabi.newpipe.R; + +import java.lang.ref.WeakReference; + +public final class FocusOverlayView extends Drawable implements + ViewTreeObserver.OnGlobalFocusChangeListener, + ViewTreeObserver.OnDrawListener, + ViewTreeObserver.OnGlobalLayoutListener, + ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnTouchModeChangeListener { + + private boolean isInTouchMode; + + private final Rect focusRect = new Rect(); + + private final Paint rectPaint = new Paint(); + + private final Handler animator = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + updateRect(); + } + }; + + private WeakReference focused; + + public FocusOverlayView(Context context) { + rectPaint.setStyle(Paint.Style.STROKE); + rectPaint.setStrokeWidth(2); + rectPaint.setColor(context.getResources().getColor(R.color.white)); + } + + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + int l = focusRect.left; + int r = focusRect.right; + int t = focusRect.top; + int b = focusRect.bottom; + + if (newFocus != null && newFocus.getWidth() > 0 && newFocus.getHeight() > 0) { + newFocus.getGlobalVisibleRect(focusRect); + + focused = new WeakReference<>(newFocus); + } else { + focusRect.setEmpty(); + + focused = null; + } + + if (l != focusRect.left || r != focusRect.right || t != focusRect.top || b != focusRect.bottom) { + invalidateSelf(); + } + + focused = new WeakReference<>(newFocus); + + animator.sendEmptyMessageDelayed(0, 1000); + } + + private void updateRect() { + if (focused == null) { + return; + } + + View focused = this.focused.get(); + + int l = focusRect.left; + int r = focusRect.right; + int t = focusRect.top; + int b = focusRect.bottom; + + if (focused != null) { + focused.getGlobalVisibleRect(focusRect); + } else { + focusRect.setEmpty(); + } + + if (l != focusRect.left || r != focusRect.right || t != focusRect.top || b != focusRect.bottom) { + invalidateSelf(); + } + } + + @Override + public void onDraw() { + updateRect(); + } + + @Override + public void onScrollChanged() { + updateRect(); + + animator.removeMessages(0); + animator.sendEmptyMessageDelayed(0, 1000); + } + + @Override + public void onGlobalLayout() { + updateRect(); + + animator.sendEmptyMessageDelayed(0, 1000); + } + + @Override + public void onTouchModeChanged(boolean isInTouchMode) { + this.isInTouchMode = isInTouchMode; + + if (isInTouchMode) { + updateRect(); + } else { + invalidateSelf(); + } + } + + public void setCurrentFocus(View focused) { + if (focused == null) { + return; + } + + this.isInTouchMode = focused.isInTouchMode(); + + onGlobalFocusChanged(null, focused); + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (!isInTouchMode && focusRect.width() != 0) { + canvas.drawRect(focusRect, rectPaint); + } + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSPARENT; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + } + + public static void setupFocusObserver(Dialog dialog) { + Rect displayRect = new Rect(); + + Window window = dialog.getWindow(); + assert window != null; + + View decor = window.getDecorView(); + decor.getWindowVisibleDisplayFrame(displayRect); + + FocusOverlayView overlay = new FocusOverlayView(dialog.getContext()); + overlay.setBounds(0, 0, displayRect.width(), displayRect.height()); + + setupOverlay(window, overlay); + } + + public static void setupFocusObserver(Activity activity) { + Rect displayRect = new Rect(); + + Window window = activity.getWindow(); + View decor = window.getDecorView(); + decor.getWindowVisibleDisplayFrame(displayRect); + + FocusOverlayView overlay = new FocusOverlayView(activity); + overlay.setBounds(0, 0, displayRect.width(), displayRect.height()); + + setupOverlay(window, overlay); + } + + private static void setupOverlay(Window window, FocusOverlayView overlay) { + ViewGroup decor = (ViewGroup) window.getDecorView(); + decor.getOverlay().add(overlay); + + ViewTreeObserver observer = decor.getViewTreeObserver(); + observer.addOnScrollChangedListener(overlay); + observer.addOnGlobalFocusChangeListener(overlay); + observer.addOnGlobalLayoutListener(overlay); + observer.addOnTouchModeChangeListener(overlay); + + overlay.setCurrentFocus(decor.getFocusedChild()); + + // Some key presses don't actually move focus, but still result in movement on screen. + // For example, MovementMethod of TextView may cause requestRectangleOnScreen() due to + // some "focusable" spans, which in turn causes CoordinatorLayout to "scroll" it's children. + // Unfortunately many such forms of "scrolling" do not count as scrolling for purpose + // of dispatching ViewTreeObserver callbacks, so we have to intercept them by directly + // receiving keys from Window. + window.setCallback(new WindowCallbackWrapper(window.getCallback()) { + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + boolean res = super.dispatchKeyEvent(event); + overlay.onKey(event); + return res; + } + }); + } + + private void onKey(KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return; + } + + updateRect(); + + animator.sendEmptyMessageDelayed(0, 100); + } +} From 28fb864ed01bf0af2989527d984e500f91717fe1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 23 Sep 2019 17:20:15 +0700 Subject: [PATCH 015/663] Focus video view thumbnail after it is loaded --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 14e989625..fd2a3285d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -505,7 +505,7 @@ protected void initViews(View rootView, Bundle savedInstanceState) { setHeightThumbnail(); - + thumbnailBackgroundButton.requestFocus(); } @Override From 79c962fc8805183fdbe44b5dfe0ab7b2e32cd141 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 30 Sep 2019 12:02:07 +0700 Subject: [PATCH 016/663] More robust focus search in SuperScrollLayoutManager FocusFinder has glitches when some of target Views have different size. Fortunately LayoutManager can redefine focus search strategy to override the default behavior. --- .../views/SuperScrollLayoutManager.java | 110 +++++++++++++++++- app/src/main/res/layout/fragment_comments.xml | 2 +- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java b/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java index 33fe7b9cc..3946b8435 100644 --- a/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java +++ b/app/src/main/java/org/schabi/newpipe/views/SuperScrollLayoutManager.java @@ -19,16 +19,21 @@ import android.content.Context; import android.graphics.Rect; -import android.view.FocusFinder; import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; + public final class SuperScrollLayoutManager extends LinearLayoutManager { private final Rect handy = new Rect(); + private final ArrayList focusables = new ArrayList<>(); + public SuperScrollLayoutManager(Context context) { super(context); } @@ -50,4 +55,107 @@ public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, @NonN return super.requestChildRectangleOnScreen(parent, child, rect, immediate, focusedChildVisible); } + + @Nullable + @Override + public View onInterceptFocusSearch(@NonNull View focused, int direction) { + View focusedItem = findContainingItemView(focused); + if (focusedItem == null) { + return super.onInterceptFocusSearch(focused, direction); + } + + int listDirection = getAbsoluteDirection(direction); + if (listDirection == 0) { + return super.onInterceptFocusSearch(focused, direction); + } + + // FocusFinder has an oddity: it considers size of Views more important + // than closeness to source View. This means, that big Views far away from current item + // are preferred to smaller sub-View of closer item. Setting focusability of closer item + // to FOCUS_AFTER_DESCENDANTS does not solve this, because ViewGroup#addFocusables omits + // such parent itself from list, if any of children are focusable. + // Fortunately we can intercept focus search and implement our own logic, based purely + // on position along the LinearLayoutManager axis + + ViewGroup recycler = (ViewGroup) focusedItem.getParent(); + + int sourcePosition = getPosition(focusedItem); + if (sourcePosition == 0 && listDirection < 0) { + return super.onInterceptFocusSearch(focused, direction); + } + + View preferred = null; + + int distance = Integer.MAX_VALUE; + + focusables.clear(); + + recycler.addFocusables(focusables, direction, recycler.isInTouchMode() ? View.FOCUSABLES_TOUCH_MODE : View.FOCUSABLES_ALL); + + try { + for (View view : focusables) { + if (view == focused || view == recycler) { + continue; + } + + int candidate = getDistance(sourcePosition, view, listDirection); + if (candidate < 0) { + continue; + } + + if (candidate < distance) { + distance = candidate; + preferred = view; + } + } + } finally { + focusables.clear(); + } + + return preferred; + } + + private int getAbsoluteDirection(int direction) { + switch (direction) { + default: + break; + case View.FOCUS_FORWARD: + return 1; + case View.FOCUS_BACKWARD: + return -1; + } + + if (getOrientation() == RecyclerView.HORIZONTAL) { + switch (direction) { + default: + break; + case View.FOCUS_LEFT: + return getReverseLayout() ? 1 : -1; + case View.FOCUS_RIGHT: + return getReverseLayout() ? -1 : 1; + } + } else { + switch (direction) { + default: + break; + case View.FOCUS_UP: + return getReverseLayout() ? 1 : -1; + case View.FOCUS_DOWN: + return getReverseLayout() ? -1 : 1; + } + } + + return 0; + } + + private int getDistance(int sourcePosition, View candidate, int direction) { + View itemView = findContainingItemView(candidate); + if (itemView == null) { + return -1; + } + + int position = getPosition(itemView); + + return direction * (position - sourcePosition); + } } diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml index 0ee62c05d..4ced11d35 100644 --- a/app/src/main/res/layout/fragment_comments.xml +++ b/app/src/main/res/layout/fragment_comments.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - Date: Wed, 9 Oct 2019 17:09:07 +0700 Subject: [PATCH 017/663] Allow comment links (if any) to gain focus --- .../holder/CommentsMiniInfoItemHolder.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 4d94ec392..e7b09f3e2 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -1,6 +1,9 @@ package org.schabi.newpipe.info_list.holder; import androidx.appcompat.app.AppCompatActivity; + +import android.text.method.LinkMovementMethod; +import android.text.style.URLSpan; import android.text.util.Linkify; import android.view.ViewGroup; import android.widget.TextView; @@ -122,15 +125,35 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h }); } + private void allowLinkFocus() { + if (itemView.isInTouchMode()) { + return; + } + + URLSpan[] urls = itemContentView.getUrls(); + + if (urls != null && urls.length != 0) { + itemContentView.setMovementMethod(LinkMovementMethod.getInstance()); + } + } + private void ellipsize() { + boolean hasEllipsis = false; + if (itemContentView.getLineCount() > commentDefaultLines){ int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2); if(end == -1) end = Math.max(endOfLastLine -2, 0); String newVal = itemContentView.getText().subSequence(0, end) + " …"; itemContentView.setText(newVal); + hasEllipsis = true; } + linkify(); + + if (!hasEllipsis) { + allowLinkFocus(); + } } private void toggleEllipsize() { @@ -145,11 +168,13 @@ private void expand() { itemContentView.setMaxLines(commentExpandedLines); itemContentView.setText(commentText); linkify(); + allowLinkFocus(); } private void linkify(){ Linkify.addLinks(itemContentView, Linkify.WEB_URLS); Linkify.addLinks(itemContentView, pattern, null, null, timestampLink); + itemContentView.setMovementMethod(null); } } From 6e76610f303af3428f5ec0afc565ce98cc4bb7ed Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 23 Sep 2019 17:57:14 +0700 Subject: [PATCH 018/663] Eliminate bunch of ExoPlayer warnings --- app/src/main/java/org/schabi/newpipe/player/BasePlayer.java | 2 +- .../org/schabi/newpipe/player/playback/MediaSourceManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index a07afcea9..50a60ecb1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -556,7 +556,7 @@ public void triggerProgressUpdate() { } private Disposable getProgressReactor() { - return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS) + return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(ignored -> triggerProgressUpdate(), error -> Log.e(TAG, "Progress update failure: ", error)); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 85c852f57..bbe391807 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -318,7 +318,7 @@ private synchronized void maybeSynchronizePlayer() { //////////////////////////////////////////////////////////////////////////*/ private Observable getEdgeIntervalSignal() { - return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS) + return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) .filter(ignored -> playbackListener.isApproachingPlaybackEdge(playbackNearEndGapMillis)); } From a7c31e6bcc28067573aebd8a198091cdc053a7f4 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Fri, 8 Nov 2019 14:26:12 +0700 Subject: [PATCH 019/663] RecyclerView scroll fixes * Move all focus-related work arouns to NewPipeRecyclerView * Try to pass focus within closer parents first * Do small arrow scroll if there are not more focusables in move direction --- .../newpipe/views/NewPipeRecyclerView.java | 171 ++++++++++++++++-- 1 file changed, 159 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java index 76dee200f..435281d14 100644 --- a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java +++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java @@ -18,55 +18,202 @@ package org.schabi.newpipe.views; import android.content.Context; +import android.graphics.Rect; +import android.os.Build; import android.util.AttributeSet; +import android.util.Log; +import android.view.FocusFinder; import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; public class NewPipeRecyclerView extends RecyclerView { - private static final String TAG = "FixedRecyclerView"; + private static final String TAG = "NewPipeRecyclerView"; + + private Rect focusRect = new Rect(); + private Rect tempFocus = new Rect(); + + private boolean allowDpadScroll; public NewPipeRecyclerView(@NonNull Context context) { super(context); + + init(); } public NewPipeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); + + init(); } public NewPipeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + + init(); } - @Override - public View focusSearch(int direction) { - return null; + private void init() { + setFocusable(true); + + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + } + + public void setFocusScrollAllowed(boolean allowDpadScroll) { + this.allowDpadScroll = allowDpadScroll; } @Override public View focusSearch(View focused, int direction) { + // RecyclerView has buggy focusSearch(), that calls into Adapter several times, + // but ultimately fails to produce correct results in many cases. To add insult to injury, + // it's focusSearch() hard-codes several behaviors, incompatible with widely accepted focus + // handling practices: RecyclerView does not allow Adapter to give focus to itself (!!) and + // always checks, that returned View is located in "correct" direction (which prevents us + // from temporarily giving focus to special hidden View). return null; } + @Override + protected void removeDetachedView(View child, boolean animate) { + if (child.hasFocus()) { + // If the focused child is being removed (can happen during very fast scrolling), + // temporarily give focus to ourselves. This will usually result in another child + // gaining focus (which one does not really matter, because at that point scrolling + // is FAST, and that child will soon be off-screen too) + requestFocus(); + } + + super.removeDetachedView(child, animate); + } + + // we override focusSearch to always return null, so all moves moves lead to dispatchUnhandledMove() + // as added advantage, we can fully swallow some kinds of moves (such as downward movement, that + // happens when loading additional contents is in progress + @Override public boolean dispatchUnhandledMove(View focused, int direction) { - View found = super.focusSearch(focused, direction); - if (found != null) { - found.requestFocus(direction); + tempFocus.setEmpty(); + + // save focus rect before further manipulation (both focusSearch() and scrollBy() + // can mess with focused View by moving it off-screen and detaching) + + if (focused != null) { + View focusedItem = findContainingItemView(focused); + if (focusedItem != null) { + focusedItem.getHitRect(focusRect); + } + } + + // call focusSearch() to initiate layout, but disregard returned View for now + View adapterResult = super.focusSearch(focused, direction); + if (adapterResult != null && !isOutside(adapterResult)) { + adapterResult.requestFocus(direction); + return true; + } + + if (arrowScroll(direction)) { + // if RecyclerView can not yield focus, but there is still some scrolling space in indicated, + // direction, scroll some fixed amount in that direction (the same logic in ScrollView) return true; } - if (direction == View.FOCUS_UP) { - if (canScrollVertically(-1)) { - scrollBy(0, -10); + if (focused != this && direction == FOCUS_DOWN && !allowDpadScroll) { + Log.i(TAG, "Consuming downward scroll: content load in progress"); + return true; + } + + if (tryFocusFinder(direction)) { + return true; + } + + if (adapterResult != null) { + adapterResult.requestFocus(direction); + return true; + } + + return super.dispatchUnhandledMove(focused, direction); + } + + private boolean tryFocusFinder(int direction) { + if (Build.VERSION.SDK_INT >= 28) { + // Android 9 implemented bunch of handy changes to focus, that render code below less useful, and + // also broke findNextFocusFromRect in way, that render this hack useless + return false; + } + + FocusFinder finder = FocusFinder.getInstance(); + + // try to use FocusFinder instead of adapter + ViewGroup root = (ViewGroup) getRootView(); + + tempFocus.set(focusRect); + + root.offsetDescendantRectToMyCoords(this, tempFocus); + + View focusFinderResult = finder.findNextFocusFromRect(root, tempFocus, direction); + if (focusFinderResult != null && !isOutside(focusFinderResult)) { + focusFinderResult.requestFocus(direction); + return true; + } + + // look for focus in our ancestors, increasing search scope with each failure + // this provides much better locality than using FocusFinder with root + ViewGroup parent = (ViewGroup) getParent(); + + while (parent != root) { + tempFocus.set(focusRect); + + parent.offsetDescendantRectToMyCoords(this, tempFocus); + + View candidate = finder.findNextFocusFromRect(parent, tempFocus, direction); + if (candidate != null && candidate.requestFocus(direction)) { return true; } - return false; + parent = (ViewGroup) parent.getParent(); } - return super.dispatchUnhandledMove(focused, direction); + return false; + } + + private boolean arrowScroll(int direction) { + switch (direction) { + case FOCUS_DOWN: + if (!canScrollVertically(1)) { + return false; + } + scrollBy(0, 100); + break; + case FOCUS_UP: + if (!canScrollVertically(-1)) { + return false; + } + scrollBy(0, -100); + break; + case FOCUS_LEFT: + if (!canScrollHorizontally(-1)) { + return false; + } + scrollBy(-100, 0); + break; + case FOCUS_RIGHT: + if (!canScrollHorizontally(-1)) { + return false; + } + scrollBy(100, 0); + break; + default: + return false; + } + + return true; + } + + private boolean isOutside(View view) { + return findContainingItemView(view) == null; } } From b5558a8b78962234d855f318e1b059bd88892242 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Fri, 8 Nov 2019 14:41:16 +0700 Subject: [PATCH 020/663] Remove FixedGridLayoutManager --- .../fragments/list/BaseListFragment.java | 3 +- .../newpipe/local/BaseLocalListFragment.java | 3 +- .../subscription/SubscriptionFragment.java | 3 +- .../newpipe/views/FixedGridLayoutManager.java | 59 ------------------- .../giga/ui/fragment/MissionsFragment.java | 3 +- 5 files changed, 4 insertions(+), 67 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 88684f2e7..a3844a92f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -35,7 +35,6 @@ import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.views.SuperScrollLayoutManager; -import org.schabi.newpipe.views.FixedGridLayoutManager; import java.util.List; import java.util.Queue; @@ -157,7 +156,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { 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 GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index c1293e240..414a9b6b5 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -18,7 +18,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.list.ListViewContract; -import org.schabi.newpipe.views.FixedGridLayoutManager; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -96,7 +95,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { 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 GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java index ea820b71e..bff6c1b3a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java @@ -57,7 +57,6 @@ import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.CollapsibleView; -import org.schabi.newpipe.views.FixedGridLayoutManager; import java.io.File; import java.text.SimpleDateFormat; @@ -193,7 +192,7 @@ protected RecyclerView.LayoutManager getGridLayoutManager() { 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 GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java b/app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java deleted file mode 100644 index a3ea5929b..000000000 --- a/app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) Eltex ltd 2019 - * FixedGridLayoutManager.java is part of NewPipe. - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ -package org.schabi.newpipe.views; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.FocusFinder; -import android.view.View; -import android.view.ViewGroup; - -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -// Version of GridLayoutManager that works around https://issuetracker.google.com/issues/37067220 -public class FixedGridLayoutManager extends GridLayoutManager { - public FixedGridLayoutManager(Context context, int spanCount) { - super(context, spanCount); - } - - public FixedGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public FixedGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { - super(context, spanCount, orientation, reverseLayout); - } - - @Override - public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) { - FocusFinder ff = FocusFinder.getInstance(); - - View result = ff.findNextFocus((ViewGroup) focused.getParent(), focused, focusDirection); - if (result != null) { - return super.onFocusSearchFailed(focused, focusDirection, recycler, state); - } - - if (focusDirection == View.FOCUS_DOWN) { - scrollVerticallyBy(10, recycler, state); - return null; - } - - return super.onFocusSearchFailed(focused, focusDirection, recycler, state); - } -} diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 3792f030a..26da47b1f 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -30,7 +30,6 @@ import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ThemeHelper; -import org.schabi.newpipe.views.FixedGridLayoutManager; import java.io.File; import java.io.IOException; @@ -109,7 +108,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, mList = v.findViewById(R.id.mission_recycler); // Init layouts managers - mGridManager = new FixedGridLayoutManager(getActivity(), SPAN_SIZE); + mGridManager = new GridLayoutManager(getActivity(), SPAN_SIZE); mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { From 9801cf50e38abd0214340d48dba1e5b8e1572cef Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Thu, 14 Nov 2019 20:34:31 +0659 Subject: [PATCH 021/663] Save/restore focused item --- .../fragments/list/BaseListFragment.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index a3844a92f..a2821a65e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -34,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.NewPipeRecyclerView; import org.schabi.newpipe.views.SuperScrollLayoutManager; import java.util.List; @@ -50,6 +51,7 @@ public abstract class BaseListFragment extends BaseStateFragment implem protected InfoListAdapter infoListAdapter; protected RecyclerView itemsList; private int updateFlags = 0; + private int focusedPosition = -1; private static final int LIST_MODE_UPDATE_FLAG = 0x32; @@ -111,9 +113,22 @@ 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(Queue objectsToSave) { objectsToSave.add(infoListAdapter.getItemsList()); + objectsToSave.add(getFocusedPosition()); } @Override @@ -121,6 +136,20 @@ public void writeTo(Queue objectsToSave) { public void readFrom(@NonNull Queue savedObjects) throws Exception { infoListAdapter.getItemsList().clear(); infoListAdapter.getItemsList().addAll((List) savedObjects.poll()); + restoreFocus((Integer) savedObjects.poll()); + } + + private void restoreFocus(Integer position) { + if (position == null || position < 0) { + return; + } + + itemsList.post(() -> { + RecyclerView.ViewHolder focusedHolder = itemsList.findViewHolderForAdapterPosition(position); + if (focusedHolder != null) { + focusedHolder.itemView.requestFocus(); + } + }); } @Override @@ -135,6 +164,18 @@ protected void onRestoreInstanceState(@NonNull Bundle bundle) { savedState = StateSaver.tryToRestore(bundle, this); } + @Override + public void onStop() { + focusedPosition = getFocusedPosition(); + super.onStop(); + } + + @Override + public void onStart() { + super.onStart(); + restoreFocus(focusedPosition); + } + /*////////////////////////////////////////////////////////////////////////// // Init //////////////////////////////////////////////////////////////////////////*/ From 7bb5cacb0dff6f167046f612af5caf3f603f69da Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Thu, 14 Nov 2019 20:37:16 +0659 Subject: [PATCH 022/663] Special MovementMethod for video description Video descriptions can be very long. Some of them are basically walls of text with couple of lines at top or bottom. They are also not scrolled within TextView itself, - instead NewPipe expects user to scroll their containing ViewGroup. This renders all builtin MovementMethod implementations useless. This commit adds a new MovementMethod, that uses requestRectangleOnScreen to intelligently re-position the TextView within it's scrollable container. --- .../fragments/detail/VideoDetailFragment.java | 5 +- .../views/LargeTextMovementMethod.java | 290 ++++++++++++++++++ .../fragment_video_detail.xml | 1 + 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/LargeTextMovementMethod.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index fd2a3285d..c698d4ad4 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -87,6 +87,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; @@ -441,10 +442,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); } } @@ -481,7 +485,6 @@ protected void initViews(View rootView, 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()); videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS); thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view); diff --git a/app/src/main/java/org/schabi/newpipe/views/LargeTextMovementMethod.java b/app/src/main/java/org/schabi/newpipe/views/LargeTextMovementMethod.java new file mode 100644 index 000000000..1f9ab5e2d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/LargeTextMovementMethod.java @@ -0,0 +1,290 @@ +/* + * Copyright 2019 Alexander Rvachev + * FocusOverlayView.java is part of NewPipe + * + * License: GPL-3.0+ + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.schabi.newpipe.views; + +import android.graphics.Rect; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.TextView; + +public class LargeTextMovementMethod extends LinkMovementMethod { + private final Rect visibleRect = new Rect(); + + private int dir; + + @Override + public void onTakeFocus(TextView view, Spannable text, int dir) { + Selection.removeSelection(text); + + super.onTakeFocus(view, text, dir); + + this.dir = dirToRelative(dir); + } + + @Override + protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event) { + if (!doHandleMovement(widget, buffer, keyCode, movementMetaState, event)) { + // clear selection to make sure, that it does not confuse focus handling code + Selection.removeSelection(buffer); + return false; + } + + return true; + } + + private boolean doHandleMovement(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event) { + int newDir = keyToDir(keyCode); + + if (dir != 0 && newDir != dir) { + return false; + } + + this.dir = 0; + + ViewGroup root = findScrollableParent(widget); + + widget.getHitRect(visibleRect); + + root.offsetDescendantRectToMyCoords((View) widget.getParent(), visibleRect); + + return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event); + } + + @Override + protected boolean up(TextView widget, Spannable buffer) { + if (gotoPrev(widget, buffer)) { + return true; + } + + return super.up(widget, buffer); + } + + @Override + protected boolean left(TextView widget, Spannable buffer) { + if (gotoPrev(widget, buffer)) { + return true; + } + + return super.left(widget, buffer); + } + + @Override + protected boolean right(TextView widget, Spannable buffer) { + if (gotoNext(widget, buffer)) { + return true; + } + + return super.right(widget, buffer); + } + + @Override + protected boolean down(TextView widget, Spannable buffer) { + if (gotoNext(widget, buffer)) { + return true; + } + + return super.down(widget, buffer); + } + + private boolean gotoPrev(TextView view, Spannable buffer) { + Layout layout = view.getLayout(); + if (layout == null) { + return false; + } + + View root = findScrollableParent(view); + + int rootHeight = root.getHeight(); + + if (visibleRect.top >= 0) { + // we fit entirely into the viewport, no need for fancy footwork + return false; + } + + int topExtra = -visibleRect.top; + + int firstVisibleLineNumber = layout.getLineForVertical(topExtra); + + // when deciding whether to pass "focus" to span, account for one more line + // this ensures, that focus is never passed to spans partially outside scroll window + int visibleStart = firstVisibleLineNumber == 0 ? 0 : layout.getLineStart(firstVisibleLineNumber - 1); + + ClickableSpan[] candidates = buffer.getSpans(visibleStart, buffer.length(), ClickableSpan.class); + + if (candidates.length != 0) { + int a = Selection.getSelectionStart(buffer); + int b = Selection.getSelectionEnd(buffer); + + int selStart = Math.min(a, b); + int selEnd = Math.max(a, b); + + int bestStart = -1; + int bestEnd = -1; + + for (int i = 0; i < candidates.length; i++) { + int start = buffer.getSpanStart(candidates[i]); + int end = buffer.getSpanEnd(candidates[i]); + + if ((end < selEnd || selStart == selEnd) && start >= visibleStart) { + if (end > bestEnd) { + bestStart = buffer.getSpanStart(candidates[i]); + bestEnd = end; + } + } + } + + if (bestStart >= 0) { + Selection.setSelection(buffer, bestEnd, bestStart); + return true; + } + } + + float fourLines = view.getTextSize() * 4; + + visibleRect.left = 0; + visibleRect.right = view.getWidth(); + visibleRect.top = Math.max(0, (int) (topExtra - fourLines)); + visibleRect.bottom = visibleRect.top + rootHeight; + + return view.requestRectangleOnScreen(visibleRect); + } + + private boolean gotoNext(TextView view, Spannable buffer) { + Layout layout = view.getLayout(); + if (layout == null) { + return false; + } + + View root = findScrollableParent(view); + + int rootHeight = root.getHeight(); + + if (visibleRect.bottom <= rootHeight) { + // we fit entirely into the viewport, no need for fancy footwork + return false; + } + + int bottomExtra = visibleRect.bottom - rootHeight; + + int visibleBottomBorder = view.getHeight() - bottomExtra; + + int lineCount = layout.getLineCount(); + + int lastVisibleLineNumber = layout.getLineForVertical(visibleBottomBorder); + + // when deciding whether to pass "focus" to span, account for one more line + // this ensures, that focus is never passed to spans partially outside scroll window + int visibleEnd = lastVisibleLineNumber == lineCount - 1 ? buffer.length() : layout.getLineEnd(lastVisibleLineNumber - 1); + + ClickableSpan[] candidates = buffer.getSpans(0, visibleEnd, ClickableSpan.class); + + if (candidates.length != 0) { + int a = Selection.getSelectionStart(buffer); + int b = Selection.getSelectionEnd(buffer); + + int selStart = Math.min(a, b); + int selEnd = Math.max(a, b); + + int bestStart = Integer.MAX_VALUE; + int bestEnd = Integer.MAX_VALUE; + + for (int i = 0; i < candidates.length; i++) { + int start = buffer.getSpanStart(candidates[i]); + int end = buffer.getSpanEnd(candidates[i]); + + if ((start > selStart || selStart == selEnd) && end <= visibleEnd) { + if (start < bestStart) { + bestStart = start; + bestEnd = buffer.getSpanEnd(candidates[i]); + } + } + } + + if (bestEnd < Integer.MAX_VALUE) { + // cool, we have managed to find next link without having to adjust self within view + Selection.setSelection(buffer, bestStart, bestEnd); + return true; + } + } + + // there are no links within visible area, but still some text past visible area + // scroll visible area further in required direction + float fourLines = view.getTextSize() * 4; + + visibleRect.left = 0; + visibleRect.right = view.getWidth(); + visibleRect.bottom = Math.min((int) (visibleBottomBorder + fourLines), view.getHeight()); + visibleRect.top = visibleRect.bottom - rootHeight; + + return view.requestRectangleOnScreen(visibleRect); + } + + private ViewGroup findScrollableParent(View view) { + View current = view; + + ViewParent parent; + do { + parent = current.getParent(); + + if (parent == current || !(parent instanceof View)) { + return (ViewGroup) view.getRootView(); + } + + current = (View) parent; + + if (current.isScrollContainer()) { + return (ViewGroup) current; + } + } + while (true); + } + + private int dirToRelative(int dir) { + switch (dir) { + case View.FOCUS_DOWN: + case View.FOCUS_RIGHT: + return View.FOCUS_FORWARD; + case View.FOCUS_UP: + case View.FOCUS_LEFT: + return View.FOCUS_BACKWARD; + } + + return dir; + } + + private int keyToDir(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + return View.FOCUS_BACKWARD; + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_RIGHT: + return View.FOCUS_FORWARD; + } + + return View.FOCUS_FORWARD; + } +} diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 02d330ade..6d54525db 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -15,6 +15,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="5" + android:isScrollContainer="true" android:fitsSystemWindows="true"> Date: Thu, 14 Nov 2019 20:48:19 +0659 Subject: [PATCH 023/663] Add hints for focus transition from description --- .../main/res/layout-large-land/fragment_video_detail.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 6d54525db..e1a680e5d 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -379,6 +379,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" + android:focusable="true" + android:descendantFocusability="afterDescendants" android:padding="6dp"> @@ -467,6 +469,8 @@ android:layout_marginTop="5dp" android:orientation="vertical" android:visibility="gone" + android:focusable="true" + android:descendantFocusability="afterDescendants" tools:visibility="visible"> Date: Thu, 14 Nov 2019 20:50:35 +0659 Subject: [PATCH 024/663] More fixes to comment focus handling --- .../holder/CommentsMiniInfoItemHolder.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index e7b09f3e2..198766069 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -126,14 +126,28 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h } private void allowLinkFocus() { + itemContentView.setMovementMethod(LinkMovementMethod.getInstance()); + } + + private void denyLinkFocus() { + itemContentView.setMovementMethod(null); + } + + private boolean shouldFocusLinks() { if (itemView.isInTouchMode()) { - return; + return false; } URLSpan[] urls = itemContentView.getUrls(); - if (urls != null && urls.length != 0) { - itemContentView.setMovementMethod(LinkMovementMethod.getInstance()); + return urls != null && urls.length != 0; + } + + private void determineLinkFocus() { + if (shouldFocusLinks()) { + allowLinkFocus(); + } else { + denyLinkFocus(); } } @@ -151,8 +165,10 @@ private void ellipsize() { linkify(); - if (!hasEllipsis) { - allowLinkFocus(); + if (hasEllipsis) { + denyLinkFocus(); + } else { + determineLinkFocus(); } } @@ -168,13 +184,11 @@ private void expand() { itemContentView.setMaxLines(commentExpandedLines); itemContentView.setText(commentText); linkify(); - allowLinkFocus(); + determineLinkFocus(); } private void linkify(){ Linkify.addLinks(itemContentView, Linkify.WEB_URLS); Linkify.addLinks(itemContentView, pattern, null, null, timestampLink); - - itemContentView.setMovementMethod(null); } } From 7d75950624f56512a713a2b1ba4cbdc700d89673 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Thu, 14 Nov 2019 20:54:40 +0659 Subject: [PATCH 025/663] Disable srolling down comment list while comments are loading Prevents comment list from losing focus to some outside View when user tries to scroll down after reaching "end" --- .../fragments/list/BaseListInfoFragment.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 9a8e1fd17..7363d221c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -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; @@ -17,6 +18,8 @@ import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; public abstract class BaseListInfoFragment @@ -136,9 +139,13 @@ protected void loadMoreItems() { isLoading.set(true); 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); handleNextItems(InfoItemsPage); @@ -148,6 +155,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(ListExtractor.InfoItemsPage result) { super.handleNextItems(result); From 436c75ca6c081b34388a835924b1270277d50ffe Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Thu, 14 Nov 2019 22:43:54 +0659 Subject: [PATCH 026/663] Make comment pic explicitly non-focusable --- app/src/main/res/layout/list_comments_item.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 393d7d1b4..41606201f 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -18,6 +18,7 @@ android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:contentDescription="@string/list_thumbnail_view_description" + android:focusable="false" android:src="@drawable/buddy" tools:ignore="RtlHardcoded" /> From a1e02f770434237b0be4bf0bf8a938a33018b509 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Sat, 16 Nov 2019 13:02:46 +0659 Subject: [PATCH 027/663] Default to landscape orientation for Android TV --- .../main/java/org/schabi/newpipe/player/MainVideoPlayer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 38da4d8b2..0650e2a26 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -164,13 +164,14 @@ public void onChange(boolean selfChange) { super.onChange(selfChange); if (globalScreenOrientationLocked()) { final boolean lastOrientationWasLandscape = defaultPreferences.getBoolean( - getString(R.string.last_orientation_landscape_key), false); + getString(R.string.last_orientation_landscape_key), FireTvUtils.isFireTv()); setLandscape(lastOrientationWasLandscape); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } } }; + getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, rotationObserver); @@ -238,7 +239,7 @@ protected void onResume() { if (globalScreenOrientationLocked()) { boolean lastOrientationWasLandscape = defaultPreferences.getBoolean( - getString(R.string.last_orientation_landscape_key), false); + getString(R.string.last_orientation_landscape_key), FireTvUtils.isFireTv()); setLandscape(lastOrientationWasLandscape); } From c0fb96a911c7c0464e26b78597e58aa96b19bde9 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Sat, 16 Nov 2019 13:05:59 +0659 Subject: [PATCH 028/663] Release seekbar on any confirmation key, not just DPAD_CENTER --- .../java/org/schabi/newpipe/util/FireTvUtils.java | 13 +++++++++++++ .../org/schabi/newpipe/views/FocusAwareSeekBar.java | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java b/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java index 879b54e1f..2c5090381 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java @@ -3,6 +3,7 @@ import android.annotation.SuppressLint; import android.content.pm.PackageManager; +import android.view.KeyEvent; import org.schabi.newpipe.App; public class FireTvUtils { @@ -15,4 +16,16 @@ public static boolean isFireTv(){ return pm.hasSystemFeature(AMAZON_FEATURE_FIRE_TV) || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); } + + public static boolean isConfirmKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_SPACE: + case KeyEvent.KEYCODE_NUMPAD_ENTER: + return true; + default: + return false; + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java index 3789ea344..dafd5ae6f 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java @@ -25,6 +25,7 @@ import android.widget.SeekBar; import androidx.appcompat.widget.AppCompatSeekBar; +import org.schabi.newpipe.util.FireTvUtils; /** * SeekBar, adapted for directional navigation. It emulates touch-related callbacks @@ -57,7 +58,7 @@ public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (!isInTouchMode() && keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + if (!isInTouchMode() && FireTvUtils.isConfirmKey(keyCode)) { releaseTrack(); } From dc7ae3917e97889def2f7315e7ca4d947dbdd847 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Sun, 17 Nov 2019 16:53:11 +0659 Subject: [PATCH 029/663] Leanback launcher support --- app/src/main/AndroidManifest.xml | 2 ++ .../main/res/mipmap-xhdpi/newpipe_tv_banner.png | Bin 0 -> 2138 bytes 2 files changed, 2 insertions(+) create mode 100644 app/src/main/res/mipmap-xhdpi/newpipe_tv_banner.png diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9052dabab..3583d0312 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + diff --git a/app/src/main/res/mipmap-xhdpi/newpipe_tv_banner.png b/app/src/main/res/mipmap-xhdpi/newpipe_tv_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..4be6644504b178aa612b218202626b4b5d9e2a26 GIT binary patch literal 2138 zcmdT`i#OZZ7XC#QnU2k3^SOFRAEAiK4{2qg9Wpsz+;r^d`u7M|GxkJDS|2 zC{fXiW|)dfYcdk*cG{sSDOIH&p*5i-5d=Xb++WxH6LZg6`@Fuj&feeNXPxhqhKKs= zeqr(j006p}0N-x`0D=bRPjs}vepa-u4-T4ylbAC)V9VD@x(TkKR|BF60I;X+vq9EN z7BS$aai(8XW&|!JlbC={28cwWQ)&j5keF~a*$IcgOqZcd06=>bt2* zMeoX}pV}{@+PGul%LFUyK0z?8J`I&jq%`8(>>UF-6Jtb$h7z@Ke#%%y_cwc7=)(@U#6inNelwd%a{vI+na zOUH810is!LOF}=^)VopvXb23@b0!Z8RI5}2FI_0(U3LXvpaw*zYPU8-E+#>bi^Z_%xW=$$Ll_em== zq9!KQXa9l{F^JWqcyUHA@XOD^`YPc_;T>rgaTI<{tVuER*!+;x^OLCL0Cn}XM2QG5 z2iSyAuM<^B=N!eDXuh83se9%yN!nRkE0Xuo2E4)^goY@_m=eQhURES2_r4E}Ckw1_ zz`%m%m54_ZniL`OlCMi%whJ~AFKvrsXk}Lc@_{%!M!vx0uPw0NwfXC##fMyMs$3dX zX+7x%3O$X0IDhj6H)!6MkOtyUW^{*`J1E8737fT8jesR%gk8s+2?sOJ(WFdq7Tx&Q z_VPPEWfz!iIzt+kYW{gju9!&X|GxYYbfro09C;g2;qWl(bOuc&iH~F4{1Iv?+*GGi zZ#jvT2t*(-b}YW=o@&{rD+gXf=~Ut6s#X3!dWY%zJiI~dmQm#uAu*nv;M3Jn8CQ|7 zh{i8<@praILEI8#+m5Aq$n3l_3unu72^&IWhg!i-PZ?^+C46+&vE<>lmyk*YhVtCf1I>#%RcUk2v}ppmq+gg# zPa0#(TiPaUq%cUshI%t`Ov^PTST5tO&+RvjEJSYd;rz{&^|>LiC`Qghx0eO50f$zg3t+C(%ON? zDdpiI^a_;{+tp!o;coug>8*b&inT{T81!i&8LjM{(YK~D87w4m`NJXy)5)ey>!X#7 zv%duX+(Qm~y7`}}$>#^0gAUFKf|QyPvXi*EEVo+UT9x$R7RTAmlHrIy$h#j6Erq5i z_YTFlNXC;CM)0z*E`$`kq=mmR{W*S+ICkJ~&!AQn+o)5k`(gz3Xh{rAEk?pY!h#VP3oEaZY=x zfgz$(*AtX%Wa_;OF=OjKL7+Q~cxHXH3pc@SHi~~}+WqdAzVBMbB^#+fhLP546P~}? zF2Qv)%0}jNO{xACArFF^whw+&ZA}{Hq$jJ@8}J+EF+_!QY__*QzThm;DFnP@;+EL2 z?IZ8ti1E2ivh&q2(y(h%0Q;i1Fx_4%V)Nn1}EMnwpr*3{}cND2vDH#2(5m z#uYCVdG5F$-&M&U8?V$GK`qt2VP56Ct7Yc*I@cpdnfD>)#S2pvj(pdFs2DWE5Y27B znh+hcyt5t0xUQ9>`M`9#kRDuHQ$REYW-$bGK=NYK6tr$wa= z<%RXpf`oU?>?ao|-B;@4SD=)s$AtyaMaK%bjeFOEz$fGG2_8zuIzP6K`QQwm%yRAy zh6USk++UCjU&GPjtwoN!kX&ZU^Mp#jf;7}R#=f$U9!6>G`AZ^RzMkmpa6tV4`F$4{ zJHF2dHX6_9XH2hS>P#5GSlhI-qn?-6AqKETpu5=~@JdUCfYp)vR~#JFA^$gxliS|7 z7tweb%WjwW47M3vuyeMkcKKsSquPpO<8uSntr}WCQaA8ffp+)&U+U9eYN|xG(W$3i T=$3)s006-Fh5E8iUcB}{BjNT* literal 0 HcmV?d00001 From 106e538d0824d23604ce83c8bf9ad0916dc65ee5 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Sun, 17 Nov 2019 16:54:18 +0659 Subject: [PATCH 030/663] Excpicitly disable touchscreen requirement --- app/src/main/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3583d0312..3284202fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,8 @@ + + Date: Sun, 17 Nov 2019 16:55:22 +0659 Subject: [PATCH 031/663] Disable touchScreenBlocksFocus on AppBarLayout For some inexplicable reason this attribute got enabled by default on Android 9, which effectively prevents details screen from working --- app/src/main/res/layout-large-land/fragment_video_detail.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index e1a680e5d..684adc222 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -23,6 +23,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/transparent" + android:touchscreenBlocksFocus="false" android:fitsSystemWindows="true" app:elevation="0dp" app:layout_behavior="com.google.android.material.appbar.FlingBehavior"> From 29136d633a9f54d774e5dbc39b85c73d6b2b06ae Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Sun, 1 Dec 2019 12:33:34 +0659 Subject: [PATCH 032/663] Intercept ActivityNotFoundException for ACTION_CAPTIONING_SETTINGS --- .../newpipe/settings/AppearanceSettingsFragment.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java index ce22b84e9..72d720824 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java @@ -1,9 +1,11 @@ package org.schabi.newpipe.settings; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.preference.Preference; @@ -42,7 +44,11 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @Override public boolean onPreferenceTreeClick(Preference preference) { if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) { - startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); + try { + startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); + } catch (ActivityNotFoundException e) { + Toast.makeText(getActivity(), R.string.general_error, Toast.LENGTH_SHORT).show(); + } } return super.onPreferenceTreeClick(preference); From 3f51114129c813ac83bf658f88934f968990faa9 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Sun, 1 Dec 2019 12:38:01 +0659 Subject: [PATCH 033/663] Improve usability of settings on TV devices * Add focus overlay to SettingsActivity * Make screen "Contents of Main Page" navigable from remote --- .../java/org/schabi/newpipe/settings/SettingsActivity.java | 6 ++++++ app/src/main/res/layout/list_choose_tabs.xml | 1 + 2 files changed, 7 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index a3f218074..e53b7ba07 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -12,7 +12,9 @@ import android.view.MenuItem; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.FireTvUtils; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.views.FocusOverlayView; /* @@ -56,6 +58,10 @@ protected void onCreate(Bundle savedInstanceBundle) { .replace(R.id.fragment_holder, new MainSettingsFragment()) .commit(); } + + if (FireTvUtils.isFireTv()) { + FocusOverlayView.setupFocusObserver(this); + } } @Override diff --git a/app/src/main/res/layout/list_choose_tabs.xml b/app/src/main/res/layout/list_choose_tabs.xml index ce17e0382..82c9dd081 100644 --- a/app/src/main/res/layout/list_choose_tabs.xml +++ b/app/src/main/res/layout/list_choose_tabs.xml @@ -12,6 +12,7 @@ android:layout_marginTop="3dp" android:minHeight="?listPreferredItemHeightSmall" android:orientation="horizontal" + android:focusable="true" app:cardCornerRadius="5dp" app:cardElevation="4dp"> From 8c9015b57bd489a192e69f4a8977c745810403b2 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Tue, 10 Dec 2019 21:21:35 +0659 Subject: [PATCH 034/663] Remove commented code --- .../com/google/android/material/appbar/FlingBehavior.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index ea2857b03..3af2c95bc 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -1,6 +1,5 @@ package com.google.android.material.appbar; -import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; @@ -35,8 +34,6 @@ public boolean onRequestChildRectangleOnScreen(@NonNull CoordinatorLayout coordi return false; } - int offset = getTopAndBottomOffset(); - int dy; if (focusScrollRect.bottom > height) { @@ -49,8 +46,6 @@ public boolean onRequestChildRectangleOnScreen(@NonNull CoordinatorLayout coordi return false; } - //int newOffset = offset + dy; - int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0); return consumed == dy; From caa1de8aff77b92255d68113ade22afe7d17e8ed Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Wed, 29 Jan 2020 03:15:50 +0659 Subject: [PATCH 035/663] Rename FireTvUtils to AndroidTvUtils and isFireTv() to isTV() Because those methods are no longer exclusive to Amazon devices --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 6 +++--- .../main/java/org/schabi/newpipe/RouterActivity.java | 4 ++-- .../org/schabi/newpipe/download/DownloadActivity.java | 5 ++--- .../newpipe/fragments/list/search/SearchFragment.java | 4 ++-- .../org/schabi/newpipe/player/MainVideoPlayer.java | 10 +++++----- .../org/schabi/newpipe/settings/SettingsActivity.java | 4 ++-- .../util/{FireTvUtils.java => AndroidTvUtils.java} | 4 ++-- .../org/schabi/newpipe/views/FocusAwareSeekBar.java | 4 ++-- 8 files changed, 20 insertions(+), 21 deletions(-) rename app/src/main/java/org/schabi/newpipe/util/{FireTvUtils.java => AndroidTvUtils.java} (92%) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index a2f161847..d2cbb49e0 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -69,7 +69,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.FireTvUtils; +import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PeertubeHelper; @@ -140,7 +140,7 @@ protected void onCreate(Bundle savedInstanceState) { ErrorActivity.reportUiError(this, e); } - if (FireTvUtils.isFireTv()) { + if (AndroidTvUtils.isTv()) { FocusOverlayView.setupFocusObserver(this); } } @@ -489,7 +489,7 @@ protected void onNewIntent(Intent intent) { public void onBackPressed() { if (DEBUG) Log.d(TAG, "onBackPressed() called"); - if (FireTvUtils.isFireTv()) { + if (AndroidTvUtils.isTv()) { View drawerPanel = findViewById(R.id.navigation_layout); if (drawer.isDrawerOpen(drawerPanel)) { drawer.closeDrawers(); diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index c5b97f86f..412bea0e1 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -45,7 +45,7 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.FireTvUtils; +import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; @@ -319,7 +319,7 @@ private void showDialog(final List choices) { alertDialog.show(); - if (FireTvUtils.isFireTv()) { + if (AndroidTvUtils.isTv()) { FocusOverlayView.setupFocusObserver(alertDialog); } } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java index 6ceacbb05..514c3dd37 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java @@ -12,8 +12,7 @@ import android.view.ViewTreeObserver; import org.schabi.newpipe.R; -import org.schabi.newpipe.settings.SettingsActivity; -import org.schabi.newpipe.util.FireTvUtils; +import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; @@ -53,7 +52,7 @@ public void onGlobalLayout() { } }); - if (FireTvUtils.isFireTv()) { + if (AndroidTvUtils.isTv()) { FocusOverlayView.setupFocusObserver(this); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index f2e8aa244..9e4fd467c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -40,7 +40,7 @@ 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.util.AndroidTvUtils; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -471,7 +471,7 @@ private void initSearchListeners() { if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) { showSuggestionsPanel(); } - if(FireTvUtils.isFireTv()){ + if(AndroidTvUtils.isTv()){ showKeyboardSearch(); } }); diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index fa742f771..470e1c963 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -78,7 +78,7 @@ import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.util.AnimationUtils; -import org.schabi.newpipe.util.FireTvUtils; +import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.KoreUtil; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; @@ -166,7 +166,7 @@ public void onChange(boolean selfChange) { super.onChange(selfChange); if (globalScreenOrientationLocked()) { final boolean lastOrientationWasLandscape = defaultPreferences.getBoolean( - getString(R.string.last_orientation_landscape_key), FireTvUtils.isFireTv()); + getString(R.string.last_orientation_landscape_key), AndroidTvUtils.isTv()); setLandscape(lastOrientationWasLandscape); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); @@ -178,7 +178,7 @@ public void onChange(boolean selfChange) { Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, rotationObserver); - if (FireTvUtils.isFireTv()) { + if (AndroidTvUtils.isTv()) { FocusOverlayView.setupFocusObserver(this); } } @@ -206,7 +206,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { default: break; case KeyEvent.KEYCODE_BACK: - if (FireTvUtils.isFireTv() && playerImpl.isControlsVisible()) { + if (AndroidTvUtils.isTv() && playerImpl.isControlsVisible()) { playerImpl.hideControls(0, 0); hideSystemUi(); return true; @@ -241,7 +241,7 @@ protected void onResume() { if (globalScreenOrientationLocked()) { boolean lastOrientationWasLandscape = defaultPreferences.getBoolean( - getString(R.string.last_orientation_landscape_key), FireTvUtils.isFireTv()); + getString(R.string.last_orientation_landscape_key), AndroidTvUtils.isTv()); setLandscape(lastOrientationWasLandscape); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index e53b7ba07..49d6d49fe 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -12,7 +12,7 @@ import android.view.MenuItem; import org.schabi.newpipe.R; -import org.schabi.newpipe.util.FireTvUtils; +import org.schabi.newpipe.util.AndroidTvUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; @@ -59,7 +59,7 @@ protected void onCreate(Bundle savedInstanceBundle) { .commit(); } - if (FireTvUtils.isFireTv()) { + if (AndroidTvUtils.isTv()) { FocusOverlayView.setupFocusObserver(this); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java b/app/src/main/java/org/schabi/newpipe/util/AndroidTvUtils.java similarity index 92% rename from app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java rename to app/src/main/java/org/schabi/newpipe/util/AndroidTvUtils.java index 2c5090381..203501a51 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/AndroidTvUtils.java @@ -6,9 +6,9 @@ import android.view.KeyEvent; import org.schabi.newpipe.App; -public class FireTvUtils { +public class AndroidTvUtils { @SuppressLint("InlinedApi") - public static boolean isFireTv(){ + public static boolean isTv(){ final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; PackageManager pm = App.getApp().getPackageManager(); diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java index dafd5ae6f..8ccff85d5 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareSeekBar.java @@ -25,7 +25,7 @@ import android.widget.SeekBar; import androidx.appcompat.widget.AppCompatSeekBar; -import org.schabi.newpipe.util.FireTvUtils; +import org.schabi.newpipe.util.AndroidTvUtils; /** * SeekBar, adapted for directional navigation. It emulates touch-related callbacks @@ -58,7 +58,7 @@ public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (!isInTouchMode() && FireTvUtils.isConfirmKey(keyCode)) { + if (!isInTouchMode() && AndroidTvUtils.isConfirmKey(keyCode)) { releaseTrack(); } From 01dcf550cfd6f5e5b0d8f72779f118bea3622028 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 22 Feb 2020 19:56:56 -0800 Subject: [PATCH 036/663] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 24 ++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..32fa3e03e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a bug report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + + +### Steps to reproduce +Steps to reproduce the behavior: +1. Go to '...' +2. Press on '....' +3. Swipe down to '....' + +### Expected behavior +Tell us what you expected to happen. + +### Actual behaviour +Tell us what happens instead + +### Screenshots/-recording +If applicable, add screenshots or a screen recording to help explain your problem. Github should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from a image/video hoster here instead. + +### Logs +If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it between the code tags: +here diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..a0bde3dc0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature]" +labels: enhancement +assignees: '' + +--- + +#### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. +Ex. *I want to do X, but there is no way to do it.* + +#### Describe the solution you'd like +A clear and concise description of what you want to happen. +Ex. *I think it would be nice if you would add feature Y so it will make it easier.* + +#### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. +Ex. *I considered Z, but that didn't work because...* + +#### Additional context +Add any other context or screenshots about the feature request here. +Ex. *Here's a photo of my cat!* From 495b495f27ced0191ef89b7625577b99ae87d9be Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 22 Feb 2020 20:03:38 -0800 Subject: [PATCH 037/663] deleting old template --- .github/ISSUE_TEMPLATE.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8073503ad..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,3 +0,0 @@ -- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them. -- [ ] I checked if the issue/feature exists in the latest version. -- [ ] I did use the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/) to paste bug reports. From 30f66d012e53a8d13ee8a448f2229162ed8c3df6 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Sat, 22 Feb 2020 20:16:14 -0800 Subject: [PATCH 038/663] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d0e58680a..ea06e601a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1 +1,19 @@ + + +#### long description of the changes in your PR +*Now you can make videos* + +#### Fixes the following issue(s) + +- #1234 + +#### Relies on the following changes + +- #1234 + +#### Testing apk + +debug.zip + +#### Agreement - [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them. From a3bce7f7caf14f034fd550870bcbeaf5d04dee5e Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 23 Feb 2020 09:46:42 +0100 Subject: [PATCH 039/663] Change app id based on current git branch This enables to install multiple builds from different branches at once --- app/build.gradle | 11 ++++++++++- app/src/debug/AndroidManifest.xml | 17 ----------------- app/src/main/res/values/strings.xml | 1 - 3 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 app/src/debug/AndroidManifest.xml diff --git a/app/build.gradle b/app/build.gradle index c2bceab9e..36a712cc3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,6 +9,7 @@ android { defaultConfig { applicationId "org.schabi.newpipe" + resValue "string", "app_name", "NewPipe" minSdkVersion 19 targetSdkVersion 28 versionCode 840 @@ -28,7 +29,15 @@ android { debug { multiDexEnabled true debuggable true - applicationIdSuffix ".debug" + + def workingBranch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() + if (workingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") { + applicationIdSuffix ".debug" + resValue "string", "app_name", "NewPipe Debug" + } else { + applicationIdSuffix ".debug." + workingBranch.replaceAll("[^A-Za-z]+", "") + resValue "string", "app_name", "NewPipe " + workingBranch + } } } diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml deleted file mode 100644 index a16d6796a..000000000 --- a/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 148a339a9..bcbfcd6d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,5 @@ - NewPipe Tap \"Search\" to get started %1$s views Published on %1$s From 030e5ab894ed576e9e63332c6b08cdda090eade6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 23 Feb 2020 20:56:56 +0100 Subject: [PATCH 040/663] Add comment to gradle --- app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle b/app/build.gradle index 36a712cc3..2329a7a0e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,7 @@ android { multiDexEnabled true debuggable true + // suffix the app id and the app name with git branch name def workingBranch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() if (workingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") { applicationIdSuffix ".debug" From 01c1fa0393df8e856129b59b0869ddfb1128d0ee Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+poolitzer@users.noreply.github.com> Date: Sun, 23 Feb 2020 16:16:18 -0800 Subject: [PATCH 041/663] B0pol suggested improvements --- .github/ISSUE_TEMPLATE/bug_report.md | 8 +++++--- .github/ISSUE_TEMPLATE/feature_request.md | 5 +++++ .github/PULL_REQUEST_TEMPLATE.md | 14 +++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 32fa3e03e..d4e0ea9b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -16,9 +16,10 @@ To make it easier for us to help you please enter detailed information below. Please note, we only support the latest version of NewPipe and master branch. Please make sure to upgrade & recreate the issue on the latest -version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in you app (left sidebar, about) +version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in you app (To check you version, press on the three line menu on the left, click on "About", and you will see your current version.) --> ### Steps to reproduce + Steps to reproduce the behavior: 1. Go to '...' 2. Press on '....' @@ -34,5 +35,6 @@ Tell us what happens instead If applicable, add screenshots or a screen recording to help explain your problem. Github should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from a image/video hoster here instead. ### Logs -If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it between the code tags: -here +If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it here: + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a0bde3dc0..e39fc5f07 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -22,3 +22,8 @@ Ex. *I considered Z, but that didn't work because...* #### Additional context Add any other context or screenshots about the feature request here. Ex. *Here's a photo of my cat!* + +#### Why do you/everyone wants this feature +Convince us! How does it change your NewPipe experience and/or your life? +The better this paragraph is, the more likely a developer will think about developing it + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ea06e601a..570d07778 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,15 +1,19 @@ - + -#### long description of the changes in your PR +#### What is it? +- [ ] Bug fix +- [ ] Feature + +#### Long description of the changes in your PR *Now you can make videos* #### Fixes the following issue(s) -- #1234 +- #### Relies on the following changes - -- #1234 + +- #### Testing apk From 1d9ffffc497108614a18b877f9017d12d053187f Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Mon, 24 Feb 2020 14:18:48 -0800 Subject: [PATCH 042/663] Minor improvements --- .github/ISSUE_TEMPLATE/bug_report.md | 9 +++++---- .github/ISSUE_TEMPLATE/feature_request.md | 13 +++++++------ .github/PULL_REQUEST_TEMPLATE.md | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d4e0ea9b7..8b5f10f08 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -15,8 +15,9 @@ Use this template to notify us if you found a bug. To make it easier for us to help you please enter detailed information below. Please note, we only support the latest version of NewPipe and -master branch. Please make sure to upgrade & recreate the issue on the latest -version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in you app (To check you version, press on the three line menu on the left, click on "About", and you will see your current version.) +master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in you app (To check you version, press on the three line menu on the left, click on "About", and you will see your current version). + +P.S.: Our [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be a nice document to read before you fill out the report :) --> ### Steps to reproduce @@ -29,7 +30,7 @@ Steps to reproduce the behavior: Tell us what you expected to happen. ### Actual behaviour -Tell us what happens instead +Tell us what happens instead. ### Screenshots/-recording If applicable, add screenshots or a screen recording to help explain your problem. Github should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from a image/video hoster here instead. @@ -37,4 +38,4 @@ If applicable, add screenshots or a screen recording to help explain your proble ### Logs If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it here: - + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index e39fc5f07..8453f99ba 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,24 +6,25 @@ labels: enhancement assignees: '' --- - + #### Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. -Ex. *I want to do X, but there is no way to do it.* +Example: *I want to do X, but there is no way to do it.* #### Describe the solution you'd like A clear and concise description of what you want to happen. -Ex. *I think it would be nice if you would add feature Y so it will make it easier.* +Example: *I think it would be nice if you would add feature Y so it will make it easier.* #### Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. -Ex. *I considered Z, but that didn't work because...* +Example: *I considered Z, but that didn't work because...* #### Additional context Add any other context or screenshots about the feature request here. -Ex. *Here's a photo of my cat!* +Example: *Here's a photo of my cat!* #### Why do you/everyone wants this feature Convince us! How does it change your NewPipe experience and/or your life? -The better this paragraph is, the more likely a developer will think about developing it +The better this paragraph is, the more likely a developer will think about developing it. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 570d07778..43a6c0c4d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,7 +16,7 @@ - #### Testing apk - + debug.zip #### Agreement From 6a3a72eb065bdf9419e58f01e90e4bf4a576d6a2 Mon Sep 17 00:00:00 2001 From: Alexander-- Date: Wed, 26 Feb 2020 06:40:46 +0659 Subject: [PATCH 043/663] NewPipeRecyclerView should allow scrolling down by default --- .../main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java index 435281d14..41b823db8 100644 --- a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java +++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java @@ -36,7 +36,7 @@ public class NewPipeRecyclerView extends RecyclerView { private Rect focusRect = new Rect(); private Rect tempFocus = new Rect(); - private boolean allowDpadScroll; + private boolean allowDpadScroll = true; public NewPipeRecyclerView(@NonNull Context context) { super(context); From d9a8e4d7971b6ed63bcd42d7318faa7ac0ee843a Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Wed, 26 Feb 2020 19:50:11 -0800 Subject: [PATCH 044/663] NewPipe is an app though :( And its our app! Co-Authored-By: Tobias Groza --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 43a6c0c4d..978adc100 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ - + #### What is it? - [ ] Bug fix From 46165f4a4f38353edebad49790f36717d9f3ae76 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+poolitzer@users.noreply.github.com> Date: Fri, 28 Feb 2020 15:32:14 -0800 Subject: [PATCH 045/663] adding version section to bug report --- .github/ISSUE_TEMPLATE/bug_report.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d4e0ea9b7..ad436f64f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -18,6 +18,11 @@ Please note, we only support the latest version of NewPipe and master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in you app (To check you version, press on the three line menu on the left, click on "About", and you will see your current version.) --> +### Version + +- + + ### Steps to reproduce Steps to reproduce the behavior: From 5257c5a0a89bf683cabf4a7cbfcb9a01f020c00b Mon Sep 17 00:00:00 2001 From: B0pol Date: Sat, 29 Feb 2020 17:28:58 +0000 Subject: [PATCH 046/663] Translated using Weblate (Esperanto) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-eo/strings.xml | 62 +++++++++++++------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 4a6fcdfab..6f7779a81 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -38,8 +38,8 @@ Ŝatoj Malŝatoj Uzi la programon Tor - Neniu elsendlflua ludilo trovita. Ĉu vi volas instali la aplikaĵon VLC\? - La aplikaĵo Kore ne estas trovita. Ĉu instali ĝin? + Neniu elsendlflua ludilo trovita. Ĉu instali la aplikaĵon VLC\? + Ĉu instali la mankan aplikaĵon Kore\? Montri la sekvan filmeton kaj similajn filmetojn Ĉiuj bildetoj ne ŝargeblas La subskribo de la ligilo de la filmeto ne malĉifreblas @@ -58,19 +58,19 @@ Elektu lokon por konservi elŝutitajn filmetojn Elektu lokon por konservi elŝutitajn muzikojn Enhavo - Signali eraron per retpoŝto - SIGNALI + Signali tion eraron retpoŝte + Signali Informoj: Vian komenton (angle): Detaloj: Signali eraron Filmeto Reprovi - Premi serĉon por komenci + Premi \"Serĉi\" por komenci Neniu elsendlflua ludilo trovita (instalu VLC por ludi ĝin). Malfermi en ŝprucfenestran modon - Forigas aŭdon ĉe KELKAJ rezolucioj - NewPipe ŝprucfenestran modon + Forigas aŭdon ĉe kelkaj rezolucioj + Ŝprucfenestran modon Aboni Abonita Kanalo malabonita @@ -89,7 +89,7 @@ Ludas filmeton kiam NewPipe vokas el alia programo Defaŭlta rezolucio de la ŝprucfenestra ludilo Montri pli altajn rezoluciojn - Nur kelkaj aparatoj subtenas ludi 2K / 4K filmetojn + Nur kelkaj aparatoj povas ludi 2K / 4K filmetojn Defaŭlta fomato de filmeto Nigra Memoru ŝprucfenestran grandecon kaj pozicion @@ -100,10 +100,10 @@ Ne povis konstrui la dosierujon de elŝuto Nunaj filmetoj ne estas ankoraŭ subtenataj Enhavo limigita al aĝo - Montri limigitan al aĝo filmeto. Permesanta tian materialon eblas el Parametroj. + Montri limigitan al aĝo filmeto. Postaj ŝanĝoj eblas ĉe la agordoj. Ne povis tute analizi la retejon Ne povis akiri ajnan torenton - NUNA + Nuna Elŝutoj Elŝutoj Erarosignalo @@ -130,7 +130,7 @@ Ŝprucfenestro Regrandiganta Kontrolo de gesto de ludilo - Uzu gestojn por kontroli la brilon kaj volumenon de la ludilo + Uzi gestojn por kontroli la brilon kaj volumenon Serĉi sugestojn Montri sugestojn kiam serĉanto Plej bona rezolucio @@ -138,7 +138,7 @@ Elŝuti Leteroj kaj ciferoj Plej specialaj karakteroj - Rekomenci en fokusa gajno + Ludrekomenci Daŭrigi la ludon post la interrompaĵoj (ekzemple telefonadoj) Serĉa historio Konservi la historio de serĉo lokale @@ -156,7 +156,7 @@ Supro 50 Nova & varma Montri la indiko « Tenu por aldoni » - Montri indikon kiam la fona aŭ ŝprucfenestra butono estas premita en la retpaĝo de dalatadoj de la filmeto + Montri indikon premante la fona aŭ la ŝprucfenestra butono en filmeta \"Detaloj:\" Viciĝita en la fona ludilo Viciĝita en ŝprucfenestra ludilo Ludi ĉiujn @@ -205,7 +205,7 @@ Ne povis forviŝi ludliston. Malcimigi Auto-vico sekva fluo - Aŭto-aldoni rilatan enhavon kiam ludanta la lasta enhavo en malrepetita atendovico + Daŭrigi finanta (malripetanta) atendovico aldonante rilata enhavo Dosiero Tia dosierujo ne ekzistas Tia dosiero/enhavo ne ekzistas @@ -246,9 +246,9 @@ Nova ongleto Elektu ongleton Kontrolo de volumena gesto - Uzu gestojn por kontroli la volumon de la ludilo + Uzi gestojn por kontroli la volumon Kontrolo de gesto de brilo - Uzu gestojn por kontroli la brilon de la ludilo + Uzi gestojn por kontroli la brilon Ĝisdatigoj Dosiero forviŝita Sciigo por ĝisdatigi apon @@ -264,11 +264,11 @@ Eventoj Konferencoj Montri komentojn - Malebligu por malvidigi komentojn + Malŝati por malvidigi komentojn Aŭtoludo - Komentoj - + %s komento + %s komentoj Ne povis ŝarĝi komentojn Fermi @@ -291,8 +291,8 @@ Oni petos vin kie konservi ĉion elŝutaĵon. \nElektu AFM se vi volas elŝuti al ekstera SD-karto Uzu AFM - La Atinga Framo al la Memoro ebligas elŝuti al ekstera SD-karto. -\nKomento: kelkaj aparatoj ne kongruas + La \"Atinga Framo al la Memoro\" ebligas elŝuti al ekstera SD-karto. +\nKomento: kelkaj aparatoj malkongruas Forviŝi ludajn poziciojn Forviŝi la totalon de ludaj pozicioj Ĉu vi volas forviŝi ĉiujn ludajn poziciojn \? @@ -302,7 +302,7 @@ Kio okazis: Kio:\\nPeto:\\nEnhavlingvo:\\nServo:\\nGMT Horo:\\nPako:\\nVersio:\\nOperaciumo versio: Aŭdio - Permeso por atingi la konservon rifuzita + Permesi la konservadon unue Uzantosignalo Komenci Paŭzigi @@ -377,7 +377,7 @@ Komenci ludi ĉi tie Komenci ludi fone Donaci - NewPipe estas programadita par volontuoj, elspezante tempo por alporti vin la plej bona sperto. Redoni por helpi programistojn plibonigi NewPipe dum ĝuante tason da kafo. + NewPipe estas programadita par volontuoj, elspezante lia tempo por alporti vin la plej bona uzanta sperto. Redoni por helpi programistojn plibonigi NewPipe dum ili ĝuas tason da kafo. Redoni Retejo Viziti la retejon de NewPipe por pli da informoj kaj novaĵoj. @@ -409,7 +409,7 @@ Zomi Io aperos ĉi tie baldaŭ ;D Aŭtomate generita - Ebligi LeakCanary + LeakCanary La monitorado de la memorlikadoj povas frostigi la apon dum la hejta dumpingo Signali ekster-vivciklajn erarojn Perforti signalante neenretigaj Rx esceptoj eksere la fragmento aŭ aktiveco vivciklo post dispono @@ -455,10 +455,10 @@ Plirapidigi dum silentoj Paŝo Restarigi - Uzante defaŭltajn ongletojn, eraro ludante savajn ongletojn + Ne povis legi konservitajn ongletoj, tial uzante la defaŭltajn Restaŭri la defaŭltojn - Ĉu vi volas restaŭri la defaŭltojn \? - Kalkulo de abonantoj malhavebla + Ĉu vi volas restaŭri la defaŭltojn valorojn\? + Abonantoj kalkulo malhaveblas Kioj ongletoj estas montritaj en la ĉefpaĝo Elektaĵo Ĝisdatigoj @@ -486,7 +486,7 @@ La celloko-dosierujo ne povas esti kreita La dosiero ne povas esti kreita Permeso rifuzita kaŭze de la sistemo - Sekura konekto malsukcesis + Ne povis establi sekuran konekton Ne povis trovi la servilon Ne povas konektiĝi al la servilo La servilo ne sendas datumojn @@ -519,7 +519,7 @@ Rapida antaŭen / posten daŭron Instancoj de PeerTube Elekti viajn preferitajn instancojn de PeerTube - Trovu la instancojn kiu vi povus ŝati ĉe %s + Trovu la instancojn ke vi ŝatas ĉe %s Aldoni instanco Eniri la ligilon de la instanco Ne povis validigi instanco @@ -532,8 +532,8 @@ Reakiranta Ne povas reakiri tion elŝuton Elektu instancon - Enablu bildeta filmeton ĉe ŝlosita ekrano - Uzante la fona ludilo, bildeta filmeto vidiĝos ĉe ŝlosita ekrano + Bildeta filmeton ĉe ŝlosita ekrano + Bildeta filmeto estas montrita ĉe ŝlosita ekrano uzante la fona ludilo Forviŝi la historion de elŝutoj Forviŝi elŝutitajn dosierojn %1$s elŝutoj forviŝitaj From deafe93e6c0c6b313adcbc9bc7f0554e2851283f Mon Sep 17 00:00:00 2001 From: Igor Nedoboy Date: Sat, 29 Feb 2020 21:30:34 +0000 Subject: [PATCH 047/663] Translated using Weblate (Russian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7c7965f80..1a94ebb1f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -552,7 +552,7 @@ Видео %s секунд - - + + \ No newline at end of file From ca2e9d4afa43c365619e58b9818a2ee7d6e2b22e Mon Sep 17 00:00:00 2001 From: B0pol Date: Sat, 29 Feb 2020 17:51:26 +0000 Subject: [PATCH 048/663] Translated using Weblate (French) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e004a8ea1..fccfbc387 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -428,7 +428,7 @@ Grille Auto Changer de vue - Une mise à jour de NewPipe est disponible ! + Une mise à jour de NewPipe est disponible ! Appuyer pour télécharger Terminé En attente From add08ead149afc1c31328eb767ba3d40b782de6a Mon Sep 17 00:00:00 2001 From: wb9688 Date: Mon, 2 Mar 2020 17:58:48 +0100 Subject: [PATCH 049/663] Accept music.youtube.com in manifest --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7ca04eed0..d0e204137 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -146,6 +146,7 @@ + From e7063b2c69fbfed7b28ccd8e8d0732b2b1ccef8f Mon Sep 17 00:00:00 2001 From: KOK ASiiK Date: Mon, 2 Mar 2020 02:00:34 +0000 Subject: [PATCH 050/663] Translated using Weblate (Indonesian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-in/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 40a4e65a9..450076b35 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -395,7 +395,7 @@ Nihil Minimalkan ke pemutar latar belakang Minimalkan ke pemutar popup - Henti subscribe + Unsubscribe Tab Baru Pilih Tab Tema From 667a52427e8b8838bb24b7cdb2369eabd58b0c8a Mon Sep 17 00:00:00 2001 From: Dani Pragustia Date: Mon, 2 Mar 2020 17:55:47 +0000 Subject: [PATCH 051/663] Translated using Weblate (Indonesian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-in/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 450076b35..4712a91d1 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -228,7 +228,7 @@ Geser untuk ubah urutan Tidak ada subscriber - %s subscriber + %s subscribers Belum ditonton @@ -304,7 +304,7 @@ Diplaylist Thumbnail playlist diubah. Tidak bisa menghapus playlist. - Tidak ada Takarir + Tanpa Teks Pas Isi Perbesar @@ -339,7 +339,7 @@ Apakah anda juga ingin mengimpor pengaturan\? Tindakan \'buka\' yang diinginkan Tindakan baku ketika membuka konten — %s - Takarir + Teks Ubah skala teks takarir pemutar dan gaya latar belakang. Perlu memulai ulang apl. Pemantauan kebocoran memori dapat menyebabkan apl menjadi tidak responsif saat heap dumping Laporkan galat out-of-lifecycle From 92f4010e8ee16f0ebebad91e9d217fa20e2e0418 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 2 Mar 2020 20:50:35 +0100 Subject: [PATCH 052/663] Add more checks to prevent build failures in gradle branch suffix - Add function `getGitWorkingBranch` that returns the current working branch, and "" if it could not be determined (either because git is not installed or because the directory is not a git repo). - Make sure normalizedWorkingBranch is not empty (leading to an invalid app id terminating with `.`) - Make normalizedWorkingBranch lowercase - Add comments --- app/build.gradle | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2329a7a0e..61929173e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,24 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' + +static String getGitWorkingBranch() { + try { + def gitProcess = "git rev-parse --abbrev-ref HEAD".execute() + gitProcess.waitFor() + if (gitProcess.exitValue() == 0) { + return gitProcess.text.trim() + } else { + // not a git repository + return "" + } + } catch (IOException ignored) { + // git was not found + return "" + } +} + + android { compileSdkVersion 28 buildToolsVersion '28.0.3' @@ -31,12 +49,14 @@ android { debuggable true // suffix the app id and the app name with git branch name - def workingBranch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() - if (workingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") { + def workingBranch = getGitWorkingBranch() + def normalizedWorkingBranch = workingBranch.replaceAll("[^A-Za-z]+", "").toLowerCase() + if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") { + // default values when branch name could not be determined or is master or dev applicationIdSuffix ".debug" resValue "string", "app_name", "NewPipe Debug" } else { - applicationIdSuffix ".debug." + workingBranch.replaceAll("[^A-Za-z]+", "") + applicationIdSuffix ".debug." + normalizedWorkingBranch resValue "string", "app_name", "NewPipe " + workingBranch } } From 07d1faf544037de0178b368ece3ee2dd964557de Mon Sep 17 00:00:00 2001 From: bopol Date: Wed, 12 Feb 2020 02:05:34 +0100 Subject: [PATCH 053/663] Links support for mediaccc and shortened invidious --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2a7e039b3..4bc36a4c7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.TeamNewPipe:NewPipeExtractor:6446abc6d' + implementation 'com.github.B0pol:NewPipeExtractor:6180226' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7ca04eed0..bd3724770 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -247,6 +247,7 @@ + @@ -277,8 +278,26 @@ - + + + + + + + + + + + + + + + + + + + From 124340175a81f8aa244fac41f51bce4d40760b00 Mon Sep 17 00:00:00 2001 From: bopol Date: Wed, 12 Feb 2020 20:41:59 +0100 Subject: [PATCH 054/663] remove redundant code --- app/src/main/AndroidManifest.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bd3724770..8dfeb03cc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -244,15 +244,7 @@ - - - - - - - - From afebd9b724da0c090319d8fba53baa2f791df9f9 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+poolitzer@users.noreply.github.com> Date: Mon, 2 Mar 2020 16:38:23 -0800 Subject: [PATCH 055/663] improvements --- .github/ISSUE_TEMPLATE/bug_report.md | 3 +-- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 188dd8b42..1b28d3d79 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -14,8 +14,7 @@ Use this template to notify us if you found a bug. To make it easier for us to help you please enter detailed information below. -Please note, we only support the latest version of NewPipe and -master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in you app (To check you version, press on the three line menu on the left, click on "About", and you will see your current version). +Please note, we only support the latest version of NewPipe and master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start, make sure its version is the same as in your app (to check your version, open the left drawer and click on "About"). P.S.: Our [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be a nice document to read before you fill out the report :) --> diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 8453f99ba..a6262ad7b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -18,7 +18,7 @@ Example: *I think it would be nice if you would add feature Y so it will make it #### Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. -Example: *I considered Z, but that didn't work because...* +Example: *I considered Z, but that didn't turn out to be a good idea because...* #### Additional context Add any other context or screenshots about the feature request here. From 08dffad16055b3cd889a98548dbe6efbec87a348 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+poolitzer@users.noreply.github.com> Date: Mon, 2 Mar 2020 20:52:50 -0800 Subject: [PATCH 056/663] opus4improvements --- .github/ISSUE_TEMPLATE/bug_report.md | 16 ++++++++-------- .github/ISSUE_TEMPLATE/feature_request.md | 8 ++++---- .github/PULL_REQUEST_TEMPLATE.md | 11 +++++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1b28d3d79..19e8a9fbe 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,23 +8,23 @@ assignees: '' --- ### Version - + - -### Steps to reproduce - +### Steps to reproduce the bug + Steps to reproduce the behavior: 1. Go to '...' 2. Press on '....' @@ -36,10 +36,10 @@ Tell us what you expected to happen. ### Actual behaviour Tell us what happens instead. -### Screenshots/-recording -If applicable, add screenshots or a screen recording to help explain your problem. Github should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from a image/video hoster here instead. +### Screenshots/Screen records +If applicable, add screenshots or a screen recording to help explain your problem. Github should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from an image/video hoster here instead. ### Logs If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it here: - + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a6262ad7b..b461675bd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,13 +8,13 @@ assignees: '' --- -#### Is your feature request related to a problem? Please describe. +#### Is your feature request related to a problem? Please describe it A clear and concise description of what the problem is. Example: *I want to do X, but there is no way to do it.* #### Describe the solution you'd like A clear and concise description of what you want to happen. -Example: *I think it would be nice if you would add feature Y so it will make it easier.* +Example: *I think it would be nice if you add feature Y which makes X possible.* #### Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. @@ -24,7 +24,7 @@ Example: *I considered Z, but that didn't turn out to be a good idea because...* Add any other context or screenshots about the feature request here. Example: *Here's a photo of my cat!* -#### Why do you/everyone wants this feature +#### How will you/everyone benefit from this feature? Convince us! How does it change your NewPipe experience and/or your life? -The better this paragraph is, the more likely a developer will think about developing it. +The better this paragraph is, the more likely a developer will think about working on it. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 978adc100..40dd5d616 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,14 +1,17 @@ - + #### What is it? - [ ] Bug fix - [ ] Feature #### Long description of the changes in your PR -*Now you can make videos* + +- record videos +- create clones +- take over the world #### Fixes the following issue(s) - + - #### Relies on the following changes @@ -16,7 +19,7 @@ - #### Testing apk - + debug.zip #### Agreement From d265382ddfcf4b45482b0548715544f9664c239b Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Mon, 2 Mar 2020 20:56:03 -0800 Subject: [PATCH 057/663] missed this because GitHub thought its funny to hide it for a reason. Co-Authored-By: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/feature_request.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index b461675bd..946bfb4c6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -16,7 +16,7 @@ Example: *I want to do X, but there is no way to do it.* A clear and concise description of what you want to happen. Example: *I think it would be nice if you add feature Y which makes X possible.* -#### Describe alternatives you've considered +#### (Optional) Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. Example: *I considered Z, but that didn't turn out to be a good idea because...* @@ -27,4 +27,3 @@ Example: *Here's a photo of my cat!* #### How will you/everyone benefit from this feature? Convince us! How does it change your NewPipe experience and/or your life? The better this paragraph is, the more likely a developer will think about working on it. - From 3f118a72392247579867eb12df8632aaad4bf427 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Mon, 2 Mar 2020 21:08:34 -0800 Subject: [PATCH 058/663] appending dots Co-Authored-By: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 40dd5d616..9a1193767 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,11 +15,11 @@ - #### Relies on the following changes - + - #### Testing apk - + debug.zip #### Agreement From 9b65b000dbd2bdcff3ad381e10088462b3e6d84a Mon Sep 17 00:00:00 2001 From: Dani Pragustia Date: Mon, 2 Mar 2020 17:59:04 +0000 Subject: [PATCH 059/663] Translated using Weblate (Indonesian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-in/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 4712a91d1..2efe3e012 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -395,7 +395,7 @@ Nihil Minimalkan ke pemutar latar belakang Minimalkan ke pemutar popup - Unsubscribe + Berhenti berlangganan Tab Baru Pilih Tab Tema From e421d47b231e0226a616607c4e946c1607ebd87c Mon Sep 17 00:00:00 2001 From: IQBAL AL FATAH Date: Wed, 4 Mar 2020 08:55:04 +0000 Subject: [PATCH 060/663] Translated using Weblate (Indonesian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-in/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 2efe3e012..050134c6a 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -5,13 +5,13 @@ Dipublikasikan tanggal %1$s Pasang Batal - Buka di peramban + Buka di browser Bagikan Unduh - Cari - Pengaturan + Telusuri + Setelan Bagikan dengan - Pilih peramban + Pilih browser Gunakan pemutar video eksternal Gunakan pemutar audio eksternal Folder unduhan video @@ -46,7 +46,7 @@ Tampilkan video yang dibatasi usia. Bisa diubah nanti dari pengaturan. Galat jaringan Tidak bisa memuat semua thumbnail - Maksud anda: %1$s\? + Apakah yang kamu maksud: %1$s\? rotasi Langsung Unduhan @@ -116,7 +116,7 @@ T Ya Nanti - Buka di mode popup + Buka dalam mode popup Izin ini dibutuhkan untuk \nmembuka di mode popup Mode popup From e1fb8831deacfcf3287ecfc039aed71c6f997889 Mon Sep 17 00:00:00 2001 From: AioiLight Date: Tue, 3 Mar 2020 08:26:56 +0000 Subject: [PATCH 061/663] Translated using Weblate (Japanese) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-ja/strings.xml | 32 +++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index adaaa3828..f49c5b604 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1,7 +1,7 @@ %1$s に公開 - 動画プレイヤーが見つかりません。VLC を入手しますか? + 動画プレイヤーが見つかりません。VLC をインストールしますか? 入手 キャンセル ブラウザで開く @@ -18,7 +18,7 @@ 動画ファイルをダウンロードするフォルダーを選択して下さい デフォルトの解像度 Kodi で再生 - Koreが見つかりません。Kore を入手しますか? + Kore をインストールしますか? \"Kodi で再生\" オプションを表示 Kodi メディアセンター経由で動画を再生するための設定を表示します 音声 @@ -75,13 +75,13 @@ 動画 音声 再試行 - ストレージへのアクセスが拒否されました + 初めにストレージへのアクセスを許可する 自動再生 NewPipe が他のアプリから呼び出された時、動画を再生します。 不具合を報告 利用者レポートを送る 生放送 - 開始するには検索をタップ + 開始するには \"検索\" をタップ 開始 一時停止 再生 @@ -120,13 +120,13 @@ ポップアップモードで開く ポップアップモードで開くには \n権限の許可が必要です - NewPipe ポップアップモード + ポップアップモード ポップアップモードで再生中 無効 デフォルトの動画形式 デフォルトのポップアップ解像度 高い解像度で表示 - 2K/4K ビデオの再生は一部のデバイスのみサポートしています + 2K/4K ビデオの再生は一部のデバイスのみ再生できます バックグラウンド ポップアップ フィルター @@ -170,10 +170,10 @@ 検索した履歴を記憶します 視聴履歴 再生した履歴を記憶します - オーディオフォーカス復帰で再開する + 再生の再開 電話などによる中断の後、再生を再開します プレイヤー - 動画の詳細ページで、背景またはポップアップボタンが押されたときにヒントを表示する + 動画の詳細ページで、\"バックグラウンド\" または \"ポップアップ\" ボタンが押されたときにヒントを表示する 動作 履歴とキャッシュ プレイリスト @@ -325,12 +325,12 @@ おおまかなシーク おおまかなシークを使用すると、正確さが下がりますが高速なシークが可能になります すべてのサムネイルの読み込みと保存を無効化します、このオプションを切り替えるとメモリおよびディスク上の画像キャッシュがクリアされます。 - 繰り返しではないキューの再生後、関連動画を自動的にキューに追加します + キューに関連動画を追加し続けて、再生を続ける(リピートしない場合) すべての再生履歴を削除しますか? すべての検索履歴を削除しますか? このファイル/コンテンツはありません - %s を登録しています + %s が登録しています 視聴なし @@ -375,7 +375,7 @@ 何もしない バックグラウンドに変更 ポップアップに変更 - LeakCanary を有効にする + LeakCanary メモリリークの監視は、ヒープダンピング時にアプリが無反応になる原因となります ライフサイクルエラーの報告 破棄されたフラグメントまたはアクティビティの、ライフサイクル範囲外での配信不能なRx例外を強制的に報告します @@ -453,7 +453,7 @@ ファイルを削除しました アプリの更新通知 外部 SD カードにダウンロードできません。ダウンロードフォルダーの場所をリセットしますか\? - デフォルトのタブを使用します。保存されたタブの読み込みエラーが発生しました + 保存されたタブを読み込めないため、デフォルトのタブを使用します メインページに表示されるタブ 新しいバージョンが利用可能なときにアプリの更新を確認する通知を表示します 従量制課金ネットワークの割り込み @@ -462,7 +462,7 @@ 無効にするとコメントの表示を停止します 自動再生 - コメント + %s コメント コメントはありません コメントを読み込めませんでした @@ -525,7 +525,7 @@ 修復中 ダウンロードが修復できません インスタンスを選択 - ロック画面の動画サムネイルを有効にする + ロック画面の動画サムネイル バックグラウンドプレイヤーを使用中、ロック画面に動画のサムネイルが表示されるようになります ダウンロード履歴を消去 ダウンロードしたファイルを消去 @@ -536,4 +536,8 @@ システムの既定 解けたら \"完了\" を押してください 完了 + 動画 + + %s 秒 + \ No newline at end of file From 6962882e75e839a58c12ef874d0e574f942fb205 Mon Sep 17 00:00:00 2001 From: Sylke Vicious Date: Tue, 3 Mar 2020 14:55:44 +0000 Subject: [PATCH 062/663] Translated using Weblate (Italian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-it/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2607ab210..d36d03a61 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -218,13 +218,13 @@ Feed Iscrizioni Canale Personalizzato Seleziona Canale - Nessuna Iscrizione + Ancora nessuna iscrizione ad un canale Seleziona Contenuto Locandina Tendenze Top 50 New & hot - Mostra Suggerimento \"Tieni Premuto per Accocodare\" + Mostra suggerimento \"Tieni premuto per accodare\" Nei \"Dettagli\" dei video, mostra suggerimento alla pressione dei pulsanti per la riproduzione Popup o in Sottofondo Accoda in Sottofondo Accodato in Popup @@ -352,7 +352,7 @@ Tieni presente che questa operazione può consumare una grande quantità di traffico dati. \n \nVuoi continuare? - Carica Copertine + Carica miniature Disabilita per prevenire il caricamento delle anteprime, risparmiando dati e memoria. La modifica di questa opzione cancellerà la cache delle immagini in memoria e sul disco. Cache immagini svuotata Pulisci Cache Metadati @@ -533,8 +533,8 @@ recupero Impossibile recuperare questo download Scegli un\'Istanza - Copertina sulla Schermata di Blocco - La copertina del video verrà mostrata nella schermata di blocco, durante la riproduzione in sottofondo. + Miniatura del video sulla schermata di blocco + La miniatura del video verrà mostrata nella schermata di blocco, durante la riproduzione in sottofondo Svuota Cronologia Download Elimina File Scaricati %1$s download eliminati @@ -546,6 +546,6 @@ Video %s secondi - + \ No newline at end of file From ebdf48899fd0a8a3555bd3415fdb3fd11bc9d121 Mon Sep 17 00:00:00 2001 From: "Mohd. A" <1mohd@pm.me> Date: Tue, 3 Mar 2020 09:52:17 +0000 Subject: [PATCH 063/663] Translated using Weblate (Arabic) Currently translated at 98.3% (524 of 533 strings) --- app/src/main/res/values-ar/strings.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 469c13177..0407e1836 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -94,7 +94,7 @@ تذكر حجم النافذة و وضعها تذكر آخر مكان و حجم للنافذة المنبثقة اعدادات إيماءة المشغل - استخدم إيماءات التحكم في سطوع وصوت المشغل + استخدم الإيماءات للتحكم في سطوع وصوت المشغل اقتراحات البحث عرض الاقتراحات عند البحث سجل البحث @@ -111,7 +111,7 @@ تم وضعه على قائمة الانتظار في مشغل الخلفية تم وضعه على قائمة الانتظار في مشغل النافذة المنبثقة محتوى مقيد بحسب العمر - "إظهار الفيديو المقيد بحسب العمر. يمكن السماح باستخدام هذه المواد من \"الإعدادات\"." + إظهار الفيديو المقيد بحسب العمر. التغييرات المستقبلية ممكنة من \"الإعدادات\". بث مباشر تقرير خطأ قائمة التشغيل @@ -205,7 +205,7 @@ إذا كانت لديك أفكار؛ أو ترجمة، أو تغييرات تخص التصميم، أو تنظيف و تحسين الشفرة البرمجية ، أو تعديلات عميقة عليها، فتذكر أنّ مساعدتك دائما موضع ترحيب. وكلما أتممنا شيئا كلما كان ذلك أفضل ! عرض على GitHub تبرع - يتم تطوير NewPipe من قبل متطوعين يقضون وقت فراغهم لتقديم أفضل تجربة لك. حان الوقت لرد المساعدة مع المطورين وجعل NewPipe أكثر و أفضل بينما تستمتع بفنجان من القهوة. + يتم تطوير NewPipe من قبل متطوعين يقضون وقت فراغهم لتقديم أفضل تجربة لك. حان الوقت لرد المساعدة مع المطورين وجعل NewPipe أكثر و أفضل بينما يستمتعون بفنجان من القهوة. تبرع موقع الويب قم بزيارة موقع NewPipe لمزيد من المعلومات والمستجدات. @@ -417,7 +417,7 @@ إلغاء الاشتراك علامة تبويب جديدة اختر علامة التبويب - استخدم إيماءات التحكم في سطوع وصوت المشغل + استخدم إيماءات التحكم في صوت المشغل التحكم بالإيماءات السطوع استخدام الإيماءات للتحكم في سطوع المشغل التحديثات @@ -463,7 +463,7 @@ لا يمكن إنشاء الملف لا يمكن إنشاء المجلد الوجهة تم رفضها من قبل النظام - فشل اتصال الأمن + فشل الاتصال الآمن تعذر العثور على الخادم لا يمكن الاتصال بالخادم الخادم لايقوم بإرسال البيانات @@ -556,8 +556,8 @@ لا يمكن استرداد هذا التنزيل اختيار مثيل ابحث عن مثيلات الخوادم التي تناسبك على %s - تمكين قفل شاشة الصور المصغرة الفيديو - عند استخدام مشغل الخلفية ، سيتم عرض صورة مصغرة للفيديو على شاشة القفل + تمكين صورة العرض للفيديو في شاشة القفل + عند استخدام مشغل الخلفية، سيتم عرض صورة العرض للفيديو على شاشة القفل تنظيف تاريخ التحميل حذف الملفات التي تم تنزيلها التنزيلات %1$s المحذوفة @@ -569,10 +569,10 @@ الفيديوهات %s ثوانٍ - - - - - + + + + + \ No newline at end of file From 371280ff766b0335b70acb12005ab27279c3f5eb Mon Sep 17 00:00:00 2001 From: IQBAL AL FATAH Date: Wed, 4 Mar 2020 09:09:51 +0000 Subject: [PATCH 064/663] Translated using Weblate (Indonesian) Currently translated at 100.0% (533 of 533 strings) --- app/src/main/res/values-in/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 050134c6a..557451220 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -395,7 +395,7 @@ Nihil Minimalkan ke pemutar latar belakang Minimalkan ke pemutar popup - Berhenti berlangganan + Berhenti Subscribe Tab Baru Pilih Tab Tema From 5c559e4cc6172d1a99f0d944cd759cf5da486593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20Holmstr=C3=B6m?= Date: Wed, 4 Mar 2020 05:31:01 +0000 Subject: [PATCH 065/663] Translated using Weblate (Swedish) Currently translated at 86.8% (463 of 533 strings) --- app/src/main/res/values-sv/strings.xml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 68aeca87c..5290517ab 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -18,7 +18,7 @@ Använd extern videospelare Tar bort ljud vid VISSA upplösningar Använd extern ljudspelare - NewPipe popup-läge + Popup-läge Bakgrund Extrafönster Mapp för nerladdning av video @@ -112,7 +112,7 @@ Spara sökfrågor lokalt Visningshistorik Håll koll på videor som du tittat på - Återuppta när fokus återfås + Återuppta spelning Fortsätta spela efter avbrott (t.ex. telefonsamtal) Visa \"Håll för att lägga till\" tips Visa tips när bakgrunds- eller popup-knappen trycks på sidan för videodetaljer @@ -458,7 +458,7 @@ Kommentarer - Inaktivera för att sluta visa kommentarer + Inaktivera för att inte visa kommentarer Återuppta uppspelning Återställ den senaste uppspelningspositionen Positioner i listor @@ -468,4 +468,15 @@ Snabb spola -framåt/-bakåt Aktivera video på låsskärmen När bakgrundsspelaren används så visas videon på låsskärmen + Visa positionindikationer i listor + Radera uppspelningspositioner + PeerTube-instanser + Välj din favorit PeerTube-instans + Hitta instanser du gillar på %s + Lägg till instans + Fyll i instans-URL + Kunde inte validera instans + Enbart HTTPS-URL stöds + Instansen finns redan + Videos \ No newline at end of file From 2ca580dc1639d9eb7e4c87600dbacc8bce22a16c Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Wed, 4 Mar 2020 20:21:44 -0800 Subject: [PATCH 066/663] minor improvements of sentences Co-Authored-By: Stypox --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 19e8a9fbe..0137335e4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -14,7 +14,7 @@ Use this template to notify us if you found a bug. To make it easier for us to help you please enter detailed information below. -Please note, we only support the latest version of NewPipe and the master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start. Make sure its version is the same as in your app (to check your version, open the left drawer and click on "About"). +Please note, we only support the latest version of NewPipe and the master branch. Make sure to upgrade & reproduce the problem on the latest version before opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start. Make sure its version is the same as in your app (to check your version, open the left drawer and click on "About"). P.S.: Our [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be a nice document to read before you fill out the report :) --> @@ -24,7 +24,7 @@ P.S.: Our [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/ ### Steps to reproduce the bug - + Steps to reproduce the behavior: 1. Go to '...' 2. Press on '....' From 4e37a762d2fa4336f804e1f0a57743751dffd0d3 Mon Sep 17 00:00:00 2001 From: poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Wed, 4 Mar 2020 20:31:36 -0800 Subject: [PATCH 067/663] Further minor improvements --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0137335e4..85a058344 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -14,7 +14,7 @@ Use this template to notify us if you found a bug. To make it easier for us to help you please enter detailed information below. -Please note, we only support the latest version of NewPipe and the master branch. Make sure to upgrade & reproduce the problem on the latest version before opening an issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is a good start. Make sure its version is the same as in your app (to check your version, open the left drawer and click on "About"). +Please note, we only support the latest version of NewPipe and the master branch. Make sure you have that version installed. If you don't, upgrade & reproduce the problem before opening the issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is the go-to place to get this version. In order to check your app version, open the left drawer and click on "About". P.S.: Our [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be a nice document to read before you fill out the report :) --> From ecb1b45280951599c636d7600af95f5d89570172 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 7 Mar 2020 15:55:55 -0300 Subject: [PATCH 068/663] Fix visual glitch when exiting the app --- .../main/res/values-v21/styles_services.xml | 24 +++++++++---------- app/src/main/res/values/styles.xml | 17 ------------- app/src/main/res/values/styles_services.xml | 24 +++++++++---------- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/app/src/main/res/values-v21/styles_services.xml b/app/src/main/res/values-v21/styles_services.xml index 176bc1f51..1c725f887 100644 --- a/app/src/main/res/values-v21/styles_services.xml +++ b/app/src/main/res/values-v21/styles_services.xml @@ -1,69 +1,69 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml index 28490d7c6..013690b44 100644 --- a/app/src/main/res/values/styles_services.xml +++ b/app/src/main/res/values/styles_services.xml @@ -1,31 +1,31 @@ - - - - - - - - - - - - @@ -127,15 +165,53 @@ @drawable/ic_pause_white_24dp @drawable/ic_settings_update_white @drawable/ic_done_white_24dp + @drawable/ic_refresh_white_24dp + @drawable/ic_computer_white_24dp + @drawable/ic_videogame_white_24dp + @drawable/ic_music_note_white_24dp + @drawable/ic_stars_white_24dp + @drawable/ic_sports_white_24dp + @drawable/ic_money_white_24dp + @drawable/ic_person_white_24dp + @drawable/ic_people_white_24dp + @drawable/ic_heart_white_24dp + @drawable/ic_kids_white_24dp + @drawable/ic_fastfood_white_24dp + @drawable/ic_car_white_24dp + @drawable/ic_motorcycle_white_24dp + @drawable/ic_trending_up_white_24dp + @drawable/ic_school_white_24dp + @drawable/ic_asterisk_white_24dp + @drawable/ic_emoticon_white_24dp + @drawable/ic_edit_white_24dp + @drawable/ic_explore_white_24dp + @drawable/ic_fitness_white_24dp + @drawable/ic_restaurant_white_24dp + @drawable/ic_mic_white_24dp + @drawable/ic_radio_white_24dp + @drawable/ic_shopping_cart_white_24dp + @drawable/ic_watch_later_white_24dp + @drawable/ic_work_white_24dp + @drawable/ic_movie_white_24dp + @drawable/ic_pets_white_24dp + @drawable/ic_world_white_24dp + @drawable/ic_sunny_white_24dp + @drawable/ic_telescope_white_24dp + @drawable/ic_megaphone_white_24dp @color/dark_separator_color @color/dark_contrast_background_color @drawable/dark_checked_selector + @drawable/dark_focused_selector @color/dark_queue_background_color @drawable/toolbar_shadow_dark @drawable/dark_selector @color/dark_ripple_color @drawable/progress_youtube_horizontal_dark + @color/dark_card_item_background_color + @color/dark_card_item_contrast_color + @color/dark_border_color + @drawable/dashed_border_dark @style/PreferenceThemeOverlay.v14.Material @@ -148,6 +224,11 @@ @color/black_separator_color @color/black_contrast_background_color + + @color/black_card_item_background_color + @color/black_card_item_contrast_color + @color/black_border_color + @drawable/dashed_border_black @@ -167,6 +248,22 @@ @color/dark_dialog_background_color + + + + - - " + insert[1]; } catch (Exception e) { - throw new NullPointerException("could not get license file:" + getLicenseStylesheet(context)); + throw new NullPointerException("could not get license file:" + + getLicenseStylesheet(context)); } return webViewData; } /** - * * @param context * @return String which is a CSS stylesheet according to the context's theme */ - public static String getLicenseStylesheet(Context context) { + public static String getLicenseStylesheet(final Context context) { boolean isLightTheme = ThemeHelper.isLightThemeSelected(context); return "body{padding:12px 15px;margin:0;background:#" + getHexRGBColor(context, isLightTheme - ? R.color.light_license_background_color - : R.color.dark_license_background_color) + ? R.color.light_license_background_color + : R.color.dark_license_background_color) + ";color:#" + getHexRGBColor(context, isLightTheme - ? R.color.light_license_text_color - : R.color.dark_license_text_color) + ";}" + ? R.color.light_license_text_color + : R.color.dark_license_text_color) + ";}" + "a[href]{color:#" + getHexRGBColor(context, isLightTheme - ? R.color.light_youtube_primary_color - : R.color.dark_youtube_primary_color) + ";}" + ? R.color.light_youtube_primary_color + : R.color.dark_youtube_primary_color) + ";}" + "pre{white-space: pre-wrap;}"; } /** - * Cast R.color to a hexadecimal color value + * Cast R.color to a hexadecimal color value. + * * @param context the context to use - * @param color the color number from R.color + * @param color the color number from R.color * @return a six characters long String with hexadecimal RGB values */ - public static String getHexRGBColor(Context context, int color) { + public static String getHexRGBColor(final Context context, final int color) { return context.getResources().getString(color).substring(3); } + @Nullable + private Activity getActivity() { + Activity activity = weakReference.get(); + + if (activity != null && activity.isFinishing()) { + return null; + } else { + return activity; + } + } + + @Override + protected Integer doInBackground(final Object... objects) { + license = (License) objects[0]; + return 1; + } + + @Override + protected void onPostExecute(final Integer result) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + String webViewData = getFormattedLicense(activity, license); + AlertDialog.Builder alert = new AlertDialog.Builder(activity); + alert.setTitle(license.getName()); + + WebView wv = new WebView(activity); + wv.loadData(webViewData, "text/html; charset=UTF-8", null); + + alert.setView(wv); + assureCorrectAppLanguage(activity.getApplicationContext()); + alert.setNegativeButton(getFinishString(activity), (dialog, which) -> dialog.dismiss()); + alert.show(); + } + } diff --git a/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.java b/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.java index edab3e174..946945142 100644 --- a/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.java +++ b/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.java @@ -4,39 +4,18 @@ import android.os.Parcelable; public class SoftwareComponent implements Parcelable { - public static final Creator CREATOR = new Creator() { @Override - public SoftwareComponent createFromParcel(Parcel source) { + public SoftwareComponent createFromParcel(final Parcel source) { return new SoftwareComponent(source); } @Override - public SoftwareComponent[] newArray(int size) { + public SoftwareComponent[] newArray(final int size) { return new SoftwareComponent[size]; } }; - public String getName() { - return name; - } - - public String getYears() { - return years; - } - - public String getCopyrightOwner() { - return copyrightOwner; - } - - public String getLink() { - return link; - } - - public String getVersion() { - return version; - } - private final License license; private final String name; private final String years; @@ -44,7 +23,8 @@ public String getVersion() { private final String link; private final String version; - public SoftwareComponent(String name, String years, String copyrightOwner, String link, License license) { + public SoftwareComponent(final String name, final String years, final String copyrightOwner, + final String link, final License license) { this.name = name; this.years = years; this.copyrightOwner = copyrightOwner; @@ -53,7 +33,7 @@ public SoftwareComponent(String name, String years, String copyrightOwner, Strin this.version = null; } - protected SoftwareComponent(Parcel in) { + protected SoftwareComponent(final Parcel in) { this.name = in.readString(); this.license = in.readParcelable(License.class.getClassLoader()); this.copyrightOwner = in.readString(); @@ -62,6 +42,26 @@ protected SoftwareComponent(Parcel in) { this.version = in.readString(); } + public String getName() { + return name; + } + + public String getYears() { + return years; + } + + public String getCopyrightOwner() { + return copyrightOwner; + } + + public String getLink() { + return link; + } + + public String getVersion() { + return version; + } + public License getLicense() { return license; } @@ -72,7 +72,7 @@ public int describeContents() { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(final Parcel dest, final int flags) { dest.writeString(name); dest.writeParcelable(license, flags); dest.writeString(copyrightOwner); diff --git a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java index 00a479336..75a7a8613 100644 --- a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java +++ b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java @@ -1,12 +1,19 @@ package org.schabi.newpipe.about; /** - * Standard software licenses + * Class containing information about standard software licenses. */ public final class StandardLicenses { - public static final License GPL2 = new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html"); - public static final License GPL3 = new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html"); - public static final License APACHE2 = new License("Apache License, Version 2.0", "ALv2", "apache2.html"); - public static final License MPL2 = new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html"); - public static final License MIT = new License("MIT License", "MIT", "mit.html"); + public static final License GPL2 + = new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html"); + public static final License GPL3 + = new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html"); + public static final License APACHE2 + = new License("Apache License, Version 2.0", "ALv2", "apache2.html"); + public static final License MPL2 + = new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html"); + public static final License MIT + = new License("MIT License", "MIT", "mit.html"); + + private StandardLicenses() { } } diff --git a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java index d3cd6eb80..3b5bda155 100644 --- a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java +++ b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java @@ -46,14 +46,20 @@ public abstract class AppDatabase extends RoomDatabase { public abstract SearchHistoryDAO searchHistoryDAO(); public abstract StreamDAO streamDAO(); + public abstract StreamHistoryDAO streamHistoryDAO(); + public abstract StreamStateDAO streamStateDAO(); public abstract PlaylistDAO playlistDAO(); + public abstract PlaylistStreamDAO playlistStreamDAO(); + public abstract PlaylistRemoteDAO playlistRemoteDAO(); public abstract FeedDAO feedDAO(); + public abstract FeedGroupDAO feedGroupDAO(); + public abstract SubscriptionDAO subscriptionDAO(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java index b7381b9f1..bcb9ece10 100644 --- a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java @@ -15,13 +15,13 @@ public interface BasicDAO { /* Inserts */ @Insert(onConflict = OnConflictStrategy.FAIL) - long insert(final Entity entity); + long insert(Entity entity); @Insert(onConflict = OnConflictStrategy.FAIL) - List insertAll(final Entity... entities); + List insertAll(Entity... entities); @Insert(onConflict = OnConflictStrategy.FAIL) - List insertAll(final Collection entities); + List insertAll(Collection entities); /* Searches */ Flowable> getAll(); @@ -30,17 +30,17 @@ public interface BasicDAO { /* Deletes */ @Delete - void delete(final Entity entity); + void delete(Entity entity); @Delete - int delete(final Collection entities); + int delete(Collection entities); int deleteAll(); /* Updates */ @Update - int update(final Entity entity); + int update(Entity entity); @Update - void update(final Collection entities); + void update(Collection entities); } diff --git a/app/src/main/java/org/schabi/newpipe/database/Converters.java b/app/src/main/java/org/schabi/newpipe/database/Converters.java index 2f510c8ec..e1a2fe2f3 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Converters.java +++ b/app/src/main/java/org/schabi/newpipe/database/Converters.java @@ -7,47 +7,52 @@ import java.util.Date; -public class Converters { +public final class Converters { + private Converters() { } /** - * Convert a long value to a date + * Convert a long value to a date. + * * @param value the long value * @return the date */ @TypeConverter - public static Date fromTimestamp(Long value) { + public static Date fromTimestamp(final Long value) { return value == null ? null : new Date(value); } /** - * Convert a date to a long value + * Convert a date to a long value. + * * @param date the date * @return the long value */ @TypeConverter - public static Long dateToTimestamp(Date date) { + public static Long dateToTimestamp(final Date date) { return date == null ? null : date.getTime(); } @TypeConverter - public static StreamType streamTypeOf(String value) { + public static StreamType streamTypeOf(final String value) { return StreamType.valueOf(value); } @TypeConverter - public static String stringOf(StreamType streamType) { + public static String stringOf(final StreamType streamType) { return streamType.name(); } @TypeConverter - public static Integer integerOf(FeedGroupIcon feedGroupIcon) { + public static Integer integerOf(final FeedGroupIcon feedGroupIcon) { return feedGroupIcon.getId(); } @TypeConverter - public static FeedGroupIcon feedGroupIconOf(Integer id) { + public static FeedGroupIcon feedGroupIconOf(final Integer id) { for (FeedGroupIcon icon : FeedGroupIcon.values()) { - if (icon.getId() == id) return icon; + if (icon.getId() == id) { + return icon; + } } throw new IllegalArgumentException("There's no feed group icon with the id \"" + id + "\""); diff --git a/app/src/main/java/org/schabi/newpipe/database/LocalItem.java b/app/src/main/java/org/schabi/newpipe/database/LocalItem.java index e121739ab..54b856b06 100644 --- a/app/src/main/java/org/schabi/newpipe/database/LocalItem.java +++ b/app/src/main/java/org/schabi/newpipe/database/LocalItem.java @@ -1,6 +1,8 @@ package org.schabi.newpipe.database; public interface LocalItem { + LocalItemType getLocalItemType(); + enum LocalItemType { PLAYLIST_LOCAL_ITEM, PLAYLIST_REMOTE_ITEM, @@ -8,6 +10,4 @@ enum LocalItemType { PLAYLIST_STREAM_ITEM, STATISTIC_STREAM_ITEM, } - - LocalItemType getLocalItemType(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java index afefb2fd1..088b9ed19 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java +++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java @@ -1,72 +1,103 @@ package org.schabi.newpipe.database; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.room.migration.Migration; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.room.migration.Migration; +import androidx.sqlite.db.SupportSQLiteDatabase; + import org.schabi.newpipe.BuildConfig; -public class Migrations { +public final class Migrations { public static final int DB_VER_1 = 1; public static final int DB_VER_2 = 2; public static final int DB_VER_3 = 3; - public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); private static final String TAG = Migrations.class.getName(); + public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) { @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - if(DEBUG) { + public void migrate(@NonNull final SupportSQLiteDatabase database) { + if (DEBUG) { Log.d(TAG, "Start migrating database"); } /* - * Unfortunately these queries must be hardcoded due to the possibility of - * schema and names changing at a later date, thus invalidating the older migration - * scripts if they are not hardcoded. - * */ + * Unfortunately these queries must be hardcoded due to the possibility of + * schema and names changing at a later date, thus invalidating the older migration + * scripts if they are not hardcoded. + * */ // Not much we can do about this, since room doesn't create tables before migration. // It's either this or blasting the entire database anew. - database.execSQL("CREATE INDEX `index_search_history_search` ON `search_history` (`search`)"); - database.execSQL("CREATE TABLE IF NOT EXISTS `streams` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, `stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, `thumbnail_url` TEXT)"); - database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` ON `streams` (`service_id`, `url`)"); - database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); - database.execSQL("CREATE INDEX `index_stream_history_stream_id` ON `stream_history` (`stream_id`)"); - database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); - database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)"); + database.execSQL("CREATE INDEX `index_search_history_search` " + + "ON `search_history` (`search`)"); + database.execSQL("CREATE TABLE IF NOT EXISTS `streams` " + + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, " + + "`stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, " + + "`thumbnail_url` TEXT)"); + database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` " + + "ON `streams` (`service_id`, `url`)"); + database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` " + + "(`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, " + + "`repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), " + + "FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) " + + "ON UPDATE CASCADE ON DELETE CASCADE )"); + database.execSQL("CREATE INDEX `index_stream_history_stream_id` " + + "ON `stream_history` (`stream_id`)"); + database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` " + + "(`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, " + + "PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) " + + "REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )"); + database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` " + + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`name` TEXT, `thumbnail_url` TEXT)"); database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)"); - database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); - database.execSQL("CREATE UNIQUE INDEX `index_playlist_stream_join_playlist_id_join_index` ON `playlist_stream_join` (`playlist_id`, `join_index`)"); - database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` ON `playlist_stream_join` (`stream_id`)"); - database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)"); - database.execSQL("CREATE INDEX `index_remote_playlists_name` ON `remote_playlists` (`name`)"); - database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` ON `remote_playlists` (`service_id`, `url`)"); + database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` " + + "(`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, " + + "`join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), " + + "FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, " + + "FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); + database.execSQL("CREATE UNIQUE INDEX " + + "`index_playlist_stream_join_playlist_id_join_index` " + + "ON `playlist_stream_join` (`playlist_id`, `join_index`)"); + database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` " + + "ON `playlist_stream_join` (`stream_id`)"); + database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` " + + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, " + + "`thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)"); + database.execSQL("CREATE INDEX `index_remote_playlists_name` " + + "ON `remote_playlists` (`name`)"); + database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` " + + "ON `remote_playlists` (`service_id`, `url`)"); // Populate streams table with existing entries in watch history // Latest data first, thus ignoring older entries with the same indices - database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, " + - "stream_type, duration, uploader, thumbnail_url) " + + database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, " + + "stream_type, duration, uploader, thumbnail_url) " - "SELECT service_id, url, title, 'VIDEO_STREAM', duration, " + - "uploader, thumbnail_url " + + + "SELECT service_id, url, title, 'VIDEO_STREAM', duration, " + + "uploader, thumbnail_url " - "FROM watch_history " + - "ORDER BY creation_date DESC"); + + "FROM watch_history " + + "ORDER BY creation_date DESC"); // Once the streams have PKs, join them with the normalized history table // and populate it with the remaining data from watch history - database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" + - "SELECT uid, creation_date, 1 " + - "FROM watch_history INNER JOIN streams " + - "ON watch_history.service_id == streams.service_id " + - "AND watch_history.url == streams.url " + - "ORDER BY creation_date DESC"); + database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)" + + "SELECT uid, creation_date, 1 " + + "FROM watch_history INNER JOIN streams " + + "ON watch_history.service_id == streams.service_id " + + "AND watch_history.url == streams.url " + + "ORDER BY creation_date DESC"); database.execSQL("DROP TABLE IF EXISTS watch_history"); - if(DEBUG) { + if (DEBUG) { Log.d(TAG, "Stop migrating database"); } } @@ -74,37 +105,60 @@ public void migrate(@NonNull SupportSQLiteDatabase database) { public static final Migration MIGRATION_2_3 = new Migration(DB_VER_2, DB_VER_3) { @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { + public void migrate(@NonNull final SupportSQLiteDatabase database) { // Add NOT NULLs and new fields - database.execSQL("CREATE TABLE IF NOT EXISTS streams_new " + - "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, service_id INTEGER NOT NULL, url TEXT NOT NULL, title TEXT NOT NULL, stream_type TEXT NOT NULL," + - " duration INTEGER NOT NULL, uploader TEXT NOT NULL, thumbnail_url TEXT, view_count INTEGER, textual_upload_date TEXT, upload_date INTEGER," + - " is_upload_date_approximation INTEGER)"); + database.execSQL("CREATE TABLE IF NOT EXISTS streams_new " + + "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "service_id INTEGER NOT NULL, url TEXT NOT NULL, title TEXT NOT NULL, " + + "stream_type TEXT NOT NULL, duration INTEGER NOT NULL, " + + "uploader TEXT NOT NULL, thumbnail_url TEXT, view_count INTEGER, " + + "textual_upload_date TEXT, upload_date INTEGER, " + + "is_upload_date_approximation INTEGER)"); - database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type," + - "duration, uploader, thumbnail_url, view_count," + - "textual_upload_date, upload_date, is_upload_date_approximation) " + + database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type, " + + "duration, uploader, thumbnail_url, view_count, textual_upload_date, " + + "upload_date, is_upload_date_approximation) " - "SELECT uid, service_id, url, ifnull(title, ''), ifnull(stream_type, 'VIDEO_STREAM')," + - "ifnull(duration, 0), ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL," + - "NULL, NULL, NULL " + + + "SELECT uid, service_id, url, ifnull(title, ''), " + + "ifnull(stream_type, 'VIDEO_STREAM'), ifnull(duration, 0), " + + "ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL, NULL, NULL, NULL " - "FROM streams " + - "WHERE url IS NOT NULL"); + + "FROM streams WHERE url IS NOT NULL"); database.execSQL("DROP TABLE streams"); database.execSQL("ALTER TABLE streams_new RENAME TO streams"); - database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url ON streams (service_id, url)"); + database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url " + + "ON streams (service_id, url)"); // Tables for feed feature - database.execSQL("CREATE TABLE IF NOT EXISTS feed (stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(stream_id, subscription_id), FOREIGN KEY(stream_id) REFERENCES streams(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); + database.execSQL("CREATE TABLE IF NOT EXISTS feed " + + "(stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, " + + "PRIMARY KEY(stream_id, subscription_id), " + + "FOREIGN KEY(stream_id) REFERENCES streams(uid) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, " + + "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)"); - database.execSQL("CREATE TABLE IF NOT EXISTS feed_group (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)"); + database.execSQL("CREATE TABLE IF NOT EXISTS feed_group " + + "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, " + + "icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)"); database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)"); - database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join (group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(group_id, subscription_id), FOREIGN KEY(group_id) REFERENCES feed_group(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); - database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id ON feed_group_subscription_join (subscription_id)"); - database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated (subscription_id INTEGER NOT NULL, last_updated INTEGER, PRIMARY KEY(subscription_id), FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); + database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join " + + "(group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, " + + "PRIMARY KEY(group_id, subscription_id), " + + "FOREIGN KEY(group_id) REFERENCES feed_group(uid) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, " + + "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); + database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id " + + "ON feed_group_subscription_join (subscription_id)"); + database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated " + + "(subscription_id INTEGER NOT NULL, last_updated INTEGER, " + + "PRIMARY KEY(subscription_id), " + + "FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) " + + "ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)"); } }; + private Migrations() { } } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index df8094830..972435859 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -1,8 +1,8 @@ package org.schabi.newpipe.database.history.dao; +import androidx.annotation.Nullable; import androidx.room.Dao; import androidx.room.Query; -import androidx.annotation.Nullable; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; @@ -18,11 +18,10 @@ @Dao public interface SearchHistoryDAO extends HistoryDAO { - String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC"; - @Query("SELECT * FROM " + TABLE_NAME + - " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") + @Query("SELECT * FROM " + TABLE_NAME + + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") @Nullable SearchHistoryEntry getLatestEntry(); @@ -37,13 +36,16 @@ public interface SearchHistoryDAO extends HistoryDAO { @Override Flowable> getAll(); - @Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit") + @Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + + " LIMIT :limit") Flowable> getUniqueEntries(int limit); - @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) + @Query("SELECT * FROM " + TABLE_NAME + + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) @Override Flowable> listByService(int serviceId); - @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit") + @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'" + + " GROUP BY " + SEARCH + " LIMIT :limit") Flowable> getSimilarEntries(String query, int limit); } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java index 2703b9783..7daf21e6e 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java @@ -1,32 +1,31 @@ package org.schabi.newpipe.database.history.dao; - +import androidx.annotation.Nullable; import androidx.room.Dao; import androidx.room.Query; -import androidx.annotation.Nullable; +import org.schabi.newpipe.database.history.model.StreamHistoryEntity; import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; -import org.schabi.newpipe.database.history.model.StreamHistoryEntity; import java.util.List; import io.reactivex.Flowable; +import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; +import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; +import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT; import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE; import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT; import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID; import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE; -import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; -import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; -import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; @Dao public abstract class StreamHistoryDAO implements HistoryDAO { - @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + - " WHERE " + STREAM_ACCESS_DATE + " = " + - "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")") + @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + + " WHERE " + STREAM_ACCESS_DATE + " = " + + "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")") @Override @Nullable public abstract StreamHistoryEntity getLatestEntry(); @@ -40,33 +39,33 @@ public abstract class StreamHistoryDAO implements HistoryDAO> listByService(int serviceId) { + public Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } - @Query("SELECT * FROM " + STREAM_TABLE + - " INNER JOIN " + STREAM_HISTORY_TABLE + - " ON " + STREAM_ID + " = " + JOIN_STREAM_ID + - " ORDER BY " + STREAM_ACCESS_DATE + " DESC") + @Query("SELECT * FROM " + STREAM_TABLE + + " INNER JOIN " + STREAM_HISTORY_TABLE + + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID + + " ORDER BY " + STREAM_ACCESS_DATE + " DESC") public abstract Flowable> getHistory(); - @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + - " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") + @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + + " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") @Nullable - public abstract StreamHistoryEntity getLatestEntry(final long streamId); + public abstract StreamHistoryEntity getLatestEntry(long streamId); @Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") - public abstract int deleteStreamHistory(final long streamId); + public abstract int deleteStreamHistory(long streamId); - @Query("SELECT * FROM " + STREAM_TABLE + + @Query("SELECT * FROM " + STREAM_TABLE // Select the latest entry and watch count for each stream id on history table - " INNER JOIN " + - "(SELECT " + JOIN_STREAM_ID + ", " + - " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " + - " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT + - " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" + + + " INNER JOIN " + + "(SELECT " + JOIN_STREAM_ID + ", " + + " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " + + " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT + + " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" - " ON " + STREAM_ID + " = " + JOIN_STREAM_ID) + + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID) public abstract Flowable> getStatistics(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.java b/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.java index 222ef0a59..752835182 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.java @@ -13,7 +13,6 @@ @Entity(tableName = SearchHistoryEntry.TABLE_NAME, indices = {@Index(value = SEARCH)}) public class SearchHistoryEntry { - public static final String ID = "id"; public static final String TABLE_NAME = "search_history"; public static final String SERVICE_ID = "service_id"; @@ -33,7 +32,7 @@ public class SearchHistoryEntry { @ColumnInfo(name = SEARCH) private String search; - public SearchHistoryEntry(Date creationDate, int serviceId, String search) { + public SearchHistoryEntry(final Date creationDate, final int serviceId, final String search) { this.serviceId = serviceId; this.creationDate = creationDate; this.search = search; @@ -43,7 +42,7 @@ public long getId() { return id; } - public void setId(long id) { + public void setId(final long id) { this.id = id; } @@ -51,7 +50,7 @@ public Date getCreationDate() { return creationDate; } - public void setCreationDate(Date creationDate) { + public void setCreationDate(final Date creationDate) { this.creationDate = creationDate; } @@ -59,7 +58,7 @@ public int getServiceId() { return serviceId; } - public void setServiceId(int serviceId) { + public void setServiceId(final int serviceId) { this.serviceId = serviceId; } @@ -67,13 +66,13 @@ public String getSearch() { return search; } - public void setSearch(String search) { + public void setSearch(final String search) { this.search = search; } @Ignore - public boolean hasEqualValues(SearchHistoryEntry otherEntry) { - return getServiceId() == otherEntry.getServiceId() && - getSearch().equals(otherEntry.getSearch()); + public boolean hasEqualValues(final SearchHistoryEntry otherEntry) { + return getServiceId() == otherEntry.getServiceId() + && getSearch().equals(otherEntry.getSearch()); } } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.java b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.java index 64bdf34de..bf1f7a9dd 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.java @@ -1,20 +1,20 @@ package org.schabi.newpipe.database.history.model; +import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.ForeignKey; import androidx.room.Ignore; import androidx.room.Index; -import androidx.annotation.NonNull; import org.schabi.newpipe.database.stream.model.StreamEntity; import java.util.Date; import static androidx.room.ForeignKey.CASCADE; -import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; +import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; @Entity(tableName = STREAM_HISTORY_TABLE, primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE}, @@ -27,10 +27,10 @@ onDelete = CASCADE, onUpdate = CASCADE) }) public class StreamHistoryEntity { - final public static String STREAM_HISTORY_TABLE = "stream_history"; - final public static String JOIN_STREAM_ID = "stream_id"; - final public static String STREAM_ACCESS_DATE = "access_date"; - final public static String STREAM_REPEAT_COUNT = "repeat_count"; + public static final String STREAM_HISTORY_TABLE = "stream_history"; + public static final String JOIN_STREAM_ID = "stream_id"; + public static final String STREAM_ACCESS_DATE = "access_date"; + public static final String STREAM_REPEAT_COUNT = "repeat_count"; @ColumnInfo(name = JOIN_STREAM_ID) private long streamUid; @@ -42,14 +42,15 @@ public class StreamHistoryEntity { @ColumnInfo(name = STREAM_REPEAT_COUNT) private long repeatCount; - public StreamHistoryEntity(long streamUid, @NonNull Date accessDate, long repeatCount) { + public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate, + final long repeatCount) { this.streamUid = streamUid; this.accessDate = accessDate; this.repeatCount = repeatCount; } @Ignore - public StreamHistoryEntity(long streamUid, @NonNull Date accessDate) { + public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate) { this(streamUid, accessDate, 1); } @@ -57,7 +58,7 @@ public long getStreamUid() { return streamUid; } - public void setStreamUid(long streamUid) { + public void setStreamUid(final long streamUid) { this.streamUid = streamUid; } @@ -65,7 +66,7 @@ public Date getAccessDate() { return accessDate; } - public void setAccessDate(@NonNull Date accessDate) { + public void setAccessDate(@NonNull final Date accessDate) { this.accessDate = accessDate; } @@ -73,7 +74,7 @@ public long getRepeatCount() { return repeatCount; } - public void setRepeatCount(long repeatCount) { + public void setRepeatCount(final long repeatCount) { this.repeatCount = repeatCount; } } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java index 252ca07f0..a13894030 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java @@ -7,18 +7,19 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL; public class PlaylistMetadataEntry implements PlaylistLocalItem { - final public static String PLAYLIST_STREAM_COUNT = "streamCount"; + public static final String PLAYLIST_STREAM_COUNT = "streamCount"; @ColumnInfo(name = PLAYLIST_ID) - final public long uid; + public final long uid; @ColumnInfo(name = PLAYLIST_NAME) - final public String name; + public final String name; @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL) - final public String thumbnailUrl; + public final String thumbnailUrl; @ColumnInfo(name = PLAYLIST_STREAM_COUNT) - final public long streamCount; + public final long streamCount; - public PlaylistMetadataEntry(long uid, String name, String thumbnailUrl, long streamCount) { + public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl, + final long streamCount) { this.uid = uid; this.name = name; this.thumbnailUrl = thumbnailUrl; diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java index f5a685a7c..2cfe5440c 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java @@ -24,13 +24,13 @@ public abstract class PlaylistDAO implements BasicDAO { public abstract int deleteAll(); @Override - public Flowable> listByService(int serviceId) { + public Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } @Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") - public abstract Flowable> getPlaylist(final long playlistId); + public abstract Flowable> getPlaylist(long playlistId); @Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") - public abstract int deletePlaylist(final long playlistId); + public abstract int deletePlaylist(long playlistId); } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java index b7ccf42f7..23442ceff 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java @@ -27,22 +27,21 @@ public abstract class PlaylistRemoteDAO implements BasicDAO> listByService(int serviceId); - @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + - REMOTE_PLAYLIST_URL + " = :url AND " + - REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") + @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") public abstract Flowable> getPlaylist(long serviceId, String url); - @Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE + - " WHERE " + - REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") + @Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE + + " WHERE " + REMOTE_PLAYLIST_URL + " = :url " + + "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") abstract Long getPlaylistIdInternal(long serviceId, String url); @Transaction - public long upsert(PlaylistRemoteEntity playlist) { + public long upsert(final PlaylistRemoteEntity playlist) { final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl()); if (playlistId == null) { @@ -54,7 +53,7 @@ public long upsert(PlaylistRemoteEntity playlist) { } } - @Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE + - " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId") - public abstract int deletePlaylist(final long playlistId); + @Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE + + " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId") + public abstract int deletePlaylist(long playlistId); } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java index 656fc27fc..2c0f7e506 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java @@ -14,9 +14,16 @@ import io.reactivex.Flowable; import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT; -import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.*; -import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.*; -import static org.schabi.newpipe.database.stream.model.StreamEntity.*; +import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; +import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; +import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; +import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL; +import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX; +import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID; +import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID; +import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE; +import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID; +import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE; @Dao public abstract class PlaylistStreamDAO implements BasicDAO { @@ -29,40 +36,39 @@ public abstract class PlaylistStreamDAO implements BasicDAO> listByService(int serviceId) { + public Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } - @Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE + - " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") - public abstract void deleteBatch(final long playlistId); + @Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE + + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") + public abstract void deleteBatch(long playlistId); - @Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" + - " FROM " + PLAYLIST_STREAM_JOIN_TABLE + - " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") - public abstract Flowable getMaximumIndexOf(final long playlistId); + @Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)" + + " FROM " + PLAYLIST_STREAM_JOIN_TABLE + + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId") + public abstract Flowable getMaximumIndexOf(long playlistId); @Transaction - @Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " + + @Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " // get ids of streams of the given playlist - "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX + - " FROM " + PLAYLIST_STREAM_JOIN_TABLE + - " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" + + + "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX + + " FROM " + PLAYLIST_STREAM_JOIN_TABLE + + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" // then merge with the stream metadata - " ON " + STREAM_ID + " = " + JOIN_STREAM_ID + - " ORDER BY " + JOIN_INDEX + " ASC") + + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID + + " ORDER BY " + JOIN_INDEX + " ASC") public abstract Flowable> getOrderedStreamsOf(long playlistId); @Transaction - @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + - PLAYLIST_THUMBNAIL_URL + ", " + - "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT + + @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", " + + "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT - " FROM " + PLAYLIST_TABLE + - " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE + - " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID + - " GROUP BY " + JOIN_PLAYLIST_ID + - " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC") + + " FROM " + PLAYLIST_TABLE + + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE + + " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID + + " GROUP BY " + JOIN_PLAYLIST_ID + + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC") public abstract Flowable> getPlaylistMetadata(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java index 9d7989b21..71abf2732 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java @@ -11,10 +11,10 @@ @Entity(tableName = PLAYLIST_TABLE, indices = {@Index(value = {PLAYLIST_NAME})}) public class PlaylistEntity { - final public static String PLAYLIST_TABLE = "playlists"; - final public static String PLAYLIST_ID = "uid"; - final public static String PLAYLIST_NAME = "name"; - final public static String PLAYLIST_THUMBNAIL_URL = "thumbnail_url"; + public static final String PLAYLIST_TABLE = "playlists"; + public static final String PLAYLIST_ID = "uid"; + public static final String PLAYLIST_NAME = "name"; + public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url"; @PrimaryKey(autoGenerate = true) @ColumnInfo(name = PLAYLIST_ID) @@ -26,7 +26,7 @@ public class PlaylistEntity { @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL) private String thumbnailUrl; - public PlaylistEntity(String name, String thumbnailUrl) { + public PlaylistEntity(final String name, final String thumbnailUrl) { this.name = name; this.thumbnailUrl = thumbnailUrl; } @@ -35,7 +35,7 @@ public long getUid() { return uid; } - public void setUid(long uid) { + public void setUid(final long uid) { this.uid = uid; } @@ -43,7 +43,7 @@ public String getName() { return name; } - public void setName(String name) { + public void setName(final String name) { this.name = name; } @@ -51,7 +51,7 @@ public String getThumbnailUrl() { return thumbnailUrl; } - public void setThumbnailUrl(String thumbnailUrl) { + public void setThumbnailUrl(final String thumbnailUrl) { this.thumbnailUrl = thumbnailUrl; } } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index fa257cfed..2e9a15d7d 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -24,14 +24,14 @@ @Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true) }) public class PlaylistRemoteEntity implements PlaylistLocalItem { - final public static String REMOTE_PLAYLIST_TABLE = "remote_playlists"; - final public static String REMOTE_PLAYLIST_ID = "uid"; - final public static String REMOTE_PLAYLIST_SERVICE_ID = "service_id"; - final public static String REMOTE_PLAYLIST_NAME = "name"; - final public static String REMOTE_PLAYLIST_URL = "url"; - final public static String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url"; - final public static String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader"; - final public static String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count"; + public static final String REMOTE_PLAYLIST_TABLE = "remote_playlists"; + public static final String REMOTE_PLAYLIST_ID = "uid"; + public static final String REMOTE_PLAYLIST_SERVICE_ID = "service_id"; + public static final String REMOTE_PLAYLIST_NAME = "name"; + public static final String REMOTE_PLAYLIST_URL = "url"; + public static final String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url"; + public static final String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader"; + public static final String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count"; @PrimaryKey(autoGenerate = true) @ColumnInfo(name = REMOTE_PLAYLIST_ID) @@ -55,8 +55,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem { @ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT) private Long streamCount; - public PlaylistRemoteEntity(int serviceId, String name, String url, String thumbnailUrl, - String uploader, Long streamCount) { + public PlaylistRemoteEntity(final int serviceId, final String name, final String url, + final String thumbnailUrl, final String uploader, + final Long streamCount) { this.serviceId = serviceId; this.name = name; this.url = url; @@ -68,7 +69,8 @@ public PlaylistRemoteEntity(int serviceId, String name, String url, String thumb @Ignore public PlaylistRemoteEntity(final PlaylistInfo info) { this(info.getServiceId(), info.getName(), info.getUrl(), - info.getThumbnailUrl() == null ? info.getUploaderAvatarUrl() : info.getThumbnailUrl(), + info.getThumbnailUrl() == null + ? info.getUploaderAvatarUrl() : info.getThumbnailUrl(), info.getUploaderName(), info.getStreamCount()); } @@ -90,7 +92,7 @@ public long getUid() { return uid; } - public void setUid(long uid) { + public void setUid(final long uid) { this.uid = uid; } @@ -98,7 +100,7 @@ public int getServiceId() { return serviceId; } - public void setServiceId(int serviceId) { + public void setServiceId(final int serviceId) { this.serviceId = serviceId; } @@ -106,7 +108,7 @@ public String getName() { return name; } - public void setName(String name) { + public void setName(final String name) { this.name = name; } @@ -114,7 +116,7 @@ public String getThumbnailUrl() { return thumbnailUrl; } - public void setThumbnailUrl(String thumbnailUrl) { + public void setThumbnailUrl(final String thumbnailUrl) { this.thumbnailUrl = thumbnailUrl; } @@ -122,7 +124,7 @@ public String getUrl() { return url; } - public void setUrl(String url) { + public void setUrl(final String url) { this.url = url; } @@ -130,7 +132,7 @@ public String getUploader() { return uploader; } - public void setUploader(String uploader) { + public void setUploader(final String uploader) { this.uploader = uploader; } @@ -138,7 +140,7 @@ public Long getStreamCount() { return streamCount; } - public void setStreamCount(Long streamCount) { + public void setStreamCount(final Long streamCount) { this.streamCount = streamCount; } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistStreamEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistStreamEntity.java index 87afdb4f9..f3208b6d5 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistStreamEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistStreamEntity.java @@ -30,11 +30,10 @@ onDelete = CASCADE, onUpdate = CASCADE, deferred = true) }) public class PlaylistStreamEntity { - - final public static String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join"; - final public static String JOIN_PLAYLIST_ID = "playlist_id"; - final public static String JOIN_STREAM_ID = "stream_id"; - final public static String JOIN_INDEX = "join_index"; + public static final String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join"; + public static final String JOIN_PLAYLIST_ID = "playlist_id"; + public static final String JOIN_STREAM_ID = "stream_id"; + public static final String JOIN_INDEX = "join_index"; @ColumnInfo(name = JOIN_PLAYLIST_ID) private long playlistUid; @@ -55,23 +54,23 @@ public long getPlaylistUid() { return playlistUid; } - public long getStreamUid() { - return streamUid; + public void setPlaylistUid(final long playlistUid) { + this.playlistUid = playlistUid; } - public int getIndex() { - return index; + public long getStreamUid() { + return streamUid; } - public void setPlaylistUid(long playlistUid) { - this.playlistUid = playlistUid; + public void setStreamUid(final long streamUid) { + this.streamUid = streamUid; } - public void setStreamUid(long streamUid) { - this.streamUid = streamUid; + public int getIndex() { + return index; } - public void setIndex(int index) { + public void setIndex(final int index) { this.index = index; } } diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java index c85810984..eb0f77f66 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.java @@ -27,21 +27,21 @@ public abstract class StreamStateDAO implements BasicDAO { public abstract int deleteAll(); @Override - public Flowable> listByService(int serviceId) { + public Flowable> listByService(final int serviceId) { throw new UnsupportedOperationException(); } @Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") - public abstract Flowable> getState(final long streamId); + public abstract Flowable> getState(long streamId); @Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") - public abstract int deleteState(final long streamId); + public abstract int deleteState(long streamId); @Insert(onConflict = OnConflictStrategy.IGNORE) - abstract void silentInsertInternal(final StreamStateEntity streamState); + abstract void silentInsertInternal(StreamStateEntity streamState); @Transaction - public long upsert(StreamStateEntity stream) { + public long upsert(final StreamStateEntity stream) { silentInsertInternal(stream); return update(stream); } diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt index ed9dc6b42..5ec2999f4 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt @@ -90,7 +90,8 @@ data class StreamEntity( if (viewCount != null) item.viewCount = viewCount as Long item.textualUploadDate = textualUploadDate item.uploadDate = uploadDate?.let { - DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation ?: false) + DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation + ?: false) } return item diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java index 8630bfa53..d275d9a71 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java @@ -1,10 +1,9 @@ package org.schabi.newpipe.database.stream.model; - +import androidx.annotation.Nullable; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.ForeignKey; -import androidx.annotation.Nullable; import java.util.concurrent.TimeUnit; @@ -21,14 +20,17 @@ onDelete = CASCADE, onUpdate = CASCADE) }) public class StreamStateEntity { - final public static String STREAM_STATE_TABLE = "stream_state"; - final public static String JOIN_STREAM_ID = "stream_id"; - final public static String STREAM_PROGRESS_TIME = "progress_time"; - + public static final String STREAM_STATE_TABLE = "stream_state"; + public static final String JOIN_STREAM_ID = "stream_id"; + public static final String STREAM_PROGRESS_TIME = "progress_time"; - /** Playback state will not be saved, if playback time less than this threshold */ + /** + * Playback state will not be saved, if playback time is less than this threshold. + */ private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5; - /** Playback state will not be saved, if time left less than this threshold */ + /** + * Playback state will not be saved, if time left is less than this threshold. + */ private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10; @ColumnInfo(name = JOIN_STREAM_ID) @@ -37,7 +39,7 @@ public class StreamStateEntity { @ColumnInfo(name = STREAM_PROGRESS_TIME) private long progressTime; - public StreamStateEntity(long streamUid, long progressTime) { + public StreamStateEntity(final long streamUid, final long progressTime) { this.streamUid = streamUid; this.progressTime = progressTime; } @@ -46,7 +48,7 @@ public long getStreamUid() { return streamUid; } - public void setStreamUid(long streamUid) { + public void setStreamUid(final long streamUid) { this.streamUid = streamUid; } @@ -54,21 +56,23 @@ public long getProgressTime() { return progressTime; } - public void setProgressTime(long progressTime) { + public void setProgressTime(final long progressTime) { this.progressTime = progressTime; } - public boolean isValid(int durationInSeconds) { + public boolean isValid(final int durationInSeconds) { final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime); return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS && seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS; } @Override - public boolean equals(@Nullable Object obj) { + public boolean equals(@Nullable final Object obj) { if (obj instanceof StreamStateEntity) { return ((StreamStateEntity) obj).streamUid == streamUid && ((StreamStateEntity) obj).progressTime == progressTime; - } else return false; + } else { + return false; + } } } diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt index bd13d9088..0269b5b17 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt @@ -48,7 +48,7 @@ abstract class SubscriptionDAO : BasicDAO { entity.uid = uidFromInsert } else { val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url) - ?: throw IllegalStateException("Subscription cannot be null just after insertion.") + ?: throw IllegalStateException("Subscription cannot be null just after insertion.") entity.uid = subscriptionIdFromDb update(entity) diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java index ec98c583a..cc7219543 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java @@ -1,11 +1,11 @@ package org.schabi.newpipe.database.subscription; +import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.Index; import androidx.room.PrimaryKey; -import androidx.annotation.NonNull; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; @@ -18,15 +18,14 @@ @Entity(tableName = SUBSCRIPTION_TABLE, indices = {@Index(value = {SUBSCRIPTION_SERVICE_ID, SUBSCRIPTION_URL}, unique = true)}) public class SubscriptionEntity { - - public static final String SUBSCRIPTION_UID = "uid"; - public static final String SUBSCRIPTION_TABLE = "subscriptions"; - public static final String SUBSCRIPTION_SERVICE_ID = "service_id"; - public static final String SUBSCRIPTION_URL = "url"; - public static final String SUBSCRIPTION_NAME = "name"; - public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url"; - public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count"; - public static final String SUBSCRIPTION_DESCRIPTION = "description"; + public static final String SUBSCRIPTION_UID = "uid"; + public static final String SUBSCRIPTION_TABLE = "subscriptions"; + public static final String SUBSCRIPTION_SERVICE_ID = "service_id"; + public static final String SUBSCRIPTION_URL = "url"; + public static final String SUBSCRIPTION_NAME = "name"; + public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url"; + public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count"; + public static final String SUBSCRIPTION_DESCRIPTION = "description"; @PrimaryKey(autoGenerate = true) private long uid = 0; @@ -49,11 +48,21 @@ public class SubscriptionEntity { @ColumnInfo(name = SUBSCRIPTION_DESCRIPTION) private String description; + @Ignore + public static SubscriptionEntity from(@NonNull final ChannelInfo info) { + SubscriptionEntity result = new SubscriptionEntity(); + result.setServiceId(info.getServiceId()); + result.setUrl(info.getUrl()); + result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), + info.getSubscriberCount()); + return result; + } + public long getUid() { return uid; } - public void setUid(long uid) { + public void setUid(final long uid) { this.uid = uid; } @@ -61,7 +70,7 @@ public int getServiceId() { return serviceId; } - public void setServiceId(int serviceId) { + public void setServiceId(final int serviceId) { this.serviceId = serviceId; } @@ -69,7 +78,7 @@ public String getUrl() { return url; } - public void setUrl(String url) { + public void setUrl(final String url) { this.url = url; } @@ -77,7 +86,7 @@ public String getName() { return name; } - public void setName(String name) { + public void setName(final String name) { this.name = name; } @@ -85,7 +94,7 @@ public String getAvatarUrl() { return avatarUrl; } - public void setAvatarUrl(String avatarUrl) { + public void setAvatarUrl(final String avatarUrl) { this.avatarUrl = avatarUrl; } @@ -93,7 +102,7 @@ public Long getSubscriberCount() { return subscriberCount; } - public void setSubscriberCount(Long subscriberCount) { + public void setSubscriberCount(final Long subscriberCount) { this.subscriberCount = subscriberCount; } @@ -101,19 +110,16 @@ public String getDescription() { return description; } - public void setDescription(String description) { + public void setDescription(final String description) { this.description = description; } @Ignore - public void setData(final String name, - final String avatarUrl, - final String description, - final Long subscriberCount) { - this.setName(name); - this.setAvatarUrl(avatarUrl); - this.setDescription(description); - this.setSubscriberCount(subscriberCount); + public void setData(final String n, final String au, final String d, final Long sc) { + this.setName(n); + this.setAvatarUrl(au); + this.setDescription(d); + this.setSubscriberCount(sc); } @Ignore @@ -124,13 +130,4 @@ public ChannelInfoItem toChannelInfoItem() { item.setDescription(getDescription()); return item; } - - @Ignore - public static SubscriptionEntity from(@NonNull ChannelInfo info) { - SubscriptionEntity result = new SubscriptionEntity(); - result.setServiceId(info.getServiceId()); - result.setUrl(info.getUrl()); - result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount()); - return result; - } } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java index de3df3527..912d63e5a 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java @@ -3,16 +3,16 @@ import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.ViewTreeObserver; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + import org.schabi.newpipe.R; -import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; import us.shandian.giga.service.DownloadManagerService; @@ -25,7 +25,7 @@ public class DownloadActivity extends AppCompatActivity { private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag"; @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { // Service Intent i = new Intent(); i.setClass(this, DownloadManagerService.class); @@ -46,7 +46,8 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.setDisplayShowTitleEnabled(true); } - getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + getWindow().getDecorView().getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { updateFragments(); @@ -65,7 +66,7 @@ private void updateFragments() { } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); @@ -75,7 +76,7 @@ public boolean onCreateOptionsMenu(Menu menu) { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index c78e68597..bdd358eaf 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -81,11 +81,12 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; -public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { +public class DownloadDialog extends DialogFragment + implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { private static final String TAG = "DialogFragment"; private static final boolean DEBUG = MainActivity.DEBUG; private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230; - + private final CompositeDisposable disposables = new CompositeDisposable(); @State protected StreamInfo currentInfo; @State @@ -100,30 +101,32 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck protected int selectedAudioIndex = 0; @State protected int selectedSubtitleIndex = 0; - + private StoredDirectoryHelper mainStorageAudio = null; + private StoredDirectoryHelper mainStorageVideo = null; + private DownloadManager downloadManager = null; + private ActionMenuItemView okButton = null; + private Context context; + private boolean askForSavePath; private StreamItemAdapter audioStreamsAdapter; private StreamItemAdapter videoStreamsAdapter; private StreamItemAdapter subtitleStreamsAdapter; - - private final CompositeDisposable disposables = new CompositeDisposable(); - private EditText nameEditText; private Spinner streamsSpinner; private RadioGroup radioStreamsGroup; private TextView threadsCountTextView; private SeekBar threadsSeekBar; - private SharedPreferences prefs; - public static DownloadDialog newInstance(StreamInfo info) { + public static DownloadDialog newInstance(final StreamInfo info) { DownloadDialog dialog = new DownloadDialog(); dialog.setInfo(info); return dialog; } - public static DownloadDialog newInstance(Context context, StreamInfo info) { - final ArrayList streamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, - info.getVideoStreams(), info.getVideoOnlyStreams(), false)); + public static DownloadDialog newInstance(final Context context, final StreamInfo info) { + final ArrayList streamsList = new ArrayList<>(ListHelper + .getSortedStreamVideosList(context, info.getVideoStreams(), + info.getVideoOnlyStreams(), false)); final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList); final DownloadDialog instance = newInstance(info); @@ -135,57 +138,61 @@ public static DownloadDialog newInstance(Context context, StreamInfo info) { return instance; } - private void setInfo(StreamInfo info) { + private void setInfo(final StreamInfo info) { this.currentInfo = info; } - public void setAudioStreams(List audioStreams) { + public void setAudioStreams(final List audioStreams) { setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext())); } - public void setAudioStreams(StreamSizeWrapper wrappedAudioStreams) { - this.wrappedAudioStreams = wrappedAudioStreams; + public void setAudioStreams(final StreamSizeWrapper was) { + this.wrappedAudioStreams = was; } - public void setVideoStreams(List videoStreams) { + public void setVideoStreams(final List videoStreams) { setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext())); } - public void setVideoStreams(StreamSizeWrapper wrappedVideoStreams) { - this.wrappedVideoStreams = wrappedVideoStreams; + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + + public void setVideoStreams(final StreamSizeWrapper wvs) { + this.wrappedVideoStreams = wvs; } - public void setSubtitleStreams(List subtitleStreams) { + public void setSubtitleStreams(final List subtitleStreams) { setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext())); } - public void setSubtitleStreams(StreamSizeWrapper wrappedSubtitleStreams) { - this.wrappedSubtitleStreams = wrappedSubtitleStreams; + public void setSubtitleStreams( + final StreamSizeWrapper wss) { + this.wrappedSubtitleStreams = wss; } - public void setSelectedVideoStream(int selectedVideoIndex) { - this.selectedVideoIndex = selectedVideoIndex; + public void setSelectedVideoStream(final int svi) { + this.selectedVideoIndex = svi; } - public void setSelectedAudioStream(int selectedAudioIndex) { - this.selectedAudioIndex = selectedAudioIndex; + public void setSelectedAudioStream(final int sai) { + this.selectedAudioIndex = sai; } - public void setSelectedSubtitleStream(int selectedSubtitleIndex) { - this.selectedSubtitleIndex = selectedSubtitleIndex; + public void setSelectedSubtitleStream(final int ssi) { + this.selectedSubtitleIndex = ssi; } - /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (DEBUG) - Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); + if (DEBUG) { + Log.d(TAG, "onCreate() called with: " + + "savedInstanceState = [" + savedInstanceState + "]"); + } - if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { + if (!PermissionHelper.checkStoragePermissions(getActivity(), + PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { getDialog().dismiss(); return; } @@ -199,17 +206,23 @@ public void onCreate(@Nullable Bundle savedInstanceState) { List videoStreams = wrappedVideoStreams.getStreamsList(); for (int i = 0; i < videoStreams.size(); i++) { - if (!videoStreams.get(i).isVideoOnly()) continue; - AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i)); + if (!videoStreams.get(i).isVideoOnly()) { + continue; + } + AudioStream audioStream = SecondaryStreamHelper + .getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i)); if (audioStream != null) { - secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream)); + secondaryStreams + .append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream)); } else if (DEBUG) { - Log.w(TAG, "No audio stream candidates for video format " + videoStreams.get(i).getFormat().name()); + Log.w(TAG, "No audio stream candidates for video format " + + videoStreams.get(i).getFormat().name()); } } - this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams, secondaryStreams); + this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams, + secondaryStreams); this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams); this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams); @@ -218,7 +231,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { context.bindService(intent, new ServiceConnection() { @Override - public void onServiceConnected(ComponentName cname, IBinder service) { + public void onServiceConnected(final ComponentName cname, final IBinder service) { DownloadManagerBinder mgr = (DownloadManagerBinder) service; mainStorageAudio = mgr.getMainStorageAudio(); @@ -232,25 +245,34 @@ public void onServiceConnected(ComponentName cname, IBinder service) { } @Override - public void onServiceDisconnected(ComponentName name) { + public void onServiceDisconnected(final ComponentName name) { // nothing to do } }, Context.BIND_AUTO_CREATE); } + /*////////////////////////////////////////////////////////////////////////// + // Inits + //////////////////////////////////////////////////////////////////////////*/ + @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if (DEBUG) - Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]"); + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + if (DEBUG) { + Log.d(TAG, "onCreateView() called with: " + + "inflater = [" + inflater + "], container = [" + container + "], " + + "savedInstanceState = [" + savedInstanceState + "]"); + } return inflater.inflate(R.layout.download_dialog, container); } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); nameEditText = view.findViewById(R.id.file_name); nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName())); - selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams()); + selectedAudioIndex = ListHelper + .getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams()); selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll()); @@ -272,21 +294,20 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat threadsCountTextView.setText(String.valueOf(threads)); threadsSeekBar.setProgress(threads - 1); threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - progress++; - prefs.edit().putInt(getString(R.string.default_download_threads), progress).apply(); - threadsCountTextView.setText(String.valueOf(progress)); + public void onProgressChanged(final SeekBar seekbar, final int progress, + final boolean fromUser) { + final int newProgress = progress + 1; + prefs.edit().putInt(getString(R.string.default_download_threads), newProgress) + .apply(); + threadsCountTextView.setText(String.valueOf(newProgress)); } @Override - public void onStartTrackingTouch(SeekBar p1) { - } + public void onStartTrackingTouch(final SeekBar p1) { } @Override - public void onStopTrackingTouch(SeekBar p1) { - } + public void onStopTrackingTouch(final SeekBar p1) { } }); fetchStreamsSize(); @@ -295,17 +316,20 @@ public void onStopTrackingTouch(SeekBar p1) { private void fetchStreamsSize() { disposables.clear(); - disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> { + disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams) + .subscribe(result -> { if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) { setupVideoSpinner(); } })); - disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> { + disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams) + .subscribe(result -> { if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) { setupAudioSpinner(); } })); - disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> { + disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams) + .subscribe(result -> { if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) { setupSubtitleSpinner(); } @@ -318,14 +342,22 @@ public void onDestroy() { disposables.clear(); } + /*////////////////////////////////////////////////////////////////////////// + // Radio group Video&Audio options - Listener + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onSaveInstanceState(@NonNull Bundle outState) { + public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } + /*////////////////////////////////////////////////////////////////////////// + // Streams Spinner Listener + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) { @@ -336,7 +368,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) { File file = Utils.getFileForUri(data.getData()); - checkSelectedDownload(null, Uri.fromFile(file), file.getName(), StoredFileHelper.DEFAULT_MIME); + checkSelectedDownload(null, Uri.fromFile(file), file.getName(), + StoredFileHelper.DEFAULT_MIME); return; } @@ -347,27 +380,27 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } // check if the selected file was previously used - checkSelectedDownload(null, data.getData(), docFile.getName(), docFile.getType()); + checkSelectedDownload(null, data.getData(), docFile.getName(), + docFile.getType()); } } - /*////////////////////////////////////////////////////////////////////////// - // Inits - //////////////////////////////////////////////////////////////////////////*/ - - private void initToolbar(Toolbar toolbar) { - if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]"); + private void initToolbar(final Toolbar toolbar) { + if (DEBUG) { + Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]"); + } boolean isLight = ThemeHelper.isLightThemeSelected(getActivity()); toolbar.setTitle(R.string.download_dialog_title); - toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp); + toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp + : R.drawable.ic_arrow_back_white_24dp); toolbar.inflateMenu(R.menu.dialog_url); toolbar.setNavigationOnClickListener(v -> getDialog().dismiss()); toolbar.setNavigationContentDescription(R.string.cancel); okButton = toolbar.findViewById(R.id.okay); - okButton.setEnabled(false);// disable until the download service connection is done + okButton.setEnabled(false); // disable until the download service connection is done toolbar.setOnMenuItemClickListener(item -> { if (item.getItemId() == R.id.okay) { @@ -381,8 +414,14 @@ private void initToolbar(Toolbar toolbar) { }); } + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + private void setupAudioSpinner() { - if (getContext() == null) return; + if (getContext() == null) { + return; + } streamsSpinner.setAdapter(audioStreamsAdapter); streamsSpinner.setSelection(selectedAudioIndex); @@ -390,7 +429,9 @@ private void setupAudioSpinner() { } private void setupVideoSpinner() { - if (getContext() == null) return; + if (getContext() == null) { + return; + } streamsSpinner.setAdapter(videoStreamsAdapter); streamsSpinner.setSelection(selectedVideoIndex); @@ -398,21 +439,21 @@ private void setupVideoSpinner() { } private void setupSubtitleSpinner() { - if (getContext() == null) return; + if (getContext() == null) { + return; + } streamsSpinner.setAdapter(subtitleStreamsAdapter); streamsSpinner.setSelection(selectedSubtitleIndex); setRadioButtonsState(true); } - /*////////////////////////////////////////////////////////////////////////// - // Radio group Video&Audio options - Listener - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { - if (DEBUG) - Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]"); + public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) { + if (DEBUG) { + Log.d(TAG, "onCheckedChanged() called with: " + + "group = [" + group + "], checkedId = [" + checkedId + "]"); + } boolean flag = true; switch (checkedId) { @@ -431,14 +472,14 @@ public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { threadsSeekBar.setEnabled(flag); } - /*////////////////////////////////////////////////////////////////////////// - // Streams Spinner Listener - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (DEBUG) - Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); + public void onItemSelected(final AdapterView parent, final View view, + final int position, final long id) { + if (DEBUG) { + Log.d(TAG, "onItemSelected() called with: " + + "parent = [" + parent + "], view = [" + view + "], " + + "position = [" + position + "], id = [" + id + "]"); + } switch (radioStreamsGroup.getCheckedRadioButtonId()) { case R.id.audio_button: selectedAudioIndex = position; @@ -453,13 +494,9 @@ public void onItemSelected(AdapterView parent, View view, int position, long } @Override - public void onNothingSelected(AdapterView parent) { + public void onNothingSelected(final AdapterView parent) { } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - protected void setupDownloadOptions() { setRadioButtonsState(false); @@ -484,30 +521,36 @@ protected void setupDownloadOptions() { subtitleButton.setChecked(true); setupSubtitleSpinner(); } else { - Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), R.string.no_streams_available_download, + Toast.LENGTH_SHORT).show(); getDialog().dismiss(); } } - private void setRadioButtonsState(boolean enabled) { + private void setRadioButtonsState(final boolean enabled) { radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled); radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled); radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled); } - private int getSubtitleIndexBy(List streams) { + private int getSubtitleIndexBy(final List streams) { final Localization preferredLocalization = NewPipe.getPreferredLocalization(); int candidate = 0; for (int i = 0; i < streams.size(); i++) { final Locale streamLocale = streams.get(i).getLocale(); - final boolean languageEquals = streamLocale.getLanguage() != null && preferredLocalization.getLanguageCode() != null && - streamLocale.getLanguage().equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage()); - final boolean countryEquals = streamLocale.getCountry() != null && streamLocale.getCountry().equals(preferredLocalization.getCountryCode()); + final boolean languageEquals = streamLocale.getLanguage() != null + && preferredLocalization.getLanguageCode() != null + && streamLocale.getLanguage() + .equals(new Locale(preferredLocalization.getLanguageCode()).getLanguage()); + final boolean countryEquals = streamLocale.getCountry() != null + && streamLocale.getCountry().equals(preferredLocalization.getCountryCode()); if (languageEquals) { - if (countryEquals) return i; + if (countryEquals) { + return i; + } candidate = i; } @@ -516,20 +559,13 @@ private int getSubtitleIndexBy(List streams) { return candidate; } - StoredDirectoryHelper mainStorageAudio = null; - StoredDirectoryHelper mainStorageVideo = null; - DownloadManager downloadManager = null; - ActionMenuItemView okButton = null; - Context context; - boolean askForSavePath; - private String getNameEditText() { String str = nameEditText.getText().toString().trim(); return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str); } - private void showFailedDialog(@StringRes int msg) { + private void showFailedDialog(@StringRes final int msg) { assureCorrectAppLanguage(getContext()); new AlertDialog.Builder(context) .setTitle(R.string.general_error) @@ -539,13 +575,14 @@ private void showFailedDialog(@StringRes int msg) { .show(); } - private void showErrorActivity(Exception e) { + private void showErrorActivity(final Exception e) { ErrorActivity.reportError( context, Collections.singletonList(e), null, null, - ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error) + ErrorActivity.ErrorInfo + .make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error) ); } @@ -563,7 +600,7 @@ private void prepareSelectedDownload() { case R.id.audio_button: mainStorage = mainStorageAudio; format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat(); - switch(format) { + switch (format) { case WEBMA_OPUS: mime = "audio/ogg"; filename += "opus"; @@ -581,7 +618,7 @@ private void prepareSelectedDownload() { filename += format.suffix; break; case R.id.subtitle_button: - mainStorage = mainStorageVideo;// subtitle & video files go together + mainStorage = mainStorageVideo; // subtitle & video files go together format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat(); mime = format.mimeType; filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix; @@ -596,23 +633,25 @@ private void prepareSelectedDownload() { // * save path not defined (via download settings) // * the user checked the "ask where to download" option - if (!askForSavePath) - Toast.makeText(context, getString(R.string.no_available_dir), Toast.LENGTH_LONG).show(); + if (!askForSavePath) { + Toast.makeText(context, getString(R.string.no_available_dir), + Toast.LENGTH_LONG).show(); + } if (NewPipeSettings.useStorageAccessFramework(context)) { - StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, filename, mime); + StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, + filename, mime); } else { File initialSavePath; - if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) + if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) { initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC); - else + } else { initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES); + } initialSavePath = new File(initialSavePath, filename); - startActivityForResult( - FilePickerActivityHelper.chooseFileToSave(context, initialSavePath.getAbsolutePath()), - REQUEST_DOWNLOAD_SAVE_AS - ); + startActivityForResult(FilePickerActivityHelper.chooseFileToSave(context, + initialSavePath.getAbsolutePath()), REQUEST_DOWNLOAD_SAVE_AS); } return; @@ -622,7 +661,9 @@ private void prepareSelectedDownload() { checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime); } - private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri targetFile, String filename, String mime) { + private void checkSelectedDownload(final StoredDirectoryHelper mainStorage, + final Uri targetFile, final String filename, + final String mime) { StoredFileHelper storage; try { @@ -631,10 +672,12 @@ private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri target storage = new StoredFileHelper(context, null, targetFile, ""); } else if (targetFile == null) { // the file does not exist, but it is probably used in a pending download - storage = new StoredFileHelper(mainStorage.getUri(), filename, mime, mainStorage.getTag()); + storage = new StoredFileHelper(mainStorage.getUri(), filename, mime, + mainStorage.getTag()); } else { // the target filename is already use, attempt to use it - storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag()); + storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile, + mainStorage.getTag()); } } catch (Exception e) { showErrorActivity(e); @@ -738,24 +781,28 @@ private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri target } else { try { // try take (or steal) the file - storageNew = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag()); + storageNew = new StoredFileHelper(context, mainStorage.getUri(), + targetFile, mainStorage.getTag()); } catch (IOException e) { - Log.e(TAG, "Failed to take (or steal) the file in " + targetFile.toString()); + Log.e(TAG, "Failed to take (or steal) the file in " + + targetFile.toString()); storageNew = null; } } - if (storageNew != null && storageNew.canWrite()) + if (storageNew != null && storageNew.canWrite()) { continueSelectedDownload(storageNew); - else + } else { showFailedDialog(R.string.error_file_creation); + } break; case PendingRunning: storageNew = mainStorage.createUniqueFile(filename, mime); - if (storageNew == null) + if (storageNew == null) { showFailedDialog(R.string.error_file_creation); - else + } else { continueSelectedDownload(storageNew); + } break; } }); @@ -763,7 +810,7 @@ private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri target askDialog.create().show(); } - private void continueSelectedDownload(@NonNull StoredFileHelper storage) { + private void continueSelectedDownload(@NonNull final StoredFileHelper storage) { if (!storage.canWrite()) { showFailedDialog(R.string.permission_denied); return; @@ -771,7 +818,9 @@ private void continueSelectedDownload(@NonNull StoredFileHelper storage) { // check if the selected file has to be overwritten, by simply checking its length try { - if (storage.length() > 0) storage.truncate(); + if (storage.length() > 0) { + storage.truncate(); + } } catch (IOException e) { Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e); showFailedDialog(R.string.overwrite_failed); @@ -811,13 +860,15 @@ private void continueSelectedDownload(@NonNull StoredFileHelper storage) { if (secondary != null) { secondaryStream = secondary.getStream(); - if (selectedStream.getFormat() == MediaFormat.MPEG_4) + if (selectedStream.getFormat() == MediaFormat.MPEG_4) { psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER; - else + } else { psName = Postprocessing.ALGORITHM_WEBM_MUXER; + } psArgs = null; - long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream); + long videoSize = wrappedVideoStreams + .getSizeInBytes((VideoStream) selectedStream); // set nearLength, only, if both sizes are fetched or known. This probably // does not work on slow networks but is later updated in the downloader @@ -827,7 +878,7 @@ private void continueSelectedDownload(@NonNull StoredFileHelper storage) { } break; case R.id.subtitle_button: - threads = 1;// use unique thread for subtitles due small file size + threads = 1; // use unique thread for subtitles due small file size kind = 's'; selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex); @@ -835,7 +886,7 @@ private void continueSelectedDownload(@NonNull StoredFileHelper storage) { psName = Postprocessing.ALGORITHM_TTML_CONVERTER; psArgs = new String[]{ selectedStream.getFormat().getSuffix(), - "false",// ignore empty frames + "false" // ignore empty frames }; } break; @@ -854,14 +905,12 @@ private void continueSelectedDownload(@NonNull StoredFileHelper storage) { urls = new String[]{ selectedStream.getUrl(), secondaryStream.getUrl() }; - recoveryInfo = new MissionRecoveryInfo[]{ - new MissionRecoveryInfo(selectedStream), new MissionRecoveryInfo(secondaryStream) - }; + recoveryInfo = new MissionRecoveryInfo[]{new MissionRecoveryInfo(selectedStream), + new MissionRecoveryInfo(secondaryStream)}; } - DownloadManagerService.startMission( - context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo - ); + DownloadManagerService.startMission(context, urls, storage, kind, threads, + currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo); dismiss(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BackPressable.java b/app/src/main/java/org/schabi/newpipe/fragments/BackPressable.java index 737db784b..6add5eb09 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BackPressable.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BackPressable.java @@ -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 */ diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index f9852b7b0..861dc2c60 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -1,9 +1,8 @@ package org.schabi.newpipe.fragments; +import android.content.Context; import android.content.Intent; import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; import android.util.Log; import android.view.View; import android.widget.Button; @@ -11,6 +10,9 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + import com.jakewharton.rxbinding2.view.RxView; import org.schabi.newpipe.BaseFragment; @@ -36,22 +38,21 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; public abstract class BaseStateFragment extends BaseFragment implements ViewContract { - @State protected AtomicBoolean wasLoading = new AtomicBoolean(); protected AtomicBoolean isLoading = new AtomicBoolean(); @Nullable - protected View emptyStateView; + private View emptyStateView; @Nullable - protected ProgressBar loadingProgressBar; + private ProgressBar loadingProgressBar; protected View errorPanelRoot; - protected Button errorButtonRetry; - protected TextView errorTextView; + private Button errorButtonRetry; + private TextView errorTextView; @Override - public void onViewCreated(View rootView, Bundle savedInstanceState) { + public void onViewCreated(final View rootView, final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); doInitialLoadLogic(); } @@ -62,14 +63,12 @@ public void onPause() { wasLoading.set(isLoading.get()); } - /*////////////////////////////////////////////////////////////////////////// // Init //////////////////////////////////////////////////////////////////////////*/ - @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); emptyStateView = rootView.findViewById(R.id.empty_state_view); @@ -105,8 +104,10 @@ protected void doInitialLoadLogic() { startLoading(true); } - protected void startLoading(boolean forceLoad) { - if (DEBUG) Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]"); + protected void startLoading(final boolean forceLoad) { + if (DEBUG) { + Log.d(TAG, "startLoading() called with: forceLoad = [" + forceLoad + "]"); + } showLoading(); isLoading.set(true); } @@ -117,42 +118,62 @@ protected void startLoading(boolean forceLoad) { @Override public void showLoading() { - if (emptyStateView != null) animateView(emptyStateView, false, 150); - if (loadingProgressBar != null) animateView(loadingProgressBar, true, 400); + if (emptyStateView != null) { + animateView(emptyStateView, false, 150); + } + if (loadingProgressBar != null) { + animateView(loadingProgressBar, true, 400); + } animateView(errorPanelRoot, false, 150); } @Override public void hideLoading() { - if (emptyStateView != null) animateView(emptyStateView, false, 150); - if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0); + if (emptyStateView != null) { + animateView(emptyStateView, false, 150); + } + if (loadingProgressBar != null) { + animateView(loadingProgressBar, false, 0); + } animateView(errorPanelRoot, false, 150); } @Override public void showEmptyState() { isLoading.set(false); - if (emptyStateView != null) animateView(emptyStateView, true, 200); - if (loadingProgressBar != null) animateView(loadingProgressBar, false, 0); + if (emptyStateView != null) { + animateView(emptyStateView, true, 200); + } + if (loadingProgressBar != null) { + animateView(loadingProgressBar, false, 0); + } animateView(errorPanelRoot, false, 150); } @Override - public void showError(String message, boolean showRetryButton) { - if (DEBUG) Log.d(TAG, "showError() called with: message = [" + message + "], showRetryButton = [" + showRetryButton + "]"); + public void showError(final String message, final boolean showRetryButton) { + if (DEBUG) { + Log.d(TAG, "showError() called with: " + + "message = [" + message + "], showRetryButton = [" + showRetryButton + "]"); + } isLoading.set(false); InfoCache.getInstance().clearCache(); hideLoading(); errorTextView.setText(message); - if (showRetryButton) animateView(errorButtonRetry, true, 600); - else animateView(errorButtonRetry, false, 0); + if (showRetryButton) { + animateView(errorButtonRetry, true, 600); + } else { + animateView(errorButtonRetry, false, 0); + } animateView(errorPanelRoot, true, 300); } @Override - public void handleResult(I result) { - if (DEBUG) Log.d(TAG, "handleResult() called with: result = [" + result + "]"); + public void handleResult(final I result) { + if (DEBUG) { + Log.d(TAG, "handleResult() called with: result = [" + result + "]"); + } hideLoading(); } @@ -161,21 +182,28 @@ public void handleResult(I result) { //////////////////////////////////////////////////////////////////////////*/ /** - * Default implementation handles some general exceptions + * Default implementation handles some general exceptions. * - * @return if the exception was handled + * @param exception The exception that should be handled + * @return If the exception was handled */ - protected boolean onError(Throwable exception) { - if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]"); + protected boolean onError(final Throwable exception) { + if (DEBUG) { + Log.d(TAG, "onError() called with: exception = [" + exception + "]"); + } isLoading.set(false); if (isDetached() || isRemoving()) { - if (DEBUG) Log.w(TAG, "onError() is detached or removing = [" + exception + "]"); + if (DEBUG) { + Log.w(TAG, "onError() is detached or removing = [" + exception + "]"); + } return true; } if (ExtractorHelper.isInterruptedCaused(exception)) { - if (DEBUG) Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); + if (DEBUG) { + Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); + } return true; } @@ -193,8 +221,10 @@ protected boolean onError(Throwable exception) { return false; } - public void onReCaptchaException(ReCaptchaException exception) { - if (DEBUG) Log.d(TAG, "onReCaptchaException() called"); + public void onReCaptchaException(final ReCaptchaException exception) { + if (DEBUG) { + Log.d(TAG, "onReCaptchaException() called"); + } Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); // Starting ReCaptcha Challenge Activity Intent intent = new Intent(activity, ReCaptchaActivity.class); @@ -204,33 +234,58 @@ public void onReCaptchaException(ReCaptchaException exception) { 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 exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) { - if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]"); - - if (serviceName == null) serviceName = "none"; - if (request == null) request = "none"; + public void onUnrecoverableError(final List exception, final UserAction userAction, + final String serviceName, final String request, + @StringRes final int errorId) { + if (DEBUG) { + Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]"); + } - 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 exception, UserAction userAction, String serviceName, String request, @StringRes int errorId) { + public void showSnackBarError(final List 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)); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java index 1e284c711..0cccfa4fe 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java @@ -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. diff --git a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java index de9716f28..62f823c73 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java @@ -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); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index a157f34bf..52c1afb93 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -50,14 +50,15 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); tabsManager = TabsManager.getManager(activity); tabsManager.setSavedTabsListener(() -> { if (DEBUG) { - Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed()); + Log.d(TAG, "TabsManager.SavedTabsChangeListener: " + + "onTabsChanged called, isResumed = " + isResumed()); } if (isResumed()) { setupTabs(); @@ -68,12 +69,14 @@ public void onCreate(Bundle savedInstanceState) { } @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main, container, false); } @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); tabLayout = rootView.findViewById(R.id.main_tab_layout); @@ -89,14 +92,18 @@ protected void initViews(View rootView, Bundle savedInstanceState) { public void onResume() { super.onResume(); - if (hasTabsChanged) setupTabs(); + if (hasTabsChanged) { + setupTabs(); + } } @Override public void onDestroy() { super.onDestroy(); tabsManager.unsetSavedTabsListener(); - if (viewPager != null) viewPager.setAdapter(null); + if (viewPager != null) { + viewPager.setAdapter(null); + } } /*////////////////////////////////////////////////////////////////////////// @@ -104,9 +111,12 @@ public void onDestroy() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } inflater.inflate(R.menu.main_fragment_menu, menu); ActionBar supportActionBar = activity.getSupportActionBar(); @@ -116,7 +126,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_search: try { @@ -141,7 +151,8 @@ private void setupTabs() { tabsList.addAll(tabsManager.getTabs()); if (pagerAdapter == null || !pagerAdapter.sameTabs(tabsList)) { - pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), getChildFragmentManager(), tabsList); + pagerAdapter = new SelectedTabsPagerAdapter(requireContext(), + getChildFragmentManager(), tabsList); } viewPager.setAdapter(null); @@ -165,31 +176,37 @@ private void updateTabsIconAndDescription() { } } - private void updateTitleForTab(int tabPosition) { + private void updateTitleForTab(final int tabPosition) { setTitle(tabsList.get(tabPosition).getTabName(requireContext())); } @Override - public void onTabSelected(TabLayout.Tab selectedTab) { - if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]"); + public void onTabSelected(final TabLayout.Tab selectedTab) { + if (DEBUG) { + Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]"); + } updateTitleForTab(selectedTab.getPosition()); } @Override - public void onTabUnselected(TabLayout.Tab tab) { - } + public void onTabUnselected(final TabLayout.Tab tab) { } @Override - public void onTabReselected(TabLayout.Tab tab) { - if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]"); + public void onTabReselected(final TabLayout.Tab tab) { + if (DEBUG) { + Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]"); + } updateTitleForTab(tab.getPosition()); } - private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapterMenuWorkaround { + private static final class SelectedTabsPagerAdapter + extends FragmentStatePagerAdapterMenuWorkaround { private final Context context; private final List internalTabsList; - private SelectedTabsPagerAdapter(Context context, FragmentManager fragmentManager, List tabsList) { + private SelectedTabsPagerAdapter(final Context context, + final FragmentManager fragmentManager, + final List tabsList) { super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); this.context = context; this.internalTabsList = new ArrayList<>(tabsList); @@ -197,7 +214,7 @@ private SelectedTabsPagerAdapter(Context context, FragmentManager fragmentManage @NonNull @Override - public Fragment getItem(int position) { + public Fragment getItem(final int position) { final Tab tab = internalTabsList.get(position); Throwable throwable = null; @@ -209,8 +226,8 @@ public Fragment getItem(int position) { } if (throwable != null) { - ErrorActivity.reportError(context, throwable, null, null, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo + .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); return new BlankFragment(); } @@ -222,7 +239,7 @@ public Fragment getItem(int position) { } @Override - public int getItemPosition(Object object) { + public int getItemPosition(final Object object) { // Causes adapter to reload all Fragments when // notifyDataSetChanged is called return POSITION_NONE; @@ -233,7 +250,7 @@ public int getCount() { return internalTabsList.size(); } - public boolean sameTabs(List tabsToCompare) { + public boolean sameTabs(final List tabsToCompare) { return internalTabsList.equals(tabsToCompare); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/OnScrollBelowItemsListener.java b/app/src/main/java/org/schabi/newpipe/fragments/OnScrollBelowItemsListener.java index 887097679..28ce91f55 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/OnScrollBelowItemsListener.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/OnScrollBelowItemsListener.java @@ -9,12 +9,13 @@ * 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 void onScrolled(RecyclerView recyclerView, int dx, int dy) { // 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) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java b/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java index 4ce09b000..bb980ac64 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java @@ -2,8 +2,11 @@ public interface ViewContract { void showLoading(); + void hideLoading(); + void showEmptyState(); + void showError(String message, boolean showRetryButton); void handleResult(I result); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java index f7f8ad702..f966880b1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java @@ -4,19 +4,15 @@ class StackItem implements Serializable { private final int serviceId; - private String title; private final String url; + private String title; - StackItem(int serviceId, String url, String title) { + StackItem(final int serviceId, final String url, final String title) { this.serviceId = serviceId; this.url = url; this.title = title; } - public void setTitle(String title) { - this.title = title; - } - public int getServiceId() { return serviceId; } @@ -25,6 +21,10 @@ public String getTitle() { return title; } + public void setTitle(final String title) { + this.title = title; + } + public String getUrl() { return url; } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java index d86226e92..38f013200 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java @@ -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 mFragmentList = new ArrayList<>(); private final List 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 int getCount() { 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 void clearAllItems() { 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(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index ebec8db0a..43e22d597 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -8,16 +8,6 @@ import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.tabs.TabLayout; -import androidx.fragment.app.Fragment; -import androidx.core.content.ContextCompat; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; import android.text.Html; import android.text.Spanned; import android.text.TextUtils; @@ -41,6 +31,17 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.tabs.TabLayout; import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; @@ -52,7 +53,6 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; 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; @@ -105,62 +105,58 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; import static org.schabi.newpipe.util.AnimationUtils.animateView; -public class VideoDetailFragment - extends BaseStateFragment - implements BackPressable, - SharedPreferences.OnSharedPreferenceChangeListener, - View.OnClickListener, - View.OnLongClickListener { +public class VideoDetailFragment extends BaseStateFragment + implements BackPressable, SharedPreferences.OnSharedPreferenceChangeListener, + View.OnClickListener, View.OnLongClickListener { public static final String AUTO_PLAY = "auto_play"; - - private int updateFlags = 0; private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2; private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; private static final int COMMENTS_UPDATE_FLAG = 0x8; - - private boolean autoPlayEnabled; - private boolean showRelatedStreams; - private boolean showComments; - private String selectedTabTag; - + private static final String COMMENTS_TAB_TAG = "COMMENTS"; + 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"; + /** + * Stack that contains the "navigation history".
+ * The peek is the current video. + */ + private final LinkedList stack = new LinkedList<>(); @State protected int serviceId = Constants.NO_SERVICE_ID; @State protected String name; @State protected String url; + private int updateFlags = 0; + private boolean autoPlayEnabled; + private boolean showRelatedStreams; + private boolean showComments; + private String selectedTabTag; + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ private StreamInfo currentInfo; private Disposable currentWorker; @NonNull private CompositeDisposable disposables = new CompositeDisposable(); @Nullable private Disposable positionSubscriber = null; - private List sortedVideoStreams; private int selectedVideoStreamIndex = -1; - - /*////////////////////////////////////////////////////////////////////////// - // Views - //////////////////////////////////////////////////////////////////////////*/ - private Menu menu; - private Spinner spinnerToolbar; - private LinearLayout contentRootLayoutHiding; - private View thumbnailBackgroundButton; private ImageView thumbnailImageView; private ImageView thumbnailPlayButton; private AnimatedProgressBar positionView; - private View videoTitleRoot; private TextView videoTitleTextView; private ImageView videoTitleToggleArrow; private TextView videoCountView; - private TextView detailControlsBackground; private TextView detailControlsPopup; private TextView detailControlsAddToPlaylist; @@ -168,47 +164,39 @@ public class VideoDetailFragment private TextView appendControlsDetail; private TextView detailDurationView; private TextView detailPositionView; - private LinearLayout videoDescriptionRootLayout; private TextView videoUploadDateView; private TextView videoDescriptionView; - private View uploaderRootLayout; private TextView uploaderTextView; private ImageView uploaderThumb; - private TextView thumbsUpTextView; private ImageView thumbsUpImageView; private TextView thumbsDownTextView; private ImageView thumbsDownImageView; private TextView thumbsDisabledTextView; + private AppBarLayout appBarLayout; + private ViewPager viewPager; - private static final String COMMENTS_TAB_TAG = "COMMENTS"; - private static final String RELATED_TAB_TAG = "NEXT VIDEO"; - private static final String EMPTY_TAB_TAG = "EMPTY TAB"; - private AppBarLayout appBarLayout; - private ViewPager viewPager; + /*////////////////////////////////////////////////////////////////////////*/ private TabAdaptor pageAdapter; + + /*////////////////////////////////////////////////////////////////////////// + // Fragment's Lifecycle + //////////////////////////////////////////////////////////////////////////*/ private TabLayout tabLayout; private FrameLayout relatedStreamsLayout; - - /*////////////////////////////////////////////////////////////////////////*/ - - public static VideoDetailFragment getInstance(int serviceId, String videoUrl, String name) { + public static VideoDetailFragment getInstance(final int serviceId, final String videoUrl, + final String name) { VideoDetailFragment instance = new VideoDetailFragment(); instance.setInitialData(serviceId, videoUrl, name); return instance; } - /*////////////////////////////////////////////////////////////////////////// - // Fragment's Lifecycle - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void - onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); @@ -226,17 +214,21 @@ public static VideoDetailFragment getInstance(int serviceId, String videoUrl, St } @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(); + } 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(); } @@ -246,9 +238,15 @@ public void onResume() { if (updateFlags != 0) { if (!isLoading.get() && currentInfo != null) { - if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) startLoading(false); - if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBar(currentInfo); - if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) startLoading(false); + if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) { + startLoading(false); + } + if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) { + setupActionBar(currentInfo); + } + if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) { + startLoading(false); + } } if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0 @@ -273,30 +271,45 @@ public void onDestroy() { PreferenceManager.getDefaultSharedPreferences(activity) .unregisterOnSharedPreferenceChangeListener(this); - if (positionSubscriber != null) positionSubscriber.dispose(); - if (currentWorker != null) currentWorker.dispose(); - if (disposables != null) disposables.clear(); + if (positionSubscriber != null) { + positionSubscriber.dispose(); + } + if (currentWorker != null) { + currentWorker.dispose(); + } + if (disposables != null) { + disposables.clear(); + } positionSubscriber = null; currentWorker = null; disposables = null; } + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onDestroyView() { - if (DEBUG) Log.d(TAG, "onDestroyView() called"); + if (DEBUG) { + Log.d(TAG, "onDestroyView() called"); + } spinnerToolbar.setOnItemSelectedListener(null); spinnerToolbar.setAdapter(null); super.onDestroyView(); } @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 + "]"); @@ -305,7 +318,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } @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; @@ -322,15 +336,8 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } } - /*////////////////////////////////////////////////////////////////////////// - // 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, @@ -344,8 +351,12 @@ public void onSaveInstanceState(Bundle outState) { outState.putSerializable(STACK_KEY, stack); } + /*////////////////////////////////////////////////////////////////////////// + // OnClick + //////////////////////////////////////////////////////////////////////////*/ + @Override - protected void onRestoreInstanceState(@NonNull Bundle savedState) { + protected void onRestoreInstanceState(@NonNull final Bundle savedState) { super.onRestoreInstanceState(savedState); Serializable serializable = savedState.getSerializable(INFO_KEY); @@ -363,13 +374,11 @@ protected void onRestoreInstanceState(@NonNull Bundle savedState) { } - /*////////////////////////////////////////////////////////////////////////// - // OnClick - //////////////////////////////////////////////////////////////////////////*/ - @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: @@ -395,14 +404,14 @@ public void onClick(View v) { Log.w(TAG, "Can't open channel because we got no channel URL"); } else { try { - NavigationHelper.openChannelFragment( - getFragmentManager(), - currentInfo.getServiceId(), - currentInfo.getUploaderUrl(), - currentInfo.getUploaderName()); + NavigationHelper.openChannelFragment( + getFragmentManager(), + currentInfo.getServiceId(), + currentInfo.getUploaderUrl(), + currentInfo.getUploaderName()); } catch (Exception e) { ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); - } + } } break; case R.id.detail_thumbnail_root_layout: @@ -420,8 +429,10 @@ public void onClick(View v) { } @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: @@ -438,6 +449,10 @@ public boolean onLongClick(View v) { return true; } + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + private void toggleTitleAndDescription() { if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) { videoTitleTextView.setMaxLines(1); @@ -450,12 +465,8 @@ private void toggleTitleAndDescription() { } } - /*////////////////////////////////////////////////////////////////////////// - // Init - //////////////////////////////////////////////////////////////////////////*/ - @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); spinnerToolbar = activity.findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner); @@ -504,8 +515,6 @@ protected void initViews(View rootView, Bundle savedInstanceState) { relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout); setHeightThumbnail(); - - } @Override @@ -544,41 +553,42 @@ private View.OnTouchListener getOnControlsTouchListener() { }; } - private void initThumbnailViews(@NonNull StreamInfo info) { + + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + + 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 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, + IMAGE_LOADER.displayImage(info.getThumbnailUrl(), thumbnailImageView, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener); } if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) { - imageLoader.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, + IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); } } - - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - this.menu = menu; + public void onCreateOptionsMenu(final Menu m, final MenuInflater inflater) { + this.menu = m; // CAUTION set item properties programmatically otherwise it would not be accepted by // appcompat itemsinflater.inflate(R.menu.videoitem_detail, menu); - inflater.inflate(R.menu.video_detail_menu, menu); + inflater.inflate(R.menu.video_detail_menu, m); updateMenuItemVisibility(); @@ -590,7 +600,6 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } private void updateMenuItemVisibility() { - // show kodi if set in settings menu.findItem(R.id.action_play_with_kodi).setVisible( PreferenceManager.getDefaultSharedPreferences(activity).getBoolean( @@ -598,7 +607,7 @@ private void updateMenuItemVisibility() { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { NavigationHelper.openSettings(requireContext()); @@ -611,24 +620,25 @@ public boolean onOptionsItemSelected(MenuItem item) { } switch (id) { - case R.id.menu_item_share: { + case R.id.menu_item_share: if (currentInfo != null) { - ShareUtils.shareUrl(requireContext(), currentInfo.getName(), currentInfo.getOriginalUrl()); + ShareUtils.shareUrl(requireContext(), currentInfo.getName(), + currentInfo.getOriginalUrl()); } return true; - } - case R.id.menu_item_openInBrowser: { + case R.id.menu_item_openInBrowser: if (currentInfo != null) { ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getOriginalUrl()); } return true; - } case R.id.action_play_with_kodi: try { NavigationHelper.playWithKore(activity, Uri.parse( url.replace("https", "http"))); } catch (Exception e) { - if (DEBUG) Log.i(TAG, "Failed to start kore", e); + if (DEBUG) { + Log.i(TAG, "Failed to start kore", e); + } KoreUtil.showInstallKoreDialog(activity); } return true; @@ -637,75 +647,71 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - private void setupActionBarOnError(final String url) { - if (DEBUG) Log.d(TAG, "setupActionBarHandlerOnError() called with: url = [" + url + "]"); + private void setupActionBarOnError(final String u) { + if (DEBUG) { + Log.d(TAG, "setupActionBarHandlerOnError() called with: url = [" + u + "]"); + } Log.e("-----", "missing code"); } + /*////////////////////////////////////////////////////////////////////////// + // OwnStack + //////////////////////////////////////////////////////////////////////////*/ + private void setupActionBar(final StreamInfo info) { - if (DEBUG) Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]"); + if (DEBUG) { + Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]"); + } boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity) .getBoolean(activity.getString(R.string.use_external_video_player_key), false); - sortedVideoStreams = ListHelper.getSortedStreamVideosList( - activity, - info.getVideoStreams(), - info.getVideoOnlyStreams(), - false); - selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams); + sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), + info.getVideoOnlyStreams(), false); + selectedVideoStreamIndex = ListHelper + .getDefaultResolutionIndex(activity, sortedVideoStreams); - final StreamItemAdapter streamsAdapter = - new StreamItemAdapter<>(activity, - new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled); + final StreamItemAdapter streamsAdapter = new StreamItemAdapter<>( + activity, new StreamSizeWrapper<>(sortedVideoStreams, activity), + isExternalPlayerEnabled); spinnerToolbar.setAdapter(streamsAdapter); spinnerToolbar.setSelection(selectedVideoStreamIndex); spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { + public void onItemSelected(final AdapterView parent, final View view, + final int position, final long id) { selectedVideoStreamIndex = position; } @Override - public void onNothingSelected(AdapterView parent) { - } + public void onNothingSelected(final AdapterView parent) { } }); } - /*////////////////////////////////////////////////////////////////////////// - // OwnStack - //////////////////////////////////////////////////////////////////////////*/ - - /** - * Stack that contains the "navigation history".
- * The peek is the current video. - */ - protected final LinkedList stack = new LinkedList<>(); - - public void pushToStack(int serviceId, String videoUrl, String name) { + public void pushToStack(final int sid, final String videoUrl, final String title) { if (DEBUG) { Log.d(TAG, "pushToStack() called with: serviceId = [" - + serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "]"); + + sid + "], videoUrl = [" + videoUrl + "], title = [" + title + "]"); } if (stack.size() > 0 - && stack.peek().getServiceId() == serviceId + && stack.peek().getServiceId() == sid && stack.peek().getUrl().equals(videoUrl)) { Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = [" - + serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]"); + + sid + "], videoUrl == peek.getUrl = [" + videoUrl + "]"); return; } else { Log.d(TAG, "pushToStack() wasn't equal"); } - stack.push(new StackItem(serviceId, videoUrl, name)); + stack.push(new StackItem(sid, videoUrl, title)); } - public void setTitleToUrl(int serviceId, String videoUrl, String name) { - if (name != null && !name.isEmpty()) { + public void setTitleToUrl(final int sid, final String videoUrl, final String title) { + if (title != null && !title.isEmpty()) { for (StackItem stackItem : stack) { - if (stack.peek().getServiceId() == serviceId + if (stack.peek().getServiceId() == sid && stackItem.getUrl().equals(videoUrl)) { - stackItem.setTitle(name); + stackItem.setTitle(title); } } } @@ -713,20 +719,21 @@ public void setTitleToUrl(int serviceId, String videoUrl, String name) { @Override public boolean onBackPressed() { - if (DEBUG) Log.d(TAG, "onBackPressed() called"); + if (DEBUG) { + Log.d(TAG, "onBackPressed() called"); + } // That means that we are on the start of the stack, // return false to let the MainActivity handle the onBack - if (stack.size() <= 1) return false; + if (stack.size() <= 1) { + return false; + } // Remove top stack.pop(); // Get stack item from the new top StackItem peek = stack.peek(); - selectAndLoadVideo(peek.getServiceId(), - peek.getUrl(), - !TextUtils.isEmpty(peek.getTitle()) - ? peek.getTitle() - : ""); + selectAndLoadVideo(peek.getServiceId(), peek.getUrl(), + !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : ""); return true; } @@ -736,25 +743,32 @@ public boolean onBackPressed() { @Override protected void doInitialLoadLogic() { - if (currentInfo == null) prepareAndLoadInfo(); - else prepareAndHandleInfo(currentInfo, false); + if (currentInfo == null) { + prepareAndLoadInfo(); + } else { + prepareAndHandleInfo(currentInfo, false); + } } - public void selectAndLoadVideo(int serviceId, String videoUrl, String name) { - setInitialData(serviceId, videoUrl, name); + public void selectAndLoadVideo(final int sid, final String videoUrl, final String title) { + setInitialData(sid, videoUrl, title); prepareAndLoadInfo(); } - public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) { - if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" - + info + "], scrollToTop = [" + scrollToTop + "]"); + public void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) { + if (DEBUG) { + Log.d(TAG, "prepareAndHandleInfo() called with: " + + "info = [" + info + "], scrollToTop = [" + scrollToTop + "]"); + } setInitialData(info.getServiceId(), info.getUrl(), info.getName()); pushToStack(serviceId, url, name); showLoading(); initTabs(); - if (scrollToTop) appBarLayout.setExpanded(true, true); + if (scrollToTop) { + appBarLayout.setExpanded(true, true); + } handleResult(info); showContent(); @@ -767,12 +781,14 @@ protected void prepareAndLoadInfo() { } @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); initTabs(); currentInfo = null; - if (currentWorker != null) currentWorker.dispose(); + if (currentWorker != null) { + currentWorker.dispose(); + } currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad) .subscribeOn(Schedulers.io()) @@ -795,26 +811,29 @@ private void initTabs() { } pageAdapter.clearAllItems(); - if(shouldShowComments()){ - pageAdapter.addFragment(CommentsFragment.getInstance(serviceId, url, name), COMMENTS_TAB_TAG); + if (shouldShowComments()) { + pageAdapter.addFragment(CommentsFragment.getInstance(serviceId, url, name), + COMMENTS_TAB_TAG); } - if(showRelatedStreams && null == relatedStreamsLayout){ + if (showRelatedStreams && null == relatedStreamsLayout) { //temp empty fragment. will be updated in handleResult pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG); } - if(pageAdapter.getCount() == 0){ + if (pageAdapter.getCount() == 0) { pageAdapter.addFragment(new EmptyFragment(), EMPTY_TAB_TAG); } pageAdapter.notifyDataSetUpdate(); - if(pageAdapter.getCount() < 2){ + if (pageAdapter.getCount() < 2) { tabLayout.setVisibility(View.GONE); - }else{ + } else { int position = pageAdapter.getItemPositionByTitle(selectedTabTag); - if(position != -1) viewPager.setCurrentItem(position); + if (position != -1) { + viewPager.setCurrentItem(position); + } tabLayout.setVisibility(View.VISIBLE); } } @@ -859,9 +878,8 @@ private void openPopupPlayer(final boolean append) { NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue, false); } else { Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - final Intent intent = NavigationHelper.getPlayerIntent( - activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution, true - ); + final Intent intent = NavigationHelper.getPlayerIntent(activity, + PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution, true); activity.startService(intent); } } @@ -900,7 +918,7 @@ private void openNormalPlayer() { // Utils //////////////////////////////////////////////////////////////////////////*/ - public void setAutoplay(boolean autoplay) { + public void setAutoplay(final boolean autoplay) { this.autoPlayEnabled = autoplay; } @@ -913,7 +931,7 @@ private void startOnExternalPlayer(@NonNull final Context context, 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) )); } @@ -923,8 +941,9 @@ private VideoStream getSelectedVideoStream() { return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null; } - private void prepareDescription(Description description) { - if (TextUtils.isEmpty(description.getContent()) || description == Description.emptyDescription) { + private void prepareDescription(final Description description) { + if (TextUtils.isEmpty(description.getContent()) + || description == Description.emptyDescription) { return; } @@ -975,14 +994,16 @@ private void showContent() { contentRootLayoutHiding.setVisibility(View.VISIBLE); } - 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 : ""; } private void setErrorImage(final int imageResource) { - if (thumbnailImageView == null || activity == null) return; + if (thumbnailImageView == null || activity == null) { + return; + } thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource)); animateView(thumbnailImageView, false, 0, 0, @@ -990,11 +1011,12 @@ private void setErrorImage(final int imageResource) { } @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); } @@ -1009,7 +1031,7 @@ public void showLoading() { super.showLoading(); //if data is already cached, transition from VISIBLE -> INVISIBLE -> VISIBLE is not required - if(!ExtractorHelper.isCached(serviceId, url, InfoItem.InfoType.STREAM)){ + if (!ExtractorHelper.isCached(serviceId, url, InfoItem.InfoType.STREAM)) { contentRootLayoutHiding.setVisibility(View.INVISIBLE); } @@ -1028,33 +1050,35 @@ public void showLoading() { videoTitleToggleArrow.setVisibility(View.GONE); videoTitleRoot.setClickable(false); - if(relatedStreamsLayout != null){ - if(showRelatedStreams){ + if (relatedStreamsLayout != null) { + if (showRelatedStreams) { relatedStreamsLayout.setVisibility(View.INVISIBLE); - }else{ + } else { relatedStreamsLayout.setVisibility(View.GONE); } } - imageLoader.cancelDisplayTask(thumbnailImageView); - imageLoader.cancelDisplayTask(uploaderThumb); + IMAGE_LOADER.cancelDisplayTask(thumbnailImageView); + IMAGE_LOADER.cancelDisplayTask(uploaderThumb); thumbnailImageView.setImageBitmap(null); uploaderThumb.setImageBitmap(null); } @Override - public void handleResult(@NonNull StreamInfo info) { + public void handleResult(@NonNull final StreamInfo info) { super.handleResult(info); setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName()); - if(showRelatedStreams){ - if(null == relatedStreamsLayout){ //phone - pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(currentInfo)); + if (showRelatedStreams) { + if (null == relatedStreamsLayout) { //phone + pageAdapter.updateItem(RELATED_TAB_TAG, + RelatedVideosFragment.getInstance(currentInfo)); pageAdapter.notifyDataSetUpdate(); - }else{ //tablet + } else { //tablet getChildFragmentManager().beginTransaction() - .replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(currentInfo)) + .replace(R.id.relatedStreamsLayout, + RelatedVideosFragment.getInstance(currentInfo)) .commitNow(); relatedStreamsLayout.setVisibility(View.VISIBLE); } @@ -1078,9 +1102,11 @@ public void handleResult(@NonNull StreamInfo info) { 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.localizeWatchingCount(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 { @@ -1096,7 +1122,8 @@ public void handleResult(@NonNull StreamInfo info) { 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 { @@ -1136,7 +1163,8 @@ public void handleResult(@NonNull StreamInfo info) { 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); @@ -1168,9 +1196,12 @@ public void handleResult(@NonNull StreamInfo info) { spinnerToolbar.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); spinnerToolbar.setVisibility(View.GONE); @@ -1187,28 +1218,28 @@ public void handleResult(@NonNull StreamInfo info) { public void openDownloadDialog() { - try { - DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); - downloadDialog.setVideoStreams(sortedVideoStreams); - downloadDialog.setAudioStreams(currentInfo.getAudioStreams()); - downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); - downloadDialog.setSubtitleStreams(currentInfo.getSubtitles()); - - downloadDialog.show(getActivity().getSupportFragmentManager(), "downloadDialog"); - } catch (Exception e) { - ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, - ServiceList.all() - .get(currentInfo - .getServiceId()) - .getServiceInfo() - .getName(), "", - R.string.could_not_setup_download_menu); - - ErrorActivity.reportError(getActivity(), - e, - getActivity().getClass(), - getActivity().findViewById(android.R.id.content), info); - } + try { + DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); + downloadDialog.setVideoStreams(sortedVideoStreams); + downloadDialog.setAudioStreams(currentInfo.getAudioStreams()); + downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); + downloadDialog.setSubtitleStreams(currentInfo.getSubtitles()); + + downloadDialog.show(getActivity().getSupportFragmentManager(), "downloadDialog"); + } catch (Exception e) { + ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, + ServiceList.all() + .get(currentInfo + .getServiceId()) + .getServiceInfo() + .getName(), "", + R.string.could_not_setup_download_menu); + + ErrorActivity.reportError(getActivity(), + e, + getActivity().getClass(), + getActivity().findViewById(android.R.id.content), info); + } } /*////////////////////////////////////////////////////////////////////////// @@ -1216,12 +1247,16 @@ public void openDownloadDialog() { //////////////////////////////////////////////////////////////////////////*/ @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 YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error - : exception instanceof ExtractionException ? R.string.parsing_error - : R.string.general_error; + 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); @@ -1234,9 +1269,9 @@ private void updateProgressInfo(@NonNull final StreamInfo info) { 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); if (!playbackResumeEnabled || info.getDuration() <= 0) { positionView.setVisibility(View.INVISIBLE); @@ -1244,8 +1279,8 @@ private void updateProgressInfo(@NonNull final StreamInfo info) { // 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)) { + if (!info.getStreamType().equals(StreamType.LIVE_STREAM) + && !info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) { return; } } @@ -1258,14 +1293,17 @@ private void updateProgressInfo(@NonNull final StreamInfo info) { .onErrorComplete() .observeOn(AndroidSchedulers.mainThread()) .subscribe(state -> { - final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()); + final int seconds + = (int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()); positionView.setMax((int) info.getDuration()); positionView.setProgressAnimated(seconds); detailPositionView.setText(Localization.getDurationString(seconds)); animateView(positionView, true, 500); animateView(detailPositionView, true, 500); }, e -> { - if (DEBUG) e.printStackTrace(); + if (DEBUG) { + e.printStackTrace(); + } }, () -> { animateView(positionView, false, 500); animateView(detailPositionView, false, 500); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index d55bf3f40..68937f078 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -7,16 +7,17 @@ import android.content.res.Resources; import android.os.Bundle; import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; + import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; @@ -40,24 +41,26 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; -public abstract class BaseListFragment extends BaseStateFragment implements ListViewContract, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener { - +public abstract class BaseListFragment extends BaseStateFragment + implements ListViewContract, StateSaver.WriteRead, + SharedPreferences.OnSharedPreferenceChangeListener { /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ + private static final int LIST_MODE_UPDATE_FLAG = 0x32; protected InfoListAdapter infoListAdapter; protected RecyclerView itemsList; - private int updateFlags = 0; - - private static final int LIST_MODE_UPDATE_FLAG = 0x32; + protected StateSaver.SavedState savedState; /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ + private boolean useDefaultStateSaving = true; + private int updateFlags = 0; @Override - public void onAttach(Context context) { + public void onAttach(final Context context) { super.onAttach(context); if (infoListAdapter == null) { @@ -71,17 +74,23 @@ public void onDetach() { } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); PreferenceManager.getDefaultSharedPreferences(activity) .registerOnSharedPreferenceChangeListener(this); } + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onDestroy() { super.onDestroy(); - if (useDefaultStateSaving) StateSaver.onDestroy(savedState); + if (useDefaultStateSaving) { + StateSaver.onDestroy(savedState); + } PreferenceManager.getDefaultSharedPreferences(activity) .unregisterOnSharedPreferenceChangeListener(this); } @@ -93,28 +102,23 @@ public void onResume() { if (updateFlags != 0) { if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { final boolean useGrid = isGridLayout(); - itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); - infoListAdapter.setGridItemVariants(useGrid); + itemsList.setLayoutManager(useGrid + ? getGridLayoutManager() : getListLayoutManager()); + infoListAdapter.setUseGridVariant(useGrid); infoListAdapter.notifyDataSetChanged(); } updateFlags = 0; } } - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - - protected StateSaver.SavedState savedState; - protected boolean useDefaultStateSaving = true; - /** * If the default implementation of {@link StateSaver.WriteRead} should be used. * * @see StateSaver + * @param useDefaultStateSaving Whether the default implementation should be used */ - public void useDefaultStateSaving(boolean useDefault) { - this.useDefaultStateSaving = useDefault; + public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) { + this.useDefaultStateSaving = useDefaultStateSaving; } @Override @@ -124,13 +128,15 @@ public String generateSuffix() { } @Override - public void writeTo(Queue objectsToSave) { - if (useDefaultStateSaving) objectsToSave.add(infoListAdapter.getItemsList()); + public void writeTo(final Queue objectsToSave) { + if (useDefaultStateSaving) { + objectsToSave.add(infoListAdapter.getItemsList()); + } } @Override @SuppressWarnings("unchecked") - public void readFrom(@NonNull Queue savedObjects) throws Exception { + public void readFrom(@NonNull final Queue savedObjects) throws Exception { if (useDefaultStateSaving) { infoListAdapter.getItemsList().clear(); infoListAdapter.getItemsList().addAll((List) savedObjects.poll()); @@ -138,15 +144,20 @@ public void readFrom(@NonNull Queue savedObjects) throws Exception { } @Override - public void onSaveInstanceState(Bundle bundle) { + public void onSaveInstanceState(final Bundle bundle) { super.onSaveInstanceState(bundle); - if (useDefaultStateSaving) savedState = StateSaver.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this); + if (useDefaultStateSaving) { + savedState = StateSaver + .tryToSave(activity.isChangingConfigurations(), savedState, bundle, this); + } } @Override - protected void onRestoreInstanceState(@NonNull Bundle bundle) { + protected void onRestoreInstanceState(@NonNull final Bundle bundle) { super.onRestoreInstanceState(bundle); - if (useDefaultStateSaving) savedState = StateSaver.tryToRestore(bundle, this); + if (useDefaultStateSaving) { + savedState = StateSaver.tryToRestore(bundle, this); + } } /*////////////////////////////////////////////////////////////////////////// @@ -169,29 +180,32 @@ 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 @@ -199,19 +213,19 @@ protected void initListeners() { super.initListeners(); infoListAdapter.setOnStreamSelectedListener(new OnClickGesture() { @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() { @Override - public void selected(ChannelInfoItem selectedItem) { + public void selected(final ChannelInfoItem selectedItem) { try { onItemSelected(selectedItem); NavigationHelper.openChannelFragment(getFM(), @@ -226,7 +240,7 @@ public void selected(ChannelInfoItem selectedItem) { infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture() { @Override - public void selected(PlaylistInfoItem selectedItem) { + public void selected(final PlaylistInfoItem selectedItem) { try { onItemSelected(selectedItem); NavigationHelper.openPlaylistFragment(getFM(), @@ -241,7 +255,7 @@ public void selected(PlaylistInfoItem selectedItem) { infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture() { @Override - public void selected(CommentsInfoItem selectedItem) { + public void selected(final CommentsInfoItem selectedItem) { onItemSelected(selectedItem); } }); @@ -249,13 +263,13 @@ public void selected(CommentsInfoItem selectedItem) { itemsList.clearOnScrollListeners(); itemsList.addOnScrollListener(new OnScrollBelowItemsListener() { @Override - public void onScrolledDown(RecyclerView recyclerView) { + public void onScrolledDown(final RecyclerView recyclerView) { onScrollToBottom(); } }); } - private void onStreamSelected(StreamInfoItem selectedItem) { + private void onStreamSelected(final StreamInfoItem selectedItem) { onItemSelected(selectedItem); NavigationHelper.openVideoDetailFragment(getFM(), selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName()); @@ -268,12 +282,12 @@ protected void onScrollToBottom() { } - - protected void showStreamDialog(final StreamInfoItem item) { final Context context = getContext(); final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) return; + if (context == null || context.getResources() == null || activity == null) { + return; + } if (item.getStreamType() == StreamType.AUDIO_STREAM) { StreamDialogEntry.setEnabledEntries( @@ -291,8 +305,8 @@ protected void showStreamDialog(final StreamInfoItem item) { StreamDialogEntry.share); } - new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) -> - StreamDialogEntry.clickOn(which, this, item)).show(); + new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), + (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show(); } /*////////////////////////////////////////////////////////////////////////// @@ -300,8 +314,11 @@ protected void showStreamDialog(final StreamInfoItem item) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } super.onCreateOptionsMenu(menu, inflater); ActionBar supportActionBar = activity.getSupportActionBar(); if (supportActionBar != null) { @@ -339,7 +356,7 @@ public void hideLoading() { } @Override - public void showError(String message, boolean showRetryButton) { + public void showError(final String message, final boolean showRetryButton) { super.showError(message, showRetryButton); showListFooter(false); animateView(itemsList, false, 200); @@ -361,25 +378,28 @@ public void showListFooter(final boolean show) { } @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); } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 9a8e1fd17..ce379124d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -21,7 +21,6 @@ public abstract class BaseListInfoFragment extends BaseListFragment { - @State protected int serviceId = Constants.NO_SERVICE_ID; @State @@ -34,7 +33,7 @@ public abstract class BaseListInfoFragment protected Disposable currentWorker; @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); setTitle(name); showListFooter(hasMoreItems()); @@ -43,7 +42,9 @@ protected void initViews(View rootView, Bundle savedInstanceState) { @Override public void onPause() { super.onPause(); - if (currentWorker != null) currentWorker.dispose(); + if (currentWorker != null) { + currentWorker.dispose(); + } } @Override @@ -73,7 +74,7 @@ public void onDestroy() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void writeTo(Queue objectsToSave) { + public void writeTo(final Queue objectsToSave) { super.writeTo(objectsToSave); objectsToSave.add(currentInfo); objectsToSave.add(currentNextPageUrl); @@ -81,7 +82,7 @@ public void writeTo(Queue objectsToSave) { @Override @SuppressWarnings("unchecked") - public void readFrom(@NonNull Queue savedObjects) throws Exception { + public void readFrom(@NonNull final Queue savedObjects) throws Exception { super.readFrom(savedObjects); currentInfo = (I) savedObjects.poll(); currentNextPageUrl = (String) savedObjects.poll(); @@ -92,10 +93,14 @@ public void readFrom(@NonNull Queue savedObjects) throws Exception { //////////////////////////////////////////////////////////////////////////*/ protected void doInitialLoadLogic() { - if (DEBUG) Log.d(TAG, "doInitialLoadLogic() called"); + if (DEBUG) { + Log.d(TAG, "doInitialLoadLogic() called"); + } if (currentInfo == null) { startLoading(false); - } else handleResult(currentInfo); + } else { + handleResult(currentInfo); + } } /** @@ -103,18 +108,21 @@ protected void doInitialLoadLogic() { * 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 loadResult(boolean forceLoad); @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); showListFooter(false); infoListAdapter.clearStreamItemList(); currentInfo = null; - if (currentWorker != null) currentWorker.dispose(); + if (currentWorker != null) { + currentWorker.dispose(); + } currentWorker = loadResult(forceLoad) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -127,19 +135,25 @@ public void startLoading(boolean forceLoad) { } /** - * Implement the logic to load more items
- * You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper} + * Implement the logic to load more items. + *

You can use the default implementations + * from {@link org.schabi.newpipe.util.ExtractorHelper}.

+ * + * @return Rx {@link Single} containing the {@link ListExtractor.InfoItemsPage} */ protected abstract Single loadMoreItemsLogic(); protected void loadMoreItems() { isLoading.set(true); - if (currentWorker != null) currentWorker.dispose(); + if (currentWorker != null) { + currentWorker.dispose(); + } currentWorker = loadMoreItemsLogic() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> { + .subscribe((@io.reactivex.annotations.NonNull + ListExtractor.InfoItemsPage InfoItemsPage) -> { isLoading.set(false); handleNextItems(InfoItemsPage); }, (@io.reactivex.annotations.NonNull Throwable throwable) -> { @@ -149,7 +163,7 @@ protected void loadMoreItems() { } @Override - public void handleNextItems(ListExtractor.InfoItemsPage result) { + public void handleNextItems(final ListExtractor.InfoItemsPage result) { super.handleNextItems(result); currentNextPageUrl = result.getNextPageUrl(); infoListAdapter.addInfoItemList(result.getItems()); @@ -167,7 +181,7 @@ protected boolean hasMoreItems() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void handleResult(@NonNull I result) { + public void handleResult(@NonNull final I result) { super.handleResult(result); name = result.getName(); @@ -188,9 +202,9 @@ public void handleResult(@NonNull I result) { // 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 : ""; } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 40df990f9..0cd7fe32c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -4,10 +4,6 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.appcompat.app.ActionBar; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -21,6 +17,11 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.core.content.ContextCompat; + import com.jakewharton.rxbinding2.view.RxView; import org.schabi.newpipe.R; @@ -29,7 +30,6 @@ import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; -import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; @@ -63,15 +63,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; public class ChannelFragment extends BaseListInfoFragment { - + private static final int BUTTON_DEBOUNCE_INTERVAL = 100; private final CompositeDisposable disposables = new CompositeDisposable(); private Disposable subscribeButtonMonitor; - private SubscriptionManager subscriptionManager; /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ - + private SubscriptionManager subscriptionManager; private View headerRootLayout; private ImageView headerChannelBanner; private ImageView headerAvatarView; @@ -79,25 +78,24 @@ public class ChannelFragment extends BaseListInfoFragment { private TextView headerSubscribersTextView; private Button headerSubscribeButton; private View playlistCtrl; - private LinearLayout headerPlayAllButton; private LinearLayout headerPopupButton; private LinearLayout headerBackgroundButton; - private MenuItem menuRssButton; - public static ChannelFragment getInstance(int serviceId, String url, String name) { + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + + public static ChannelFragment getInstance(final int serviceId, final String url, + final String name) { ChannelFragment instance = new ChannelFragment(); instance.setInitialData(serviceId, url, name); return instance; } - /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { + public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (activity != null && useAsFrontPage @@ -107,29 +105,40 @@ public void setUserVisibleHint(boolean isVisibleToUser) { } @Override - public void onAttach(Context context) { + public void onAttach(final Context context) { super.onAttach(context); subscriptionManager = new SubscriptionManager(activity); } @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_channel, container, false); } + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + @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(); + } } /*////////////////////////////////////////////////////////////////////////// - // Init + // Menu //////////////////////////////////////////////////////////////////////////*/ protected View getListHeader() { - headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, itemsList, false); + headerRootLayout = activity.getLayoutInflater() + .inflate(R.layout.channel_header, itemsList, false); headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image); headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view); headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view); @@ -145,12 +154,8 @@ protected View getListHeader() { return headerRootLayout; } - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); ActionBar supportActionBar = activity.getSupportActionBar(); if (useAsFrontPage && supportActionBar != null) { @@ -158,8 +163,10 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } else { inflater.inflate(R.menu.menu_channel, menu); - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + - "], inflater = [" + inflater + "]"); + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } menuRssButton = menu.findItem(R.id.menu_item_rss); } } @@ -172,8 +179,12 @@ private void openRssFeed() { } } + /*////////////////////////////////////////////////////////////////////////// + // Channel Subscription + //////////////////////////////////////////////////////////////////////////*/ + @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: NavigationHelper.openSettings(requireContext()); @@ -197,22 +208,16 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } - /*////////////////////////////////////////////////////////////////////////// - // Channel Subscription - //////////////////////////////////////////////////////////////////////////*/ - - private static final int BUTTON_DEBOUNCE_INTERVAL = 100; - private void monitorSubscription(final ChannelInfo info) { final Consumer 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> observable = subscriptionManager.subscriptionTable() + final Observable> observable = subscriptionManager + .subscriptionTable() .getSubscriptionFlowable(info.getServiceId(), info.getUrl()) .toObservable(); @@ -221,17 +226,19 @@ private void monitorSubscription(final ChannelInfo info) { .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 subscriptionEntities) -> - updateSubscribeButton(!subscriptionEntities.isEmpty()) - , onError)); + updateSubscribeButton(!subscriptionEntities.isEmpty()), onError)); } - private Function mapOnSubscribe(final SubscriptionEntity subscription, ChannelInfo info) { + private Function mapOnSubscribe(final SubscriptionEntity subscription, + final ChannelInfo info) { return (@NonNull Object o) -> { subscriptionManager.insertSubscription(subscription, info); return o; @@ -246,9 +253,13 @@ private Function mapOnUnsubscribe(final SubscriptionEntity subsc } 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 onError = (@NonNull Throwable throwable) -> @@ -264,9 +275,12 @@ private void updateSubscription(final ChannelInfo info) { .subscribe(onComplete, onError)); } - private Disposable monitorSubscribeButton(final Button subscribeButton, final Function action) { + private Disposable monitorSubscribeButton(final Button subscribeButton, + final Function action) { final Consumer 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 onError = (@NonNull Throwable throwable) -> @@ -287,12 +301,18 @@ private Disposable monitorSubscribeButton(final Button subscribeButton, final Fu private Consumer> getSubscribeUpdateMonitor(final ChannelInfo info) { return (List subscriptionEntities) -> { - if (DEBUG) - Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]"); - if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); + if (DEBUG) { + Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: " + + "subscriptionEntities = [" + subscriptionEntities + "]"); + } + if (subscribeButtonMonitor != null) { + subscribeButtonMonitor.dispose(); + } if (subscriptionEntities.isEmpty()) { - if (DEBUG) Log.d(TAG, "No subscription to this channel!"); + if (DEBUG) { + Log.d(TAG, "No subscription to this channel!"); + } SubscriptionEntity channel = new SubscriptionEntity(); channel.setServiceId(info.getServiceId()); channel.setUrl(info.getUrl()); @@ -300,34 +320,45 @@ private Consumer> getSubscribeUpdateMonitor(final Chann info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount()); - subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel, info)); + subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, + mapOnSubscribe(channel, info)); } else { - if (DEBUG) Log.d(TAG, "Found subscription to this channel!"); + if (DEBUG) { + Log.d(TAG, "Found subscription to this channel!"); + } final SubscriptionEntity subscription = subscriptionEntities.get(0); - subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription)); + subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, + mapOnUnsubscribe(subscription)); } }; } - private void updateSubscribeButton(boolean isSubscribed) { - if (DEBUG) Log.d(TAG, "updateSubscribeButton() called with: isSubscribed = [" + isSubscribed + "]"); + private void updateSubscribeButton(final boolean isSubscribed) { + if (DEBUG) { + Log.d(TAG, "updateSubscribeButton() called with: " + + "isSubscribed = [" + isSubscribed + "]"); + } boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE; int backgroundDuration = isButtonVisible ? 300 : 0; int textDuration = isButtonVisible ? 200 : 0; - int subscribeBackground = ContextCompat.getColor(activity, R.color.subscribe_background_color); + int subscribeBackground = ContextCompat + .getColor(activity, R.color.subscribe_background_color); int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color); - int subscribedBackground = ContextCompat.getColor(activity, R.color.subscribed_background_color); + int subscribedBackground = ContextCompat + .getColor(activity, R.color.subscribed_background_color); int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color); if (!isSubscribed) { headerSubscribeButton.setText(R.string.subscribe_button_title); - animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground, subscribeBackground); + animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground, + subscribeBackground); animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText); } else { headerSubscribeButton.setText(R.string.subscribed_button_title); - animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground, subscribedBackground); + animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground, + subscribedBackground); animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText); } @@ -344,7 +375,7 @@ protected Single loadMoreItemsLogic() { } @Override - protected Single loadResult(boolean forceLoad) { + protected Single loadResult(final boolean forceLoad) { return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad); } @@ -356,47 +387,55 @@ protected Single loadResult(boolean forceLoad) { public void showLoading() { super.showLoading(); - imageLoader.cancelDisplayTask(headerChannelBanner); - imageLoader.cancelDisplayTask(headerAvatarView); + IMAGE_LOADER.cancelDisplayTask(headerChannelBanner); + IMAGE_LOADER.cancelDisplayTask(headerAvatarView); animateView(headerSubscribeButton, false, 100); } @Override - public void handleResult(@NonNull ChannelInfo result) { + public void handleResult(@NonNull final ChannelInfo result) { super.handleResult(result); headerRootLayout.setVisibility(View.VISIBLE); - imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner, + IMAGE_LOADER.displayImage(result.getBannerUrl(), headerChannelBanner, ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); - imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, + IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); headerSubscribersTextView.setVisibility(View.VISIBLE); if (result.getSubscriberCount() >= 0) { - headerSubscribersTextView.setText(Localization.shortSubscriberCount(activity, result.getSubscriberCount())); + headerSubscribersTextView.setText(Localization + .shortSubscriberCount(activity, result.getSubscriberCount())); } else { headerSubscribersTextView.setText(R.string.subscribers_count_not_available); } - if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); + if (menuRssButton != null) { + menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); + } playlistCtrl.setVisibility(View.VISIBLE); if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, + NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); } - if (disposables != null) disposables.clear(); - if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); + if (disposables != null) { + disposables.clear(); + } + if (subscribeButtonMonitor != null) { + subscribeButtonMonitor.dispose(); + } updateSubscription(result); monitorSubscription(result); - headerPlayAllButton.setOnClickListener( - view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); - headerPopupButton.setOnClickListener( - view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); - headerBackgroundButton.setOnClickListener( - view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); + headerPlayAllButton.setOnClickListener(view -> NavigationHelper + .playOnMainPlayer(activity, getPlayQueue(), false)); + headerPopupButton.setOnClickListener(view -> NavigationHelper + .playOnPopupPlayer(activity, getPlayQueue(), false)); + headerBackgroundButton.setOnClickListener(view -> NavigationHelper + .playOnBackgroundPlayer(activity, getPlayQueue(), false)); } private PlayQueue getPlayQueue() { @@ -410,17 +449,12 @@ private PlayQueue getPlayQueue(final int index) { streamItems.add((StreamInfoItem) i); } } - return new ChannelPlayQueue( - currentInfo.getServiceId(), - currentInfo.getUrl(), - currentInfo.getNextPageUrl(), - streamItems, - index - ); + return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(), + currentInfo.getNextPageUrl(), streamItems, index); } @Override - public void handleNextItems(ListExtractor.InfoItemsPage result) { + public void handleNextItems(final ListExtractor.InfoItemsPage result) { super.handleNextItems(result); if (!result.getErrors().isEmpty()) { @@ -437,8 +471,10 @@ public void handleNextItems(ListExtractor.InfoItemsPage 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; + } int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error; @@ -454,8 +490,10 @@ protected boolean onError(Throwable exception) { //////////////////////////////////////////////////////////////////////////*/ @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); + } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java index edaf0ec2b..d23293c8a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java @@ -2,14 +2,15 @@ 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.disposables.CompositeDisposable; public class CommentsFragment extends BaseListInfoFragment { - private CompositeDisposable disposables = new CompositeDisposable(); - /*////////////////////////////////////////////////////////////////////////// - // Views - //////////////////////////////////////////////////////////////////////////*/ - - private boolean mIsVisibleToUser = false; - public static CommentsFragment getInstance(int serviceId, String url, String name) { + public static CommentsFragment getInstance(final int serviceId, final String url, + final String name) { CommentsFragment instance = new CommentsFragment(); instance.setInitialData(serviceId, url, name); return instance; @@ -44,28 +40,31 @@ public static CommentsFragment getInstance(int serviceId, String url, String nam //////////////////////////////////////////////////////////////////////////*/ @Override - public void setUserVisibleHint(boolean isVisibleToUser) { + public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); mIsVisibleToUser = isVisibleToUser; } @Override - public void onAttach(Context context) { + public void onAttach(final Context context) { super.onAttach(context); } @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_comments, container, false); } @Override public void onDestroy() { super.onDestroy(); - if (disposables != null) disposables.clear(); + if (disposables != null) { + disposables.clear(); + } } - /*////////////////////////////////////////////////////////////////////////// // Load and handle //////////////////////////////////////////////////////////////////////////*/ @@ -76,7 +75,7 @@ protected Single loadMoreItemsLogic() { } @Override - protected Single loadResult(boolean forceLoad) { + protected Single loadResult(final boolean forceLoad) { return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); } @@ -90,27 +89,28 @@ public void showLoading() { } @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 void handleNextItems(ListExtractor.InfoItemsPage 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; + } 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 @@ protected boolean onError(Throwable exception) { //////////////////////////////////////////////////////////////////////////*/ @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() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java index 35b68b094..0702553ad 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java @@ -10,9 +10,8 @@ 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 void onResume() { super.onResume(); if (serviceId != ServiceHelper.getSelectedServiceId(requireContext())) { - if (currentWorker != null) currentWorker.dispose(); + if (currentWorker != null) { + currentWorker.dispose(); + } updateSelectedDefaultKiosk(); reloadContent(); } @@ -45,7 +46,8 @@ private void updateSelectedDefaultKiosk() { currentInfo = null; currentNextPageUrl = null; } catch (ExtractionException e) { - onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none", "Loading default kiosk from selected service", 0); + onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none", + "Loading default kiosk from selected service", 0); } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java index d082b8078..21a7944ee 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -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 @@ /** * Created by Christian Schabesberger on 23.09.17. - * + *

* Copyright (C) Christian Schabesberger 2017 * KioskFragment.java is part of NewPipe. - * + *

+ *

* 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. - * + *

+ *

* 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. - * + *

+ *

* You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . + * along with NewPipe. If not, see . + *

*/ public class KioskFragment extends BaseListInfoFragment { - @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 static KioskFragment getInstance(int serviceId, String kioskId) //////////////////////////////////////////////////////////////////////////*/ @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 void onCreate(Bundle savedInstanceState) { } @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 void setUserVisibleHint(boolean isVisibleToUser) { } @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 void onResume() { //////////////////////////////////////////////////////////////////////////*/ @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 void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { //////////////////////////////////////////////////////////////////////////*/ @Override - public Single loadResult(boolean forceReload) { + public Single loadResult(final boolean forceReload) { contentCountry = Localization.getPreferredContentCountry(requireContext()); - return ExtractorHelper.getKioskInfo(serviceId, - url, - forceReload); + return ExtractorHelper.getKioskInfo(serviceId, url, forceReload); } @Override public Single loadMoreItemsLogic() { - return ExtractorHelper.getMoreKioskItems(serviceId, - url, - currentNextPageUrl); + return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl); } /*////////////////////////////////////////////////////////////////////////// @@ -181,13 +178,13 @@ public void handleResult(@NonNull final KioskInfo result) { } @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); } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index a992cd7ba..68836bbd0 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -3,9 +3,6 @@ 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.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; @@ -57,7 +58,6 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; public class PlaylistFragment extends BaseListInfoFragment { - private CompositeDisposable disposables; private Subscription bookmarkReactor; private AtomicBoolean isBookmarkButtonReady; @@ -82,7 +82,8 @@ public class PlaylistFragment extends BaseListInfoFragment { private MenuItem playlistBookmarkButton; - public static PlaylistFragment getInstance(int serviceId, String url, String name) { + public static PlaylistFragment getInstance(final int serviceId, final String url, + final String name) { PlaylistFragment instance = new PlaylistFragment(); instance.setInitialData(serviceId, url, name); return instance; @@ -93,17 +94,18 @@ public static PlaylistFragment getInstance(int serviceId, String url, String nam //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); disposables = new CompositeDisposable(); isBookmarkButtonReady = new AtomicBoolean(false); - remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase.getInstance( - requireContext())); + remotePlaylistManager = new RemotePlaylistManager(NewPipeDatabase + .getInstance(requireContext())); } @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_playlist, container, false); } @@ -112,7 +114,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c //////////////////////////////////////////////////////////////////////////*/ protected View getListHeader() { - headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_header, itemsList, false); + headerRootLayout = activity.getLayoutInflater() + .inflate(R.layout.playlist_header, itemsList, false); headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view); headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout); headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name); @@ -129,21 +132,23 @@ protected View getListHeader() { } @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - infoListAdapter.useMiniItemVariants(true); + infoListAdapter.setUseMiniVariant(true); } - private PlayQueue getPlayQueueStartingAt(StreamInfoItem infoItem) { + private PlayQueue getPlayQueueStartingAt(final StreamInfoItem infoItem) { return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0)); } @Override - protected void showStreamDialog(StreamInfoItem item) { + protected void showStreamDialog(final StreamInfoItem item) { final Context context = getContext(); final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) return; + if (context == null || context.getResources() == null || activity == null) { + return; + } if (item.getStreamType() == StreamType.AUDIO_STREAM) { StreamDialogEntry.setEnabledEntries( @@ -160,21 +165,25 @@ protected void showStreamDialog(StreamInfoItem item) { StreamDialogEntry.append_playlist, StreamDialogEntry.share); - StreamDialogEntry.start_here_on_popup.setCustomAction( - (fragment, infoItem) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(infoItem), true)); + StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItem) -> + NavigationHelper.playOnPopupPlayer(context, + getPlayQueueStartingAt(infoItem), true)); } - StreamDialogEntry.start_here_on_background.setCustomAction( - (fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(infoItem), true)); + StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) -> + NavigationHelper.playOnBackgroundPlayer(context, + getPlayQueueStartingAt(infoItem), true)); - new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) -> - StreamDialogEntry.clickOn(which, this, item)).show(); + new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), + (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show(); } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + - "], inflater = [" + inflater + "]"); + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_playlist, menu); @@ -185,10 +194,16 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @Override public void onDestroyView() { super.onDestroyView(); - if (isBookmarkButtonReady != null) isBookmarkButtonReady.set(false); + if (isBookmarkButtonReady != null) { + isBookmarkButtonReady.set(false); + } - if (disposables != null) disposables.clear(); - if (bookmarkReactor != null) bookmarkReactor.cancel(); + if (disposables != null) { + disposables.clear(); + } + if (bookmarkReactor != null) { + bookmarkReactor.cancel(); + } bookmarkReactor = null; } @@ -197,7 +212,9 @@ public void onDestroyView() { public void onDestroy() { super.onDestroy(); - if (disposables != null) disposables.dispose(); + if (disposables != null) { + disposables.dispose(); + } disposables = null; remotePlaylistManager = null; @@ -215,12 +232,12 @@ protected Single loadMoreItemsLogic() { } @Override - protected Single loadResult(boolean forceLoad) { + protected Single loadResult(final boolean forceLoad) { return ExtractorHelper.getPlaylistInfo(serviceId, url, forceLoad); } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: NavigationHelper.openSettings(requireContext()); @@ -251,7 +268,7 @@ public void showLoading() { animateView(headerRootLayout, false, 200); animateView(itemsList, false, 100); - imageLoader.cancelDisplayTask(headerUploaderAvatar); + IMAGE_LOADER.cancelDisplayTask(headerUploaderAvatar); animateView(headerUploaderLayout, false, 200); } @@ -262,7 +279,8 @@ public void handleResult(@NonNull final PlaylistInfo result) { animateView(headerRootLayout, true, 100); animateView(headerUploaderLayout, true, 300); headerUploaderLayout.setOnClickListener(null); - if (!TextUtils.isEmpty(result.getUploaderName())) { // If we have an uploader : Put them into the ui + // If we have an uploader put them into the UI + if (!TextUtils.isEmpty(result.getUploaderName())) { headerUploaderName.setText(result.getUploaderName()); if (!TextUtils.isEmpty(result.getUploaderUrl())) { headerUploaderLayout.setOnClickListener(v -> { @@ -276,19 +294,20 @@ public void handleResult(@NonNull final PlaylistInfo result) { } }); } - } else { // Else : say we have no uploader + } else { // Else say we have no uploader headerUploaderName.setText(R.string.playlist_no_uploader); } playlistCtrl.setVisibility(View.VISIBLE); - imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, + IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.getStreamCount(), (int) result.getStreamCount())); if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, + NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); } remotePlaylistManager.getPlaylist(result) @@ -321,8 +340,8 @@ private PlayQueue getPlayQueue() { private PlayQueue getPlayQueue(final int index) { final List infoItems = new ArrayList<>(); - for(InfoItem i : infoListAdapter.getItemsList()) { - if(i instanceof StreamInfoItem) { + for (InfoItem i : infoListAdapter.getItemsList()) { + if (i instanceof StreamInfoItem) { infoItems.add((StreamInfoItem) i); } } @@ -336,12 +355,12 @@ private PlayQueue getPlayQueue(final int 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); } } @@ -350,15 +369,15 @@ public void handleNextItems(ListExtractor.InfoItemsPage result) { //////////////////////////////////////////////////////////////////////////*/ @Override - protected boolean onError(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); + 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); return true; } @@ -366,13 +385,18 @@ protected boolean onError(Throwable exception) { // Utils //////////////////////////////////////////////////////////////////////////*/ - private Flowable getUpdateProcessor(@NonNull List playlists, - @NonNull PlaylistInfo result) { + private Flowable getUpdateProcessor( + @NonNull final List playlists, + @NonNull final PlaylistInfo result) { final Flowable noItemToUpdate = Flowable.just(/*noItemToUpdate=*/-1); - if (playlists.isEmpty()) return noItemToUpdate; + if (playlists.isEmpty()) { + return noItemToUpdate; + } - final PlaylistRemoteEntity playlistEntity = playlists.get(0); - if (playlistEntity.isIdenticalTo(result)) return noItemToUpdate; + final PlaylistRemoteEntity playlistRemoteEntity = playlists.get(0); + if (playlistRemoteEntity.isIdenticalTo(result)) { + return noItemToUpdate; + } return remotePlaylistManager.onUpdate(playlists.get(0).getUid(), result).toFlowable(); } @@ -380,56 +404,59 @@ private Flowable getUpdateProcessor(@NonNull List private Subscriber> getPlaylistBookmarkSubscriber() { return new Subscriber>() { @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 playlist) { + public void onNext(final List playlist) { playlistEntity = playlist.isEmpty() ? null : playlist.get(0); updateBookmarkButtons(); isBookmarkButtonReady.set(true); - if (bookmarkReactor != null) bookmarkReactor.request(1); + if (bookmarkReactor != null) { + bookmarkReactor.request(1); + } } @Override - public void onError(Throwable t) { + public void onError(final Throwable t) { PlaylistFragment.this.onError(t); } @Override - public void onComplete() { - - } + public void onComplete() { } }; } @Override - public void setTitle(String title) { + public void setTitle(final String title) { super.setTitle(title); headerTitleView.setText(title); } private void onBookmarkClicked() { - if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get() || - remotePlaylistManager == null) + if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get() + || remotePlaylistManager == null) { return; + } final Disposable action; if (currentInfo != null && playlistEntity == null) { action = remotePlaylistManager.onBookmark(currentInfo) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> {/* Do nothing */}, this::onError); + .subscribe(ignored -> { /* Do nothing */ }, this::onError); } else if (playlistEntity != null) { action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid()) .observeOn(AndroidSchedulers.mainThread()) .doFinally(() -> playlistEntity = null) - .subscribe(ignored -> {/* Do nothing */}, this::onError); + .subscribe(ignored -> { /* Do nothing */ }, this::onError); } else { action = Disposables.empty(); } @@ -438,13 +465,15 @@ private void onBookmarkClicked() { } 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); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index bde6920d6..ce84c1b57 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -6,13 +6,6 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.TooltipCompat; -import androidx.recyclerview.widget.ItemTouchHelper; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -30,6 +23,14 @@ import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.TooltipCompat; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; @@ -40,7 +41,6 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchInfo; -import org.schabi.newpipe.util.FireTvUtils; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -49,6 +49,7 @@ import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.FireTvUtils; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ServiceHelper; @@ -77,8 +78,7 @@ import static java.util.Arrays.asList; import static org.schabi.newpipe.util.AnimationUtils.animateView; -public class SearchFragment - extends BaseListFragment +public class SearchFragment extends BaseListFragment implements BackPressable { /*////////////////////////////////////////////////////////////////////////// @@ -92,20 +92,19 @@ public class SearchFragment private static final int THRESHOLD_NETWORK_SUGGESTION = 1; /** - * How much time have to pass without emitting a item (i.e. the user stop typing) to fetch/show the suggestions, in milliseconds. + * How much time have to pass without emitting a item (i.e. the user stop typing) + * to fetch/show the suggestions, in milliseconds. */ private static final int SUGGESTIONS_DEBOUNCE = 120; //ms - + private final PublishSubject suggestionPublisher = PublishSubject.create(); + private final CompositeDisposable disposables = new CompositeDisposable(); @State protected int filterItemCheckedId = -1; - @State protected int serviceId = Constants.NO_SERVICE_ID; - // this three represet the current search query @State protected String searchString; - /** * No content filter should add like contentfilter = all * be aware of this when implementing an extractor. @@ -114,26 +113,19 @@ public class SearchFragment protected String[] contentFilter = new String[0]; @State protected String sortFilter; - // these represtent the last search @State protected String lastSearchedString; - @State protected boolean wasSearchFocused = false; - private Map menuItemToFilterName; private StreamingService service; private String currentPageUrl; private String nextPageUrl; private String contentCountry; private boolean isSuggestionsEnabled = true; - - private final PublishSubject suggestionPublisher = PublishSubject.create(); private Disposable searchDisposable; private Disposable suggestionDisposable; - private final CompositeDisposable disposables = new CompositeDisposable(); - private SuggestionListAdapter suggestionListAdapter; private HistoryRecordManager historyRecordManager; @@ -149,8 +141,9 @@ public class SearchFragment private RecyclerView suggestionsRecyclerView; /*////////////////////////////////////////////////////////////////////////*/ + private TextWatcher textWatcher; - public static SearchFragment getInstance(int serviceId, String searchString) { + public static SearchFragment getInstance(final int serviceId, final String searchString) { SearchFragment searchFragment = new SearchFragment(); searchFragment.setQuery(serviceId, searchString, new String[0], ""); @@ -161,6 +154,10 @@ public static SearchFragment getInstance(int serviceId, String searchString) { return searchFragment; } + /*////////////////////////////////////////////////////////////////////////// + // Fragment's LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + /** * Set wasLoading to true so when the fragment onResume is called, the initial search is done. */ @@ -168,38 +165,38 @@ private void setSearchOnResume() { wasLoading.set(true); } - /*////////////////////////////////////////////////////////////////////////// - // Fragment's LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onAttach(Context context) { + public void onAttach(final Context context) { super.onAttach(context); suggestionListAdapter = new SuggestionListAdapter(activity); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true); + boolean isSearchHistoryEnabled = preferences + .getBoolean(getString(R.string.enable_search_history_key), true); suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled); historyRecordManager = new HistoryRecordManager(context); } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true); - contentCountry = preferences.getString(getString(R.string.content_country_key), getString(R.string.default_localization_key)); + isSuggestionsEnabled = preferences + .getBoolean(getString(R.string.show_search_suggestions_key), true); + contentCountry = preferences.getString(getString(R.string.content_country_key), + getString(R.string.default_localization_key)); } @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_search, container, false); } @Override - public void onViewCreated(View rootView, Bundle savedInstanceState) { + public void onViewCreated(final View rootView, final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); showSearchOnStart(); initSearchListeners(); @@ -211,15 +208,23 @@ public void onPause() { wasSearchFocused = searchEditText.hasFocus(); - if (searchDisposable != null) searchDisposable.dispose(); - if (suggestionDisposable != null) suggestionDisposable.dispose(); - if (disposables != null) disposables.clear(); + if (searchDisposable != null) { + searchDisposable.dispose(); + } + if (suggestionDisposable != null) { + suggestionDisposable.dispose(); + } + if (disposables != null) { + disposables.clear(); + } hideKeyboardSearch(); } @Override public void onResume() { - if (DEBUG) Log.d(TAG, "onResume() called"); + if (DEBUG) { + Log.d(TAG, "onResume() called"); + } super.onResume(); try { @@ -245,7 +250,9 @@ public void onResume() { } } - if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver(); + if (suggestionDisposable == null || suggestionDisposable.isDisposed()) { + initSuggestionObserver(); + } if (TextUtils.isEmpty(searchString) || wasSearchFocused) { showKeyboardSearch(); @@ -259,7 +266,9 @@ public void onResume() { @Override public void onDestroyView() { - if (DEBUG) Log.d(TAG, "onDestroyView() called"); + if (DEBUG) { + Log.d(TAG, "onDestroyView() called"); + } unsetSearchListeners(); super.onDestroyView(); } @@ -267,19 +276,31 @@ public void onDestroyView() { @Override public void onDestroy() { super.onDestroy(); - if (searchDisposable != null) searchDisposable.dispose(); - if (suggestionDisposable != null) suggestionDisposable.dispose(); - if (disposables != null) disposables.clear(); + if (searchDisposable != null) { + searchDisposable.dispose(); + } + if (suggestionDisposable != null) { + suggestionDisposable.dispose(); + } + if (disposables != null) { + disposables.clear(); + } } + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { switch (requestCode) { case ReCaptchaActivity.RECAPTCHA_REQUEST: if (resultCode == Activity.RESULT_OK && !TextUtils.isEmpty(searchString)) { search(searchString, contentFilter, sortFilter); - } else Log.e(TAG, "ReCaptcha failed"); + } else { + Log.e(TAG, "ReCaptcha failed"); + } break; default: @@ -289,29 +310,31 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } /*////////////////////////////////////////////////////////////////////////// - // Init + // State Saving //////////////////////////////////////////////////////////////////////////*/ @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); suggestionsPanel = rootView.findViewById(R.id.suggestions_panel); suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list); suggestionsRecyclerView.setAdapter(suggestionListAdapter); new ItemTouchHelper(new ItemTouchHelper.Callback() { @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + public int getMovementFlags(@NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder viewHolder) { return getSuggestionMovementFlags(recyclerView, viewHolder); } @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder viewHolder1) { + public boolean onMove(@NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder viewHolder, + @NonNull final RecyclerView.ViewHolder viewHolder1) { return false; } @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { + public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int i) { onSuggestionItemSwiped(viewHolder, i); } }).attachToRecyclerView(suggestionsRecyclerView); @@ -321,26 +344,26 @@ public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear); } - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void writeTo(Queue objectsToSave) { + public void writeTo(final Queue objectsToSave) { super.writeTo(objectsToSave); objectsToSave.add(currentPageUrl); objectsToSave.add(nextPageUrl); } @Override - public void readFrom(@NonNull Queue savedObjects) throws Exception { + public void readFrom(@NonNull final Queue savedObjects) throws Exception { super.readFrom(savedObjects); currentPageUrl = (String) savedObjects.poll(); nextPageUrl = (String) savedObjects.poll(); } + /*////////////////////////////////////////////////////////////////////////// + // Init's + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onSaveInstanceState(Bundle bundle) { + public void onSaveInstanceState(final Bundle bundle) { searchString = searchEditText != null ? searchEditText.getText().toString() : searchString; @@ -348,7 +371,7 @@ public void onSaveInstanceState(Bundle bundle) { } /*////////////////////////////////////////////////////////////////////////// - // Init's + // Menu //////////////////////////////////////////////////////////////////////////*/ @Override @@ -367,12 +390,8 @@ public void reloadContent() { } } - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); ActionBar supportActionBar = activity.getSupportActionBar(); @@ -386,13 +405,13 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { int itemId = 0; boolean isFirstItem = true; final Context c = getContext(); - for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) { + for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) { menuItemToFilterName.put(itemId, filter); MenuItem item = menu.add(1, itemId++, 0, ServiceHelper.getTranslatedFilterString(filter, c)); - if(isFirstItem) { + if (isFirstItem) { item.setChecked(true); isFirstItem = false; } @@ -403,35 +422,36 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } @Override - public boolean onOptionsItemSelected(MenuItem item) { - - List contentFilter = new ArrayList<>(1); - contentFilter.add(menuItemToFilterName.get(item.getItemId())); - changeContentFilter(item, contentFilter); + public boolean onOptionsItemSelected(final MenuItem item) { + List cf = new ArrayList<>(1); + cf.add(menuItemToFilterName.get(item.getItemId())); + changeContentFilter(item, cf); return true; } - private void restoreFilterChecked(Menu menu, int itemId) { + /*////////////////////////////////////////////////////////////////////////// + // Search + //////////////////////////////////////////////////////////////////////////*/ + + 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); } } - /*////////////////////////////////////////////////////////////////////////// - // 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,9 +471,13 @@ private void showSearchOnStart() { } private void initSearchListeners() { - if (DEBUG) Log.d(TAG, "initSearchListeners() called"); + if (DEBUG) { + Log.d(TAG, "initSearchListeners() called"); + } searchClear.setOnClickListener(v -> { - if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); + if (DEBUG) { + Log.d(TAG, "onClick() called with: v = [" + v + "]"); + } if (TextUtils.isEmpty(searchEditText.getText())) { NavigationHelper.gotoMainFragment(getFragmentManager()); return; @@ -467,53 +491,63 @@ private void initSearchListeners() { TooltipCompat.setTooltipText(searchClear, getString(R.string.clear)); searchEditText.setOnClickListener(v -> { - if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); + if (DEBUG) { + Log.d(TAG, "onClick() called with: v = [" + v + "]"); + } if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) { showSuggestionsPanel(); } - if(FireTvUtils.isFireTv()){ + if (FireTvUtils.isFireTv()) { showKeyboardSearch(); } }); searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> { - if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]"); - if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) { + if (DEBUG) { + Log.d(TAG, "onFocusChange() called with: " + + "v = [" + v + "], hasFocus = [" + hasFocus + "]"); + } + if (isSuggestionsEnabled && hasFocus + && errorPanelRoot.getVisibility() != View.VISIBLE) { showSuggestionsPanel(); } }); suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() { @Override - public void onSuggestionItemSelected(SuggestionItem item) { + public void onSuggestionItemSelected(final SuggestionItem item) { search(item.query, new String[0], ""); searchEditText.setText(item.query); } @Override - public void onSuggestionItemInserted(SuggestionItem item) { + public void onSuggestionItemInserted(final SuggestionItem item) { searchEditText.setText(item.query); searchEditText.setSelection(searchEditText.getText().length()); } @Override - public void onSuggestionItemLongClick(SuggestionItem item) { - if (item.fromHistory) showDeleteSuggestionDialog(item); + public void onSuggestionItemLongClick(final SuggestionItem item) { + if (item.fromHistory) { + showDeleteSuggestionDialog(item); + } } }); - if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); + if (textWatcher != null) { + searchEditText.removeTextChangedListener(textWatcher); + } textWatcher = new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + public void beforeTextChanged(final CharSequence s, final int start, + final int count, final int after) { } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } + public void onTextChanged(final CharSequence s, final int start, + final int before, final int count) { } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(final Editable s) { String newText = searchEditText.getText().toString(); suggestionPublisher.onNext(newText); } @@ -522,48 +556,62 @@ public void afterTextChanged(Editable s) { searchEditText.setOnEditorActionListener( (TextView v, int actionId, KeyEvent event) -> { if (DEBUG) { - Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]"); + Log.d(TAG, "onEditorAction() called with: v = [" + v + "], " + + "actionId = [" + actionId + "], event = [" + event + "]"); } - if(actionId == EditorInfo.IME_ACTION_PREVIOUS){ + if (actionId == EditorInfo.IME_ACTION_PREVIOUS) { hideKeyboardSearch(); } else if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER - || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) { + || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) { search(searchEditText.getText().toString(), new String[0], ""); return true; } return false; }); - if (suggestionDisposable == null || suggestionDisposable.isDisposed()) + if (suggestionDisposable == null || suggestionDisposable.isDisposed()) { initSuggestionObserver(); + } } private void unsetSearchListeners() { - if (DEBUG) Log.d(TAG, "unsetSearchListeners() called"); + if (DEBUG) { + Log.d(TAG, "unsetSearchListeners() called"); + } searchClear.setOnClickListener(null); searchClear.setOnLongClickListener(null); searchEditText.setOnClickListener(null); searchEditText.setOnFocusChangeListener(null); searchEditText.setOnEditorActionListener(null); - if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); + if (textWatcher != null) { + searchEditText.removeTextChangedListener(textWatcher); + } textWatcher = null; } private void showSuggestionsPanel() { - if (DEBUG) Log.d(TAG, "showSuggestionsPanel() called"); + if (DEBUG) { + Log.d(TAG, "showSuggestionsPanel() called"); + } animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200); } private void hideSuggestionsPanel() { - if (DEBUG) Log.d(TAG, "hideSuggestionsPanel() called"); + if (DEBUG) { + Log.d(TAG, "hideSuggestionsPanel() called"); + } animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200); } private void showKeyboardSearch() { - if (DEBUG) Log.d(TAG, "showKeyboardSearch() called"); - if (searchEditText == null) return; + if (DEBUG) { + Log.d(TAG, "showKeyboardSearch() called"); + } + if (searchEditText == null) { + return; + } if (searchEditText.requestFocus()) { InputMethodManager imm = (InputMethodManager) activity.getSystemService( @@ -573,19 +621,26 @@ private void showKeyboardSearch() { } private void hideKeyboardSearch() { - if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called"); - if (searchEditText == null) return; + if (DEBUG) { + Log.d(TAG, "hideKeyboardSearch() called"); + } + if (searchEditText == null) { + return; + } - InputMethodManager imm = (InputMethodManager) activity.getSystemService( - Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); + InputMethodManager imm = (InputMethodManager) activity + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), + InputMethodManager.RESULT_UNCHANGED_SHOWN); searchEditText.clearFocus(); } private void showDeleteSuggestionDialog(final SuggestionItem item) { - if (activity == null || historyRecordManager == null || suggestionPublisher == null || - searchEditText == null || disposables == null) return; + if (activity == null || historyRecordManager == null || suggestionPublisher == null + || searchEditText == null || disposables == null) { + return; + } final String query = item.query; new AlertDialog.Builder(activity) .setTitle(query) @@ -624,15 +679,19 @@ public void giveSearchEditTextFocus() { } 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 observable = suggestionPublisher .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) .startWith(searchString != null ? searchString : "") - .filter(searchString -> isSuggestionsEnabled); + .filter(ss -> isSuggestionsEnabled); suggestionDisposable = observable .switchMap(query -> { @@ -641,13 +700,15 @@ private void initSuggestionObserver() { final Observable> local = flowable.toObservable() .map(searchHistoryEntries -> { List result = new ArrayList<>(); - for (SearchHistoryEntry entry : searchHistoryEntries) + for (SearchHistoryEntry entry : searchHistoryEntries) { result.add(new SuggestionItem(true, entry.getSearch())); + } return result; }); if (query.length() < THRESHOLD_NETWORK_SUGGESTION) { - // Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION + // Only pass through if the query length + // is equal or greater than THRESHOLD_NETWORK_SUGGESTION return local.materialize(); } @@ -664,7 +725,9 @@ private void initSuggestionObserver() { return Observable.zip(local, network, (localResult, networkResult) -> { List result = new ArrayList<>(); - if (localResult.size() > 0) result.addAll(localResult); + if (localResult.size() > 0) { + result.addAll(localResult); + } // Remove duplicates final Iterator iterator = networkResult.iterator(); @@ -678,7 +741,9 @@ private void initSuggestionObserver() { } } - if (networkResult.size() > 0) result.addAll(networkResult); + if (networkResult.size() > 0) { + result.addAll(networkResult); + } return result; }).materialize(); }) @@ -703,17 +768,21 @@ protected void doInitialLoadLogic() { // no-op } - private void search(final String searchString, String[] contentFilter, String sortFilter) { - if (DEBUG) Log.d(TAG, "search() called with: query = [" + searchString + "]"); - if (searchString.isEmpty()) return; + private void search(final String ss, final String[] cf, final String sf) { + if (DEBUG) { + Log.d(TAG, "search() called with: query = [" + ss + "]"); + } + if (ss.isEmpty()) { + return; + } try { - final StreamingService service = NewPipe.getServiceByUrl(searchString); - if (service != null) { + final StreamingService streamingService = NewPipe.getServiceByUrl(ss); + if (streamingService != null) { showLoading(); disposables.add(Observable .fromCallable(() -> - NavigationHelper.getIntentByLink(activity, service, searchString)) + NavigationHelper.getIntentByLink(activity, streamingService, ss)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(intent -> { @@ -728,31 +797,36 @@ private void search(final String searchString, String[] contentFilter, String so } lastSearchedString = this.searchString; - this.searchString = searchString; + this.searchString = ss; infoListAdapter.clearStreamItemList(); hideSuggestionsPanel(); hideKeyboardSearch(); - historyRecordManager.onSearched(serviceId, searchString) + historyRecordManager.onSearched(serviceId, ss) .observeOn(AndroidSchedulers.mainThread()) .subscribe( - ignored -> {}, + ignored -> { + }, error -> showSnackBarError(error, UserAction.SEARCHED, - NewPipe.getNameOfService(serviceId), searchString, 0) + NewPipe.getNameOfService(serviceId), ss, 0) ); - suggestionPublisher.onNext(searchString); + suggestionPublisher.onNext(ss); startLoading(false); } @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); - if (disposables != null) disposables.clear(); - if (searchDisposable != null) searchDisposable.dispose(); + if (disposables != null) { + disposables.clear(); + } + if (searchDisposable != null) { + searchDisposable.dispose(); + } searchDisposable = ExtractorHelper.searchFor(serviceId, - searchString, - Arrays.asList(contentFilter), - sortFilter) + searchString, + Arrays.asList(contentFilter), + sortFilter) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnEvent((searchResult, throwable) -> isLoading.set(false)) @@ -762,16 +836,20 @@ public void startLoading(boolean forceLoad) { @Override protected void loadMoreItems() { - if(nextPageUrl == null || nextPageUrl.isEmpty()) return; + if (nextPageUrl == null || nextPageUrl.isEmpty()) { + return; + } isLoading.set(true); showListFooter(true); - if (searchDisposable != null) searchDisposable.dispose(); + if (searchDisposable != null) { + searchDisposable.dispose(); + } searchDisposable = ExtractorHelper.getMoreSearchItems( - serviceId, - searchString, - asList(contentFilter), - sortFilter, - nextPageUrl) + serviceId, + searchString, + asList(contentFilter), + sortFilter, + nextPageUrl) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnEvent((nextItemsResult, throwable) -> isLoading.set(false)) @@ -785,7 +863,7 @@ protected boolean hasMoreItems() { } @Override - protected void onItemSelected(InfoItem selectedItem) { + protected void onItemSelected(final InfoItem selectedItem) { super.onItemSelected(selectedItem); hideKeyboardSearch(); } @@ -794,22 +872,22 @@ protected void onItemSelected(InfoItem selectedItem) { // Utils //////////////////////////////////////////////////////////////////////////*/ - private void changeContentFilter(MenuItem item, List contentFilter) { + private void changeContentFilter(final MenuItem item, final List cf) { this.filterItemCheckedId = item.getItemId(); item.setChecked(true); - this.contentFilter = new String[] {contentFilter.get(0)}; + this.contentFilter = new String[]{cf.get(0)}; if (!TextUtils.isEmpty(searchString)) { search(searchString, this.contentFilter, sortFilter); } } - private void setQuery(int serviceId, String searchString, String[] contentfilter, String sortFilter) { - this.serviceId = serviceId; + private void setQuery(final int sid, final String ss, final String[] cf, final String sf) { + this.serviceId = sid; this.searchString = searchString; - this.contentFilter = contentfilter; - this.sortFilter = sortFilter; + this.contentFilter = cf; + this.sortFilter = sf; } /*////////////////////////////////////////////////////////////////////////// @@ -817,7 +895,9 @@ private void setQuery(int serviceId, String searchString, String[] contentfilter //////////////////////////////////////////////////////////////////////////*/ public void handleSuggestions(@NonNull final List suggestions) { - if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); + if (DEBUG) { + Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); + } suggestionsRecyclerView.smoothScrollToPosition(0); suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions)); @@ -826,9 +906,13 @@ public void handleSuggestions(@NonNull final List suggestions) { } } - public void onSuggestionError(Throwable exception) { - if (DEBUG) Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]"); - if (super.onError(exception)) return; + public void onSuggestionError(final Throwable exception) { + if (DEBUG) { + Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]"); + } + if (super.onError(exception)) { + return; + } int errorId = exception instanceof ParsingException ? R.string.parsing_error @@ -848,7 +932,7 @@ public void hideLoading() { } @Override - public void showError(String message, boolean showRetryButton) { + public void showError(final String message, final boolean showRetryButton) { super.showError(message, showRetryButton); hideSuggestionsPanel(); hideKeyboardSearch(); @@ -859,11 +943,11 @@ public void showError(String message, boolean showRetryButton) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void handleResult(@NonNull SearchInfo result) { + public void handleResult(@NonNull final SearchInfo result) { final List exceptions = result.getErrors(); if (!exceptions.isEmpty() - && !(exceptions.size() == 1 - && exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){ + && !(exceptions.size() == 1 + && exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) { showSnackBarError(result.getErrors(), UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchString, 0); } @@ -886,7 +970,7 @@ public void handleResult(@NonNull SearchInfo result) { } @Override - public void handleNextItems(ListExtractor.InfoItemsPage result) { + public void handleNextItems(final ListExtractor.InfoItemsPage result) { showListFooter(false); currentPageUrl = result.getNextPageUrl(); infoListAdapter.addInfoItemList(result.getItems()); @@ -894,15 +978,17 @@ public void handleNextItems(ListExtractor.InfoItemsPage result) { if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), UserAction.SEARCHED, - NewPipe.getNameOfService(serviceId) - , "\"" + searchString + "\" → page: " + nextPageUrl, 0); + NewPipe.getNameOfService(serviceId), + "\"" + searchString + "\" → page: " + nextPageUrl, 0); } super.handleNextItems(result); } @Override - protected boolean onError(Throwable exception) { - if (super.onError(exception)) return true; + protected boolean onError(final Throwable exception) { + if (super.onError(exception)) { + return true; + } if (exception instanceof SearchExtractor.NothingFoundException) { infoListAdapter.clearStreamItemList(); @@ -922,13 +1008,16 @@ protected boolean onError(Throwable exception) { // Suggestion item touch helper //////////////////////////////////////////////////////////////////////////*/ - public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder viewHolder) { final int position = viewHolder.getAdapterPosition(); final SuggestionItem item = suggestionListAdapter.getItem(position); - return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0; + return item.fromHistory ? makeMovementFlags(0, + ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0; } - public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { + public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, + final int i) { final int position = viewHolder.getAdapterPosition(); final String query = suggestionListAdapter.getItem(position).query; final Disposable onDelete = historyRecordManager.deleteSearchHistory(query) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java index 722638926..5aa927ed3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java @@ -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; } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index d46f4bb31..9b7aa8fdf 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -2,36 +2,32 @@ 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 { +public class SuggestionListAdapter + extends RecyclerView.Adapter { private final ArrayList 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 items) { + public void setItems(final List items) { this.items.clear(); if (showSuggestionHistory) { this.items.addAll(items); @@ -46,36 +42,43 @@ public void setItems(List items) { 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 boolean isEmpty() { 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 static class SuggestionItemHolder extends RecyclerView.ViewHolder { private final int historyResId; private final int searchResId; - private SuggestionItemHolder(View rootView) { + private SuggestionItemHolder(final View rootView) { super(rootView); suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon); itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query); @@ -110,16 +121,17 @@ private SuggestionItemHolder(View rootView) { searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search); } - private void updateFrom(SuggestionItem item) { - suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); - itemSuggestionQuery.setText(item.query); - } - - private static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) { + private static int resolveResourceIdFromAttr(final Context context, + @AttrRes final int attr) { TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); int attributeResourceId = a.getResourceId(0, 0); a.recycle(); return attributeResourceId; } + + private void updateFrom(final SuggestionItem item) { + suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); + itemSuggestionQuery.setText(item.query); + } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java index 2186efda5..2f660c5b6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java @@ -4,8 +4,6 @@ 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.widget.CompoundButton; import android.widget.Switch; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; @@ -28,8 +29,9 @@ import io.reactivex.Single; import io.reactivex.disposables.CompositeDisposable; -public class RelatedVideosFragment extends BaseListInfoFragment implements SharedPreferences.OnSharedPreferenceChangeListener{ - +public class RelatedVideosFragment extends BaseListInfoFragment + implements SharedPreferences.OnSharedPreferenceChangeListener { + private static final String INFO_KEY = "related_info_key"; private CompositeDisposable disposables = new CompositeDisposable(); private RelatedStreamInfo relatedStreamInfo; /*////////////////////////////////////////////////////////////////////////// @@ -37,44 +39,48 @@ public class RelatedVideosFragment extends BaseListInfoFragment loadMoreItemsLogic() { return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage()); } - @Override - protected Single loadResult(boolean forceLoad) { - return Single.fromCallable(() -> relatedStreamInfo); - } - /*////////////////////////////////////////////////////////////////////////// // Contract //////////////////////////////////////////////////////////////////////////*/ + @Override + protected Single loadResult(final boolean forceLoad) { + return Single.fromCallable(() -> relatedStreamInfo); + } + @Override public void showLoading() { super.showLoading(); - if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE); + if (headerRootLayout != null) { + headerRootLayout.setVisibility(View.INVISIBLE); + } } @Override - public void handleResult(@NonNull RelatedStreamInfo result) { - + public void handleResult(@NonNull final RelatedStreamInfo result) { super.handleResult(result); - if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE); - AnimationUtils.slideUp(getView(),120, 96, 0.06f); + if (headerRootLayout != null) { + headerRootLayout.setVisibility(View.VISIBLE); + } + AnimationUtils.slideUp(getView(), 120, 96, 0.06f); if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, + NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); } - if (disposables != null) disposables.clear(); + if (disposables != null) { + disposables.clear(); + } } + /*////////////////////////////////////////////////////////////////////////// + // OnError + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void handleNextItems(ListExtractor.InfoItemsPage result) { + public void handleNextItems(final ListExtractor.InfoItemsPage result) { super.handleNextItems(result); if (!result.getErrors().isEmpty()) { @@ -143,62 +159,63 @@ public void handleNextItems(ListExtractor.InfoItemsPage result) { } /*////////////////////////////////////////////////////////////////////////// - // OnError + // Utils //////////////////////////////////////////////////////////////////////////*/ @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; } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - @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 diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 2a635bc74..148d0d074 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -1,10 +1,11 @@ package org.schabi.newpipe.info_list; import android.content.Context; -import androidx.annotation.NonNull; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; + import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.extractor.InfoItem; @@ -29,24 +30,26 @@ *

* Copyright (C) Christian Schabesberger 2016 * InfoItemBuilder.java is part of NewPipe. + *

*

* 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. + *

*

* 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. + *

*

* You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . + *

*/ public class InfoItemBuilder { - private static final String TAG = InfoItemBuilder.class.toString(); - private final Context context; private final ImageLoader imageLoader = ImageLoader.getInstance(); @@ -55,31 +58,39 @@ public class InfoItemBuilder { private OnClickGesture onPlaylistSelectedListener; private OnClickGesture onCommentsSelectedListener; - public InfoItemBuilder(Context context) { + public InfoItemBuilder(final Context context) { this.context = context; } - public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { + public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { return buildView(parent, infoItem, historyRecordManager, false); } - public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, - final HistoryRecordManager historyRecordManager, boolean useMiniVariant) { + public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, + final HistoryRecordManager historyRecordManager, + final boolean useMiniVariant) { InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); holder.updateFromItem(infoItem, historyRecordManager); return holder.itemView; } - private InfoItemHolder holderFromInfoType(@NonNull ViewGroup parent, @NonNull InfoItem.InfoType infoType, boolean useMiniVariant) { + private InfoItemHolder holderFromInfoType(@NonNull final ViewGroup parent, + @NonNull final InfoItem.InfoType infoType, + final boolean useMiniVariant) { switch (infoType) { case STREAM: - return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent) : new StreamInfoItemHolder(this, parent); + return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent) + : new StreamInfoItemHolder(this, parent); case CHANNEL: - return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent); + return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) + : new ChannelInfoItemHolder(this, parent); case PLAYLIST: - return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent); + return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) + : new PlaylistInfoItemHolder(this, parent); case COMMENT: - return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent); + return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) + : new CommentsInfoItemHolder(this, parent); default: throw new RuntimeException("InfoType not expected = " + infoType.name()); } @@ -97,7 +108,7 @@ public OnClickGesture getOnStreamSelectedListener() { return onStreamSelectedListener; } - public void setOnStreamSelectedListener(OnClickGesture listener) { + public void setOnStreamSelectedListener(final OnClickGesture listener) { this.onStreamSelectedListener = listener; } @@ -105,7 +116,7 @@ public OnClickGesture getOnChannelSelectedListener() { return onChannelSelectedListener; } - public void setOnChannelSelectedListener(OnClickGesture listener) { + public void setOnChannelSelectedListener(final OnClickGesture listener) { this.onChannelSelectedListener = listener; } @@ -113,7 +124,7 @@ public OnClickGesture getOnPlaylistSelectedListener() { return onPlaylistSelectedListener; } - public void setOnPlaylistSelectedListener(OnClickGesture listener) { + public void setOnPlaylistSelectedListener(final OnClickGesture listener) { this.onPlaylistSelectedListener = listener; } @@ -121,8 +132,8 @@ public OnClickGesture getOnCommentsSelectedListener() { return onCommentsSelectedListener; } - public void setOnCommentsSelectedListener(OnClickGesture onCommentsSelectedListener) { + public void setOnCommentsSelectedListener( + final OnClickGesture onCommentsSelectedListener) { this.onCommentsSelectedListener = onCommentsSelectedListener; } - } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java index a7f961e7d..4ff56306e 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java @@ -3,11 +3,12 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.view.View; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfoItem; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index 54cb6326c..eb4b2c2c0 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -1,13 +1,14 @@ package org.schabi.newpipe.info_list; import android.content.Context; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.InfoItem; @@ -83,42 +84,33 @@ public class InfoListAdapter extends RecyclerView.Adapter(); } - public void setOnStreamSelectedListener(OnClickGesture listener) { + public void setOnStreamSelectedListener(final OnClickGesture listener) { infoItemBuilder.setOnStreamSelectedListener(listener); } - public void setOnChannelSelectedListener(OnClickGesture listener) { + public void setOnChannelSelectedListener(final OnClickGesture listener) { infoItemBuilder.setOnChannelSelectedListener(listener); } - public void setOnPlaylistSelectedListener(OnClickGesture listener) { + public void setOnPlaylistSelectedListener(final OnClickGesture listener) { infoItemBuilder.setOnPlaylistSelectedListener(listener); } - public void setOnCommentsSelectedListener(OnClickGesture listener) { + public void setOnCommentsSelectedListener(final OnClickGesture listener) { infoItemBuilder.setOnCommentsSelectedListener(listener); } - public void useMiniItemVariants(boolean useMiniVariant) { + public void setUseMiniVariant(final boolean useMiniVariant) { this.useMiniVariant = useMiniVariant; } - public void setGridItemVariants(boolean useGridVariant) { + public void setUseGridVariant(final boolean useGridVariant) { this.useGridVariant = useGridVariant; } @@ -126,55 +118,67 @@ public void addInfoItemList(@Nullable final List data) { if (data == null) { return; } - if (DEBUG) Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + - infoItemList.size() + ", data.size() = " + data.size()); + if (DEBUG) { + Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + + infoItemList.size() + ", data.size() = " + data.size()); + } int offsetStart = sizeConsideringHeaderOffset(); infoItemList.addAll(data); - if (DEBUG) Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + - ", infoItemList.size() = " + infoItemList.size() + - ", header = " + header + ", footer = " + footer + - ", showFooter = " + showFooter); + if (DEBUG) { + Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", " + + "infoItemList.size() = " + infoItemList.size() + ", " + + "header = " + header + ", footer = " + footer + ", " + + "showFooter = " + showFooter); + } notifyItemRangeInserted(offsetStart, data.size()); if (footer != null && showFooter) { int footerNow = sizeConsideringHeaderOffset(); notifyItemMoved(offsetStart, footerNow); - if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + - " to " + footerNow); + if (DEBUG) { + Log.d(TAG, "addInfoItemList() footer from " + offsetStart + + " to " + footerNow); + } } } - public void setInfoItemList(List data) { + public void setInfoItemList(final List data) { infoItemList.clear(); infoItemList.addAll(data); notifyDataSetChanged(); } - public void addInfoItem(@Nullable InfoItem data) { + public void addInfoItem(@Nullable final InfoItem data) { if (data == null) { return; } - if (DEBUG) Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + - infoItemList.size() + ", thread = " + Thread.currentThread()); + if (DEBUG) { + Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + + infoItemList.size() + ", thread = " + Thread.currentThread()); + } int positionInserted = sizeConsideringHeaderOffset(); infoItemList.add(data); - if (DEBUG) Log.d(TAG, "addInfoItem() after > position = " + positionInserted + - ", infoItemList.size() = " + infoItemList.size() + - ", header = " + header + ", footer = " + footer + - ", showFooter = " + showFooter); + if (DEBUG) { + Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", " + + "infoItemList.size() = " + infoItemList.size() + ", " + + "header = " + header + ", footer = " + footer + ", " + + "showFooter = " + showFooter); + } notifyItemInserted(positionInserted); if (footer != null && showFooter) { int footerNow = sizeConsideringHeaderOffset(); notifyItemMoved(positionInserted, footerNow); - if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + - " to " + footerNow); + if (DEBUG) { + Log.d(TAG, "addInfoItem() footer from " + positionInserted + + " to " + footerNow); + } } } @@ -186,29 +190,39 @@ public void clearStreamItemList() { notifyDataSetChanged(); } - public void setHeader(View header) { + public void setHeader(final View header) { boolean changed = header != this.header; this.header = header; - if (changed) notifyDataSetChanged(); + if (changed) { + notifyDataSetChanged(); + } } - public void setFooter(View view) { + public void setFooter(final View view) { this.footer = view; } - public void showFooter(boolean show) { - if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]"); - if (show == showFooter) return; + public void showFooter(final boolean show) { + if (DEBUG) { + Log.d(TAG, "showFooter() called with: show = [" + show + "]"); + } + if (show == showFooter) { + return; + } showFooter = show; - if (show) notifyItemInserted(sizeConsideringHeaderOffset()); - else notifyItemRemoved(sizeConsideringHeaderOffset()); + if (show) { + notifyItemInserted(sizeConsideringHeaderOffset()); + } else { + notifyItemRemoved(sizeConsideringHeaderOffset()); + } } - private int sizeConsideringHeaderOffset() { int i = infoItemList.size() + (header != null ? 1 : 0); - if (DEBUG) Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i); + if (DEBUG) { + Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i); + } return i; } @@ -219,18 +233,27 @@ public ArrayList getItemsList() { @Override public int getItemCount() { int count = infoItemList.size(); - if (header != null) count++; - if (footer != null && showFooter) count++; + if (header != null) { + count++; + } + if (footer != null && showFooter) { + count++; + } if (DEBUG) { - Log.d(TAG, "getItemCount() called, count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter); + Log.d(TAG, "getItemCount() called with: " + + "count = " + count + ", infoItemList.size() = " + infoItemList.size() + ", " + + "header = " + header + ", footer = " + footer + ", " + + "showFooter = " + showFooter); } return count; } @Override public int getItemViewType(int position) { - if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); + if (DEBUG) { + Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); + } if (header != null && position == 0) { return HEADER_TYPE; @@ -243,11 +266,14 @@ public int getItemViewType(int position) { final InfoItem item = infoItemList.get(position); switch (item.getInfoType()) { case STREAM: - return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE; + return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant + ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE; case CHANNEL: - return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE; + return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant + ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE; case PLAYLIST: - return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE; + return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant + ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE; case COMMENT: return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE; default: @@ -257,9 +283,12 @@ public int getItemViewType(int position) { @NonNull @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { - if (DEBUG) - Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]"); + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int type) { + if (DEBUG) { + Log.d(TAG, "onCreateViewHolder() called with: " + + "parent = [" + parent + "], type = [" + type + "]"); + } switch (type) { case HEADER_TYPE: return new HFHolder(header); @@ -293,28 +322,38 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int } @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]"); + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { + if (DEBUG) { + Log.d(TAG, "onBindViewHolder() called with: " + + "holder = [" + holder.getClass().getSimpleName() + "], " + + "position = [" + position + "]"); + } if (holder instanceof InfoItemHolder) { // If header isn't null, offset the items by -1 - if (header != null) position--; + if (header != null) { + position--; + } ((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager); } else if (holder instanceof HFHolder && position == 0 && header != null) { ((HFHolder) holder).view = header; - } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) { + } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() + && footer != null && showFooter) { ((HFHolder) holder).view = footer; } } @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) { + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position, + @NonNull final List payloads) { if (!payloads.isEmpty() && holder instanceof InfoItemHolder) { for (Object payload : payloads) { if (payload instanceof StreamStateEntity) { - ((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager); + ((InfoItemHolder) holder).updateState(infoItemList + .get(header == null ? position : position - 1), recordManager); } else if (payload instanceof Boolean) { - ((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager); + ((InfoItemHolder) holder).updateState(infoItemList + .get(header == null ? position : position - 1), recordManager); } } } else { @@ -325,10 +364,19 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { return new GridLayoutManager.SpanSizeLookup() { @Override - public int getSpanSize(int position) { + public int getSpanSize(final int position) { final int type = getItemViewType(position); return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1; } }; } + + public class HFHolder extends RecyclerView.ViewHolder { + public View view; + + HFHolder(final View v) { + super(v); + view = v; + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java index 2cc575e41..a4755052e 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelGridInfoItemHolder.java @@ -6,8 +6,8 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder { - - public ChannelGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_channel_grid_item, parent); - } + public ChannelGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, + final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_channel_grid_item, parent); + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java index 956bc47a6..5942fa45e 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelInfoItemHolder.java @@ -31,19 +31,23 @@ */ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder { - public final TextView itemChannelDescriptionView; + private final TextView itemChannelDescriptionView; - public ChannelInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public ChannelInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { super(infoItemBuilder, R.layout.list_channel_item, parent); itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { super.updateFromItem(infoItem, historyRecordManager); - if (!(infoItem instanceof ChannelInfoItem)) return; - final ChannelInfoItem item = (ChannelInfoItem) infoItem; + if (!(infoItem instanceof ChannelInfoItem)) { + return; + } + final ChannelInfoItem item; + item = (ChannelInfoItem) infoItem; itemChannelDescriptionView.setText(item.getDescription()); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 3f4e4e398..9d93abcd9 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -16,9 +16,10 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { public final CircleImageView itemThumbnailView; public final TextView itemTitleView; - public final TextView itemAdditionalDetailView; + private final TextView itemAdditionalDetailView; - ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -26,13 +27,17 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails); } - public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, + final ViewGroup parent) { this(infoItemBuilder, R.layout.list_channel_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof ChannelInfoItem)) return; + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { + if (!(infoItem instanceof ChannelInfoItem)) { + return; + } final ChannelInfoItem item = (ChannelInfoItem) infoItem; itemTitleView.setText(item.getName()); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java index 90212ea31..4e74bad79 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java @@ -30,23 +30,24 @@ */ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { - public final TextView itemTitleView; - public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { super(infoItemBuilder, R.layout.list_comments_item, parent); itemTitleView = itemView.findViewById(R.id.itemTitleView); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { super.updateFromItem(infoItem, historyRecordManager); - if (!(infoItem instanceof CommentsInfoItem)) return; + if (!(infoItem instanceof CommentsInfoItem)) { + return; + } final CommentsInfoItem item = (CommentsInfoItem) infoItem; itemTitleView.setText(item.getAuthorName()); } - } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 58f1ab90d..4df237738 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -29,35 +29,41 @@ import de.hdodenhof.circleimageview.CircleImageView; public class CommentsMiniInfoItemHolder extends InfoItemHolder { + private static final int COMMENT_DEFAULT_LINES = 2; + private static final int COMMENT_EXPANDED_LINES = 1000; + private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)"); + public final CircleImageView itemThumbnailView; private final TextView itemContentView; private final TextView itemLikesCountView; private final TextView itemDislikesCountView; private final TextView itemPublishedTime; - private static final int commentDefaultLines = 2; - private static final int commentExpandedLines = 1000; - private String commentText; private String streamUrl; - private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)"); - private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() { @Override - public String transformUrl(Matcher match, String url) { + public String transformUrl(final Matcher match, final String url) { int timestamp = 0; String hours = match.group(1); String minutes = match.group(2); String seconds = match.group(3); - if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600); - if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60); - if(seconds != null) timestamp += (Integer.parseInt(seconds)); + if (hours != null) { + timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600); + } + if (minutes != null) { + timestamp += (Integer.parseInt(minutes.replace(":", "")) * 60); + } + if (seconds != null) { + timestamp += (Integer.parseInt(seconds)); + } return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp); } }; - CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -67,13 +73,17 @@ public String transformUrl(Matcher match, String url) { itemContentView = itemView.findViewById(R.id.itemCommentContentView); } - public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, + final ViewGroup parent) { this(infoItemBuilder, R.layout.list_comments_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof CommentsInfoItem)) return; + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { + if (!(infoItem instanceof CommentsInfoItem)) { + return; + } final CommentsInfoItem item = (CommentsInfoItem) infoItem; itemBuilder.getImageLoader() @@ -82,7 +92,9 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemThumbnailView.setOnClickListener(view -> { - if(StringUtil.isBlank(item.getAuthorEndpoint())) return; + if (StringUtil.isBlank(item.getAuthorEndpoint())) { + return; + } try { final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); NavigationHelper.openChannelFragment( @@ -97,7 +109,7 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h streamUrl = item.getUrl(); - itemContentView.setLines(commentDefaultLines); + itemContentView.setLines(COMMENT_DEFAULT_LINES); commentText = item.getCommentText(); itemContentView.setText(commentText); itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); @@ -130,12 +142,12 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override - public boolean onLongClick(View view) { - + public boolean onLongClick(final View view) { ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext() - .getSystemService(Context.CLIPBOARD_SERVICE); - clipboardManager.setPrimaryClip(ClipData.newPlainText(null,commentText)); - Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT).show(); + .getSystemService(Context.CLIPBOARD_SERVICE); + clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText)); + Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT) + .show(); return true; } @@ -144,10 +156,12 @@ public boolean onLongClick(View view) { } private void ellipsize() { - if (itemContentView.getLineCount() > commentDefaultLines){ - int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); - int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2); - if(end == -1) end = Math.max(endOfLastLine -2, 0); + if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) { + int endOfLastLine = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1); + int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2); + if (end == -1) { + end = Math.max(endOfLastLine - 2, 0); + } String newVal = itemContentView.getText().subSequence(0, end) + " …"; itemContentView.setText(newVal); } @@ -156,21 +170,23 @@ private void ellipsize() { private void toggleEllipsize() { if (itemContentView.getText().toString().equals(commentText)) { - if (itemContentView.getLineCount() > commentDefaultLines) ellipsize(); + if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) { + ellipsize(); + } } else { expand(); } } private void expand() { - itemContentView.setMaxLines(commentExpandedLines); + itemContentView.setMaxLines(COMMENT_EXPANDED_LINES); itemContentView.setText(commentText); linkify(); } - private void linkify(){ + private void linkify() { Linkify.addLinks(itemContentView, Linkify.WEB_URLS); - Linkify.addLinks(itemContentView, pattern, null, null, timestampLink); + Linkify.addLinks(itemContentView, PATTERN, null, null, timestampLink); itemContentView.setMovementMethod(null); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java index 1b97e2d27..9e1561786 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java @@ -1,9 +1,10 @@ package org.schabi.newpipe.info_list.holder; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -31,13 +32,15 @@ public abstract class InfoItemHolder extends RecyclerView.ViewHolder { protected final InfoItemBuilder itemBuilder; - public InfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + public InfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(LayoutInflater.from(infoItemBuilder.getContext()).inflate(layoutId, parent, false)); this.itemBuilder = infoItemBuilder; } - public abstract void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager); + public abstract void updateFromItem(InfoItem infoItem, + HistoryRecordManager historyRecordManager); - public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - } + public void updateState(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java index 96b9c90a7..1cb69208b 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java @@ -6,8 +6,8 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder { - - public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); - } -} \ No newline at end of file + public PlaylistGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, + final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java index 252d05e09..7691a377d 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java @@ -6,8 +6,7 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; public class PlaylistInfoItemHolder extends PlaylistMiniInfoItemHolder { - - public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public PlaylistInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { super(infoItemBuilder, R.layout.list_playlist_item, parent); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index b73f22d93..c6e637881 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -13,11 +13,12 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; - public final TextView itemStreamCountView; + private final TextView itemStreamCountView; public final TextView itemTitleView; public final TextView itemUploaderView; - public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -26,13 +27,17 @@ public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, itemUploaderView = itemView.findViewById(R.id.itemUploaderView); } - public PlaylistMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public PlaylistMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, + final ViewGroup parent) { this(infoItemBuilder, R.layout.list_playlist_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof PlaylistInfoItem)) return; + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { + if (!(infoItem instanceof PlaylistInfoItem)) { + return; + } final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; itemTitleView.setText(item.getName()); @@ -41,7 +46,7 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h itemBuilder.getImageLoader() .displayImage(item.getThumbnailUrl(), itemThumbnailView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnPlaylistSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java index 78bdfeaac..8e4a1914e 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java @@ -6,8 +6,7 @@ import org.schabi.newpipe.info_list.InfoItemBuilder; public class StreamGridInfoItemHolder extends StreamInfoItemHolder { - - public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_stream_grid_item, parent); - } + public StreamGridInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_stream_grid_item, parent); + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index 8f715c6c0..5fa0904de 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -20,39 +20,46 @@ *

* Copyright (C) Christian Schabesberger 2016 * StreamInfoItemHolder.java is part of NewPipe. + *

*

* 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. + *

*

* 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. + * * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . + * along with NewPipe. If not, see . + *

*/ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { - public final TextView itemAdditionalDetails; - public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { this(infoItemBuilder, R.layout.list_stream_item, parent); } - public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + public StreamInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { super.updateFromItem(infoItem, historyRecordManager); - if (!(infoItem instanceof StreamInfoItem)) return; + if (!(infoItem instanceof StreamInfoItem)) { + return; + } final StreamInfoItem item = (StreamInfoItem) infoItem; itemAdditionalDetails.setText(getStreamInfoDetailLine(item)); @@ -62,11 +69,14 @@ private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { String viewsAndDate = ""; if (infoItem.getViewCount() >= 0) { if (infoItem.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) { - viewsAndDate = Localization.listeningCount(itemBuilder.getContext(), infoItem.getViewCount()); + viewsAndDate = Localization + .listeningCount(itemBuilder.getContext(), infoItem.getViewCount()); } else if (infoItem.getStreamType().equals(StreamType.LIVE_STREAM)) { - viewsAndDate = Localization.shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount()); + viewsAndDate = Localization + .shortWatchingCount(itemBuilder.getContext(), infoItem.getViewCount()); } else { - viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); + viewsAndDate = Localization + .shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); } } @@ -84,10 +94,12 @@ private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) { if (infoItem.getUploadDate() != null) { - String formattedRelativeTime = Localization.relativeTime(infoItem.getUploadDate().date()); + String formattedRelativeTime = Localization + .relativeTime(infoItem.getUploadDate().date()); if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext()) - .getBoolean(itemBuilder.getContext().getString(R.string.show_original_time_ago_key), false)) { + .getBoolean(itemBuilder.getContext() + .getString(R.string.show_original_time_ago_key), false)) { formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")"; } return formattedRelativeTime; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 6173e53f9..da6c9e82f 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -1,11 +1,12 @@ package org.schabi.newpipe.info_list.holder; -import androidx.core.content.ContextCompat; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.core.content.ContextCompat; + import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.InfoItem; @@ -21,14 +22,14 @@ import java.util.concurrent.TimeUnit; public class StreamMiniInfoItemHolder extends InfoItemHolder { - public final ImageView itemThumbnailView; public final TextView itemVideoTitleView; public final TextView itemUploaderView; public final TextView itemDurationView; - public final AnimatedProgressBar itemProgressView; + private final AnimatedProgressBar itemProgressView; - StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -38,13 +39,16 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { itemProgressView = itemView.findViewById(R.id.itemProgressView); } - public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + public StreamMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { this(infoItemBuilder, R.layout.list_stream_mini_item, parent); } @Override - public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof StreamInfoItem)) return; + public void updateFromItem(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { + if (!(infoItem instanceof StreamInfoItem)) { + return; + } final StreamInfoItem item = (StreamInfoItem) infoItem; itemVideoTitleView.setText(item.getName()); @@ -56,11 +60,13 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); - StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; + StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem) + .blockingGet()[0]; if (state2 != null) { itemProgressView.setVisibility(View.VISIBLE); itemProgressView.setMax((int) item.getDuration()); - itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state2.getProgressTime())); + itemProgressView.setProgress((int) TimeUnit.MILLISECONDS + .toSeconds(state2.getProgressTime())); } else { itemProgressView.setVisibility(View.GONE); } @@ -103,16 +109,20 @@ public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager h } @Override - public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { + public void updateState(final InfoItem infoItem, + final HistoryRecordManager historyRecordManager) { final StreamInfoItem item = (StreamInfoItem) infoItem; StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; - if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) { + if (state != null && item.getDuration() > 0 + && item.getStreamType() != StreamType.LIVE_STREAM) { itemProgressView.setMax((int) item.getDuration()); if (itemProgressView.getVisibility() == View.VISIBLE) { - itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); } else { - itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgress((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); AnimationUtils.animateView(itemProgressView, true, 500); } } else if (itemProgressView.getVisibility() == View.VISIBLE) { @@ -134,4 +144,4 @@ private void disableLongClick() { itemView.setLongClickable(false); itemView.setOnLongClickListener(null); } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 414a9b6b5..650953bea 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -5,16 +5,17 @@ import android.content.res.Resources; import android.os.Bundle; import android.preference.PreferenceManager; -import androidx.fragment.app.Fragment; -import androidx.appcompat.app.ActionBar; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.View; +import androidx.appcompat.app.ActionBar; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.R; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.list.ListViewContract; @@ -25,10 +26,14 @@ * This fragment is design to be used with persistent data such as * {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained * in the list adapter to avoid extra writes when the it exits or re-enters its lifecycle. - * + *

* This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is * called and is memory efficient when in backstack. - * */ + *

+ * + * @param List of {@link org.schabi.newpipe.database.LocalItem}s + * @param {@link Void} + */ public abstract class BaseLocalListFragment extends BaseStateFragment implements ListViewContract, SharedPreferences.OnSharedPreferenceChangeListener { @@ -36,21 +41,19 @@ public abstract class BaseLocalListFragment extends BaseStateFragment // Views //////////////////////////////////////////////////////////////////////////*/ - protected View headerRootView; - protected View footerRootView; - + private static final int LIST_MODE_UPDATE_FLAG = 0x32; + private View headerRootView; + private View footerRootView; protected LocalItemListAdapter itemListAdapter; protected RecyclerView itemsList; private int updateFlags = 0; - private static final int LIST_MODE_UPDATE_FLAG = 0x32; - /*////////////////////////////////////////////////////////////////////////// // Lifecycle - Creation //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); PreferenceManager.getDefaultSharedPreferences(activity) @@ -70,8 +73,9 @@ public void onResume() { if (updateFlags != 0) { if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { final boolean useGrid = isGridLayout(); - itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); - itemListAdapter.setGridItemVariants(useGrid); + itemsList.setLayoutManager( + useGrid ? getGridLayoutManager() : getListLayoutManager()); + itemListAdapter.setUseGridVariant(useGrid); itemListAdapter.notifyDataSetChanged(); } updateFlags = 0; @@ -94,7 +98,8 @@ 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(itemListAdapter.getSpanSizeLookup(spanCount)); return lm; @@ -105,7 +110,7 @@ protected RecyclerView.LayoutManager getListLayoutManager() { } @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); itemListAdapter = new LocalItemListAdapter(activity); @@ -114,9 +119,11 @@ protected void initViews(View rootView, Bundle savedInstanceState) { itemsList = rootView.findViewById(R.id.items_list); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); - itemListAdapter.setGridItemVariants(useGrid); - itemListAdapter.setHeader(headerRootView = getListHeader()); - itemListAdapter.setFooter(footerRootView = getListFooter()); + itemListAdapter.setUseGridVariant(useGrid); + headerRootView = getListHeader(); + itemListAdapter.setHeader(headerRootView); + footerRootView = getListFooter(); + itemListAdapter.setFooter(footerRootView); itemsList.setAdapter(itemListAdapter); } @@ -131,13 +138,17 @@ protected void initListeners() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + - "], inflater = [" + inflater + "]"); + if (DEBUG) { + Log.d(TAG, "onCreateOptionsMenu() called with: " + + "menu = [" + menu + "], inflater = [" + inflater + "]"); + } final ActionBar supportActionBar = activity.getSupportActionBar(); - if (supportActionBar == null) return; + if (supportActionBar == null) { + return; + } supportActionBar.setDisplayShowTitleEnabled(true); } @@ -158,7 +169,7 @@ public void onDestroyView() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); resetFragment(); } @@ -166,24 +177,36 @@ public void startLoading(boolean forceLoad) { @Override public void showLoading() { super.showLoading(); - if (itemsList != null) animateView(itemsList, false, 200); - if (headerRootView != null) animateView(headerRootView, false, 200); + if (itemsList != null) { + animateView(itemsList, false, 200); + } + if (headerRootView != null) { + animateView(headerRootView, false, 200); + } } @Override public void hideLoading() { super.hideLoading(); - if (itemsList != null) animateView(itemsList, true, 200); - if (headerRootView != null) animateView(headerRootView, true, 200); + if (itemsList != null) { + animateView(itemsList, true, 200); + } + if (headerRootView != null) { + animateView(headerRootView, true, 200); + } } @Override - public void showError(String message, boolean showRetryButton) { + public void showError(final String message, final boolean showRetryButton) { super.showError(message, showRetryButton); showListFooter(false); - if (itemsList != null) animateView(itemsList, false, 200); - if (headerRootView != null) animateView(headerRootView, false, 200); + if (itemsList != null) { + animateView(itemsList, false, 200); + } + if (headerRootView != null) { + animateView(headerRootView, false, 200); + } } @Override @@ -194,14 +217,18 @@ public void showEmptyState() { @Override public void showListFooter(final boolean show) { - if (itemsList == null) return; + if (itemsList == null) { + return; + } itemsList.post(() -> { - if (itemListAdapter != null) itemListAdapter.showFooter(show); + if (itemListAdapter != null) { + itemListAdapter.showFooter(show); + } }); } @Override - public void handleNextItems(N result) { + public void handleNextItems(final N result) { isLoading.set(false); } @@ -210,30 +237,35 @@ public void handleNextItems(N result) { //////////////////////////////////////////////////////////////////////////*/ protected void resetFragment() { - if (itemListAdapter != null) itemListAdapter.clearStreamItemList(); + if (itemListAdapter != null) { + itemListAdapter.clearStreamItemList(); + } } @Override - protected boolean onError(Throwable exception) { + protected boolean onError(final Throwable exception) { resetFragment(); return super.onError(exception); } @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, + final String key) { if (key.equals(getString(R.string.list_view_mode_key))) { updateFlags |= LIST_MODE_UPDATE_FLAG; } } protected boolean isGridLayout() { - final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)); - if ("auto".equals(list_mode)) { + final String listMode = PreferenceManager.getDefaultSharedPreferences(activity) + .getString(getString(R.string.list_view_mode_key), + getString(R.string.list_view_mode_value)); + if ("auto".equals(listMode)) { final Configuration configuration = getResources().getConfiguration(); return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); } else { - return "grid".equals(list_mode); + return "grid".equals(listMode); } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/HeaderFooterHolder.java b/app/src/main/java/org/schabi/newpipe/local/HeaderFooterHolder.java index 9ee33b3c4..5aac75119 100644 --- a/app/src/main/java/org/schabi/newpipe/local/HeaderFooterHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/HeaderFooterHolder.java @@ -1,13 +1,14 @@ package org.schabi.newpipe.local; -import androidx.recyclerview.widget.RecyclerView; import android.view.View; +import androidx.recyclerview.widget.RecyclerView; + public class HeaderFooterHolder extends RecyclerView.ViewHolder { public View view; - public HeaderFooterHolder(View v) { + public HeaderFooterHolder(final View v) { super(v); view = v; } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java index 0fbab0398..d7aaddcc4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java @@ -30,14 +30,12 @@ */ public class LocalItemBuilder { - private static final String TAG = LocalItemBuilder.class.toString(); - private final Context context; private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnClickGesture onSelectedListener; - public LocalItemBuilder(Context context) { + public LocalItemBuilder(final Context context) { this.context = context; } @@ -54,7 +52,7 @@ public OnClickGesture getOnItemSelectedListener() { return onSelectedListener; } - public void setOnItemSelectedListener(OnClickGesture listener) { + public void setOnItemSelectedListener(final OnClickGesture listener) { this.onSelectedListener = listener; } } diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java index 89c1267c8..ad0524f92 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java @@ -1,13 +1,14 @@ package org.schabi.newpipe.local; import android.content.Context; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.model.StreamStateEntity; @@ -50,7 +51,6 @@ */ public class LocalItemListAdapter extends RecyclerView.Adapter { - private static final String TAG = LocalItemListAdapter.class.getSimpleName(); private static final boolean DEBUG = false; @@ -63,8 +63,8 @@ public class LocalItemListAdapter extends RecyclerView.Adapter localItems; @@ -76,7 +76,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter(); @@ -84,7 +84,7 @@ public LocalItemListAdapter(Context context) { Localization.getPreferredLocale(context)); } - public void setSelectedListener(OnClickGesture listener) { + public void setSelectedListener(final OnClickGesture listener) { localItemBuilder.setOnItemSelectedListener(listener); } @@ -92,28 +92,34 @@ public void unsetSelectedListener() { localItemBuilder.setOnItemSelectedListener(null); } - public void addItems(@Nullable List data) { + public void addItems(@Nullable final List data) { if (data == null) { return; } - if (DEBUG) Log.d(TAG, "addItems() before > localItems.size() = " + - localItems.size() + ", data.size() = " + data.size()); + if (DEBUG) { + Log.d(TAG, "addItems() before > localItems.size() = " + + localItems.size() + ", data.size() = " + data.size()); + } int offsetStart = sizeConsideringHeader(); localItems.addAll(data); - if (DEBUG) Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + - ", localItems.size() = " + localItems.size() + - ", header = " + header + ", footer = " + footer + - ", showFooter = " + showFooter); + if (DEBUG) { + Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", " + + "localItems.size() = " + localItems.size() + ", " + + "header = " + header + ", footer = " + footer + ", " + + "showFooter = " + showFooter); + } notifyItemRangeInserted(offsetStart, data.size()); if (footer != null && showFooter) { int footerNow = sizeConsideringHeader(); notifyItemMoved(offsetStart, footerNow); - if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart + - " to " + footerNow); + if (DEBUG) { + Log.d(TAG, "addItems() footer from " + offsetStart + + " to " + footerNow); + } } } @@ -123,12 +129,16 @@ public void removeItem(final LocalItem data) { notifyItemRemoved(index + (header != null ? 1 : 0)); } - public boolean swapItems(int fromAdapterPosition, int toAdapterPosition) { + public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) { final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition); final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition); - if (actualFrom < 0 || actualTo < 0) return false; - if (actualFrom >= localItems.size() || actualTo >= localItems.size()) return false; + if (actualFrom < 0 || actualTo < 0) { + return false; + } + if (actualFrom >= localItems.size() || actualTo >= localItems.size()) { + return false; + } localItems.add(actualTo, localItems.remove(actualFrom)); notifyItemMoved(fromAdapterPosition, toAdapterPosition); @@ -143,27 +153,36 @@ public void clearStreamItemList() { notifyDataSetChanged(); } - public void setGridItemVariants(boolean useGridVariant) { + public void setUseGridVariant(final boolean useGridVariant) { this.useGridVariant = useGridVariant; } - public void setHeader(View header) { + public void setHeader(final View header) { boolean changed = header != this.header; this.header = header; - if (changed) notifyDataSetChanged(); + if (changed) { + notifyDataSetChanged(); + } } - public void setFooter(View view) { + public void setFooter(final View view) { this.footer = view; } - public void showFooter(boolean show) { - if (DEBUG) Log.d(TAG, "showFooter() called with: show = [" + show + "]"); - if (show == showFooter) return; + public void showFooter(final boolean show) { + if (DEBUG) { + Log.d(TAG, "showFooter() called with: show = [" + show + "]"); + } + if (show == showFooter) { + return; + } showFooter = show; - if (show) notifyItemInserted(sizeConsideringHeader()); - else notifyItemRemoved(sizeConsideringHeader()); + if (show) { + notifyItemInserted(sizeConsideringHeader()); + } else { + notifyItemRemoved(sizeConsideringHeader()); + } } private int adapterOffsetWithoutHeader(final int offset) { @@ -181,21 +200,27 @@ public ArrayList getItemsList() { @Override public int getItemCount() { int count = localItems.size(); - if (header != null) count++; - if (footer != null && showFooter) count++; + if (header != null) { + count++; + } + if (footer != null && showFooter) { + count++; + } if (DEBUG) { - Log.d(TAG, "getItemCount() called, count = " + count + - ", localItems.size() = " + localItems.size() + - ", header = " + header + ", footer = " + footer + - ", showFooter = " + showFooter); + Log.d(TAG, "getItemCount() called, count = " + count + ", " + + "localItems.size() = " + localItems.size() + ", " + + "header = " + header + ", footer = " + footer + ", " + + "showFooter = " + showFooter); } return count; } @Override public int getItemViewType(int position) { - if (DEBUG) Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); + if (DEBUG) { + Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); + } if (header != null && position == 0) { return HEADER_TYPE; @@ -208,23 +233,34 @@ public int getItemViewType(int position) { final LocalItem item = localItems.get(position); switch (item.getLocalItemType()) { - case PLAYLIST_LOCAL_ITEM: return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE; - case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE; - - case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE; - case STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE; + case PLAYLIST_LOCAL_ITEM: + return useGridVariant + ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE; + case PLAYLIST_REMOTE_ITEM: + return useGridVariant + ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE; + + case PLAYLIST_STREAM_ITEM: + return useGridVariant + ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE; + case STATISTIC_STREAM_ITEM: + return useGridVariant + ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE; default: - Log.e(TAG, "No holder type has been considered for item: [" + - item.getLocalItemType() + "]"); + Log.e(TAG, "No holder type has been considered for item: [" + + item.getLocalItemType() + "]"); return -1; } } @NonNull @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { - if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + - parent + "], type = [" + type + "]"); + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int type) { + if (DEBUG) { + Log.d(TAG, "onCreateViewHolder() called with: " + + "parent = [" + parent + "], type = [" + type + "]"); + } switch (type) { case HEADER_TYPE: return new HeaderFooterHolder(header); @@ -253,15 +289,21 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int } @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + - holder.getClass().getSimpleName() + "], position = [" + position + "]"); + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { + if (DEBUG) { + Log.d(TAG, "onBindViewHolder() called with: " + + "holder = [" + holder.getClass().getSimpleName() + "], " + + "position = [" + position + "]"); + } if (holder instanceof LocalItemHolder) { // If header isn't null, offset the items by -1 - if (header != null) position--; + if (header != null) { + position--; + } - ((LocalItemHolder) holder).updateFromItem(localItems.get(position), recordManager, dateFormat); + ((LocalItemHolder) holder) + .updateFromItem(localItems.get(position), recordManager, dateFormat); } else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { ((HeaderFooterHolder) holder).view = header; } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() @@ -271,13 +313,16 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) { + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position, + @NonNull final List payloads) { if (!payloads.isEmpty() && holder instanceof LocalItemHolder) { for (Object payload : payloads) { if (payload instanceof StreamStateEntity) { - ((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager); + ((LocalItemHolder) holder).updateState(localItems + .get(header == null ? position : position - 1), recordManager); } else if (payload instanceof Boolean) { - ((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager); + ((LocalItemHolder) holder).updateState(localItems + .get(header == null ? position : position - 1), recordManager); } } } else { @@ -288,7 +333,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { return new GridLayoutManager.SpanSizeLookup() { @Override - public int getSpanSize(int position) { + public int getSpanSize(final int position) { final int type = getItemViewType(position); return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1; } diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index 761fa4360..3be50022f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -5,15 +5,15 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.EditText; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import io.reactivex.disposables.Disposable; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; @@ -39,10 +39,9 @@ import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; -public final class BookmarkFragment - extends BaseLocalListFragment, Void> { - +public final class BookmarkFragment extends BaseLocalListFragment, Void> { @State protected Parcelable itemsListState; @@ -55,10 +54,26 @@ public final class BookmarkFragment // Fragment LifeCycle - Creation /////////////////////////////////////////////////////////////////////////// + private static List merge( + final List localPlaylists, + final List remotePlaylists) { + List items = new ArrayList<>( + localPlaylists.size() + remotePlaylists.size()); + items.addAll(localPlaylists); + items.addAll(remotePlaylists); + + Collections.sort(items, (left, right) -> + left.getOrderingName().compareToIgnoreCase(right.getOrderingName())); + + return items; + } + @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (activity == null) return; + if (activity == null) { + return; + } final AppDatabase database = NewPipeDatabase.getInstance(activity); localPlaylistManager = new LocalPlaylistManager(database); remotePlaylistManager = new RemotePlaylistManager(database); @@ -67,41 +82,44 @@ public void onCreate(Bundle savedInstanceState) { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + final Bundle savedInstanceState) { - if(!useAsFrontPage) { + if (!useAsFrontPage) { setTitle(activity.getString(R.string.tab_bookmarks)); } return inflater.inflate(R.layout.fragment_bookmarks, container, false); } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Views + /////////////////////////////////////////////////////////////////////////// @Override - public void setUserVisibleHint(boolean isVisibleToUser) { + public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (activity != null && isVisibleToUser) { setTitle(activity.getString(R.string.tab_bookmarks)); } } - /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Views - /////////////////////////////////////////////////////////////////////////// - @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Loading + /////////////////////////////////////////////////////////////////////////// + @Override protected void initListeners() { super.initListeners(); itemListAdapter.setSelectedListener(new OnClickGesture() { @Override - public void selected(LocalItem selectedItem) { + public void selected(final LocalItem selectedItem) { final FragmentManager fragmentManager = getFM(); if (selectedItem instanceof PlaylistMetadataEntry) { @@ -120,7 +138,7 @@ public void selected(LocalItem selectedItem) { } @Override - public void held(LocalItem selectedItem) { + public void held(final LocalItem selectedItem) { if (selectedItem instanceof PlaylistMetadataEntry) { showLocalDialog((PlaylistMetadataEntry) selectedItem); } else if (selectedItem instanceof PlaylistRemoteEntity) { @@ -131,26 +149,20 @@ public void held(LocalItem selectedItem) { } /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Loading + // Fragment LifeCycle - Destruction /////////////////////////////////////////////////////////////////////////// @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); - Flowable.combineLatest( - localPlaylistManager.getPlaylists(), - remotePlaylistManager.getPlaylists(), - BookmarkFragment::merge - ).onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getPlaylistsSubscriber()); + Flowable.combineLatest(localPlaylistManager.getPlaylists(), + remotePlaylistManager.getPlaylists(), BookmarkFragment::merge) + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getPlaylistsSubscriber()); } - /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Destruction - /////////////////////////////////////////////////////////////////////////// - @Override public void onPause() { super.onPause(); @@ -161,16 +173,26 @@ public void onPause() { public void onDestroyView() { super.onDestroyView(); - if (disposables != null) disposables.clear(); - if (databaseSubscription != null) databaseSubscription.cancel(); + if (disposables != null) { + disposables.clear(); + } + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } databaseSubscription = null; } + /////////////////////////////////////////////////////////////////////////// + // Subscriptions Loader + /////////////////////////////////////////////////////////////////////////// + @Override public void onDestroy() { super.onDestroy(); - if (disposables != null) disposables.dispose(); + if (disposables != null) { + disposables.dispose(); + } disposables = null; localPlaylistManager = null; @@ -178,39 +200,41 @@ public void onDestroy() { itemsListState = null; } - /////////////////////////////////////////////////////////////////////////// - // Subscriptions Loader - /////////////////////////////////////////////////////////////////////////// - private Subscriber> getPlaylistsSubscriber() { return new Subscriber>() { @Override - public void onSubscribe(Subscription s) { + public void onSubscribe(final Subscription s) { showLoading(); - if (databaseSubscription != null) databaseSubscription.cancel(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } databaseSubscription = s; databaseSubscription.request(1); } @Override - public void onNext(List subscriptions) { + public void onNext(final List subscriptions) { handleResult(subscriptions); - if (databaseSubscription != null) databaseSubscription.request(1); + if (databaseSubscription != null) { + databaseSubscription.request(1); + } } @Override - public void onError(Throwable exception) { + public void onError(final Throwable exception) { BookmarkFragment.this.onError(exception); } @Override - public void onComplete() { - } + public void onComplete() { } }; } + /////////////////////////////////////////////////////////////////////////// + // Fragment Error Handling + /////////////////////////////////////////////////////////////////////////// @Override - public void handleResult(@NonNull List result) { + public void handleResult(@NonNull final List result) { super.handleResult(result); itemListAdapter.clearStreamItemList(); @@ -227,55 +251,58 @@ public void handleResult(@NonNull List result) { } hideLoading(); } - /////////////////////////////////////////////////////////////////////////// - // Fragment Error Handling - /////////////////////////////////////////////////////////////////////////// @Override - protected boolean onError(Throwable exception) { - if (super.onError(exception)) return true; + protected boolean onError(final Throwable exception) { + if (super.onError(exception)) { + return true; + } onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Bookmark", R.string.general_error); return true; } + /////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////// + @Override protected void resetFragment() { super.resetFragment(); - if (disposables != null) disposables.clear(); + if (disposables != null) { + disposables.clear(); + } } - /////////////////////////////////////////////////////////////////////////// - // Utils - /////////////////////////////////////////////////////////////////////////// - private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) { showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid())); } - private void showLocalDialog(PlaylistMetadataEntry selectedItem) { + private void showLocalDialog(final PlaylistMetadataEntry selectedItem) { View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null); EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text); editText.setText(selectedItem.name); Builder builder = new AlertDialog.Builder(activity); builder.setView(dialogView) - .setPositiveButton(R.string.rename_playlist, (dialog, which) -> { - changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()); - }) - .setNegativeButton(R.string.cancel, null) - .setNeutralButton(R.string.delete, (dialog, which) -> { - showDeleteDialog(selectedItem.name, - localPlaylistManager.deletePlaylist(selectedItem.uid)); - dialog.dismiss(); - }) - .create() - .show(); + .setPositiveButton(R.string.rename_playlist, (dialog, which) -> { + changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()); + }) + .setNegativeButton(R.string.cancel, null) + .setNeutralButton(R.string.delete, (dialog, which) -> { + showDeleteDialog(selectedItem.name, + localPlaylistManager.deletePlaylist(selectedItem.uid)); + dialog.dismiss(); + }) + .create() + .show(); } private void showDeleteDialog(final String name, final Single deleteReactor) { - if (activity == null || disposables == null) return; + if (activity == null || disposables == null) { + return; + } new AlertDialog.Builder(activity) .setTitle(name) @@ -284,40 +311,27 @@ private void showDeleteDialog(final String name, final Single deleteRea .setPositiveButton(R.string.delete, (dialog, i) -> disposables.add(deleteReactor .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> {/*Do nothing on success*/}, this::onError)) + .subscribe(ignored -> { /*Do nothing on success*/ }, this::onError)) ) .setNegativeButton(R.string.cancel, null) .show(); } - private void changeLocalPlaylistName(long id, String name) { + private void changeLocalPlaylistName(final long id, final String name) { if (localPlaylistManager == null) { return; } if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + id + - "] with new name=[" + name + "] items"); + Log.d(TAG, "Updating playlist id=[" + id + "] " + + "with new name=[" + name + "] items"); } localPlaylistManager.renamePlaylist(id, name); final Disposable disposable = localPlaylistManager.renamePlaylist(id, name) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(longs -> {/*Do nothing on success*/}, this::onError); + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(longs -> { /*Do nothing on success*/ }, this::onError); disposables.add(disposable); } - - private static List merge(final List localPlaylists, - final List remotePlaylists) { - List items = new ArrayList<>( - localPlaylists.size() + remotePlaylists.size()); - items.addAll(localPlaylists); - items.addAll(remotePlaylists); - - Collections.sort(items, (left, right) -> - left.getOrderingName().compareToIgnoreCase(right.getOrderingName())); - - return items; - } } diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index 81058eee6..4eb97bbbf 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -69,13 +69,13 @@ public static PlaylistAppendDialog fromPlayQueueItems(final List //////////////////////////////////////////////////////////////////////////*/ @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { return inflater.inflate(R.layout.dialog_playlists, container); } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final LocalPlaylistManager playlistManager = @@ -84,9 +84,10 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat playlistAdapter = new LocalItemListAdapter(getActivity()); playlistAdapter.setSelectedListener(new OnClickGesture() { @Override - public void selected(LocalItem selectedItem) { - if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) + public void selected(final LocalItem selectedItem) { + if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) { return; + } onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, getStreams()); } @@ -126,7 +127,9 @@ public void onDestroyView() { //////////////////////////////////////////////////////////////////////////*/ public void openCreatePlaylistDialog() { - if (getStreams() == null || getFragmentManager() == null) return; + if (getStreams() == null || getFragmentManager() == null) { + return; + } PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG); getDialog().dismiss(); @@ -145,16 +148,19 @@ private void onPlaylistsReceived(@NonNull final List play } } - private void onPlaylistSelected(@NonNull LocalPlaylistManager manager, - @NonNull PlaylistMetadataEntry playlist, - @NonNull List streams) { - if (getStreams() == null) return; + private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager, + @NonNull final PlaylistMetadataEntry playlist, + @NonNull final List streams) { + if (getStreams() == null) { + return; + } final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { - playlistDisposables.add(manager.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) + playlistDisposables.add(manager + .changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(ignored -> successToast.show())); } diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java index 0507d3dd0..b25ec7288 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistCreationDialog.java @@ -3,12 +3,13 @@ import android.app.AlertDialog; import android.app.Dialog; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.view.View; import android.widget.EditText; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; @@ -19,8 +20,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; public final class PlaylistCreationDialog extends PlaylistDialog { - private static final String TAG = PlaylistCreationDialog.class.getCanonicalName(); - public static PlaylistCreationDialog newInstance(final List streams) { PlaylistCreationDialog dialog = new PlaylistCreationDialog(); dialog.setInfo(streams); @@ -33,8 +32,10 @@ public static PlaylistCreationDialog newInstance(final List stream @NonNull @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - if (getStreams() == null) return super.onCreateDialog(savedInstanceState); + public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { + if (getStreams() == null) { + return super.onCreateDialog(savedInstanceState); + } View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null); EditText nameInput = dialogView.findViewById(R.id.playlist_name); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index 12e57808e..9ca8733cc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -2,10 +2,11 @@ import android.app.Dialog; import android.os.Bundle; +import android.view.Window; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; -import android.view.Window; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.util.StateSaver; @@ -14,7 +15,6 @@ import java.util.Queue; public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead { - private List streamEntities; private StateSaver.SavedState savedState; @@ -32,7 +32,7 @@ protected List getStreams() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); savedState = StateSaver.tryToRestore(savedInstanceState, this); } @@ -45,7 +45,7 @@ public void onDestroy() { @NonNull @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { + public Dialog onCreateDialog(final Bundle savedInstanceState) { final Dialog dialog = super.onCreateDialog(savedInstanceState); //remove title final Window window = dialog.getWindow(); @@ -66,18 +66,18 @@ public String generateSuffix() { } @Override - public void writeTo(Queue objectsToSave) { + public void writeTo(final Queue objectsToSave) { objectsToSave.add(streamEntities); } @Override @SuppressWarnings("unchecked") - public void readFrom(@NonNull Queue savedObjects) { + public void readFrom(@NonNull final Queue savedObjects) { streamEntities = (List) savedObjects.poll(); } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); if (getActivity() != null) { savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(), diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index d41a2e37b..e7ff8b86a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -41,7 +41,9 @@ import java.util.* class FeedFragment : BaseListFragment() { private lateinit var viewModel: FeedViewModel - @State @JvmField var listState: Parcelable? = null + @State + @JvmField + var listState: Parcelable? = null private var groupId = FeedGroupEntity.GROUP_ALL_ID private var groupName = "" @@ -49,13 +51,14 @@ class FeedFragment : BaseListFragment() { init { setHasOptionsMenu(true) - useDefaultStateSaving(false) + setUseDefaultStateSaving(false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) ?: FeedGroupEntity.GROUP_ALL_ID + groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) + ?: FeedGroupEntity.GROUP_ALL_ID groupName = arguments?.getString(KEY_GROUP_NAME) ?: "" } @@ -107,7 +110,7 @@ class FeedFragment : BaseListFragment() { inflater.inflate(R.menu.menu_feed_fragment, menu) if (useAsFrontPage) { - menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.findItem(R.id.menu_item_feed_help).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) } } @@ -324,4 +327,4 @@ class FeedFragment : BaseListFragment() { return feedFragment } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java index c4ca08a0a..e7ccd07d2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.local.history; import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; @@ -14,19 +15,19 @@ /** - * Adapter for history entries - * @param the type of the entries + * This is an adapter for history entries. + * + * @param the type of the entries * @param the type of the view holder */ -public abstract class HistoryEntryAdapter extends RecyclerView.Adapter { - +public abstract class HistoryEntryAdapter + extends RecyclerView.Adapter { private final ArrayList mEntries; private final DateFormat mDateFormat; private final Context mContext; private OnHistoryItemClickListener onHistoryItemClickListener = null; - - public HistoryEntryAdapter(Context context) { + public HistoryEntryAdapter(final Context context) { super(); mContext = context; mEntries = new ArrayList<>(); @@ -34,7 +35,7 @@ public HistoryEntryAdapter(Context context) { Localization.getPreferredLocale(context)); } - public void setEntries(@NonNull Collection historyEntries) { + public void setEntries(@NonNull final Collection historyEntries) { mEntries.clear(); mEntries.addAll(historyEntries); notifyDataSetChanged(); @@ -49,7 +50,7 @@ public void clear() { notifyDataSetChanged(); } - protected String getFormattedDate(Date date) { + protected String getFormattedDate(final Date date) { return mDateFormat.format(date); } @@ -63,10 +64,10 @@ public int getItemCount() { } @Override - public void onBindViewHolder(VH holder, int position) { + public void onBindViewHolder(final VH holder, final int position) { final E entry = mEntries.get(position); holder.itemView.setOnClickListener(v -> { - if(onHistoryItemClickListener != null) { + if (onHistoryItemClickListener != null) { onHistoryItemClickListener.onHistoryItemClick(entry); } }); @@ -83,14 +84,15 @@ public void onBindViewHolder(VH holder, int position) { } @Override - public void onViewRecycled(VH holder) { + public void onViewRecycled(final VH holder) { super.onViewRecycled(holder); holder.itemView.setOnClickListener(null); } abstract void onBindViewHolder(VH holder, E entry, int position); - public void setOnHistoryItemClickListener(@Nullable OnHistoryItemClickListener onHistoryItemClickListener) { + public void setOnHistoryItemClickListener( + @Nullable final OnHistoryItemClickListener onHistoryItemClickListener) { this.onHistoryItemClickListener = onHistoryItemClickListener; } @@ -100,6 +102,7 @@ public boolean isEmpty() { public interface OnHistoryItemClickListener { void onHistoryItemClick(E item); + void onHistoryItemLongClick(E item); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryListener.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryListener.java deleted file mode 100644 index fc039f770..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryListener.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.schabi.newpipe.local.history; - -import androidx.annotation.Nullable; - -import org.schabi.newpipe.extractor.stream.AudioStream; -import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.extractor.stream.VideoStream; - -public interface HistoryListener { - /** - * Called when a video is played - * - * @param streamInfo the stream info - * @param videoStream the video stream that is played. Can be null if it's not sure what - * quality was viewed (e.g. with Kodi). - */ - void onVideoPlayed(StreamInfo streamInfo, @Nullable VideoStream videoStream); - - /** - * Called when the audio is played in the background - * - * @param streamInfo the stream info - * @param audioStream the audio stream that is played - */ - void onAudioPlayed(StreamInfo streamInfo, AudioStream audioStream); - - /** - * Called when the user searched for something - * - * @param serviceId which service the search was done - * @param query what the user searched for - */ - void onSearch(int serviceId, String query); -} diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index d208f92b3..ba90ae05a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; + import androidx.annotation.NonNull; import org.schabi.newpipe.NewPipeDatabase; @@ -55,7 +56,6 @@ import io.reactivex.schedulers.Schedulers; public class HistoryRecordManager { - private final AppDatabase database; private final StreamDAO streamTable; private final StreamHistoryDAO streamHistoryTable; @@ -81,7 +81,9 @@ public HistoryRecordManager(final Context context) { /////////////////////////////////////////////////////// public Maybe onViewed(final StreamInfo info) { - if (!isStreamHistoryEnabled()) return Maybe.empty(); + if (!isStreamHistoryEnabled()) { + return Maybe.empty(); + } final Date currentTime = new Date(); return Maybe.fromCallable(() -> database.runInTransaction(() -> { @@ -149,7 +151,9 @@ private boolean isStreamHistoryEnabled() { /////////////////////////////////////////////////////// public Maybe onSearched(final int serviceId, final String search) { - if (!isSearchHistoryEnabled()) return Maybe.empty(); + if (!isSearchHistoryEnabled()) { + return Maybe.empty(); + } final Date currentTime = new Date(); final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search); @@ -231,11 +235,13 @@ public Completable saveStreamState(@NonNull final StreamInfo info, final long pr public Single loadStreamState(final InfoItem info) { return Single.fromCallable(() -> { - final List entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst(); + final List entities = streamTable + .getStream(info.getServiceId(), info.getUrl()).blockingFirst(); if (entities.isEmpty()) { return new StreamStateEntity[]{null}; } - final List states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst(); + final List states = streamStateTable + .getState(entities.get(0).getUid()).blockingFirst(); if (states.isEmpty()) { return new StreamStateEntity[]{null}; } @@ -247,12 +253,14 @@ public Single> loadStreamStateBatch(final List return Single.fromCallable(() -> { final List result = new ArrayList<>(infos.size()); for (InfoItem info : infos) { - final List entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst(); + final List entities = streamTable + .getStream(info.getServiceId(), info.getUrl()).blockingFirst(); if (entities.isEmpty()) { result.add(null); continue; } - final List states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst(); + final List states = streamStateTable + .getState(entities.get(0).getUid()).blockingFirst(); if (states.isEmpty()) { result.add(null); continue; @@ -263,7 +271,8 @@ public Single> loadStreamStateBatch(final List }).subscribeOn(Schedulers.io()); } - public Single> loadLocalStreamStateBatch(final List items) { + public Single> loadLocalStreamStateBatch( + final List items) { return Single.fromCallable(() -> { final List result = new ArrayList<>(items.size()); for (LocalItem item : items) { @@ -278,7 +287,8 @@ public Single> loadLocalStreamStateBatch(final List states = streamStateTable.getState(streamId).blockingFirst(); + final List states = streamStateTable.getState(streamId) + .blockingFirst(); if (states.isEmpty()) { result.add(null); continue; diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index a54c2a9a4..bf1f776e4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -4,10 +4,6 @@ import android.content.Context; import android.os.Bundle; import android.os.Parcelable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.snackbar.Snackbar; -import androidx.appcompat.app.AlertDialog; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -18,6 +14,12 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.snackbar.Snackbar; + import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.R; @@ -48,7 +50,10 @@ public class StatisticsPlaylistFragment extends BaseLocalListFragment, Void> { - + private final CompositeDisposable disposables = new CompositeDisposable(); + @State + Parcelable itemsListState; + private StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED; private View headerPlayAllButton; private View headerPopupButton; private View headerBackgroundButton; @@ -56,55 +61,44 @@ public class StatisticsPlaylistFragment private View sortButton; private ImageView sortButtonIcon; private TextView sortButtonText; - - @State - protected Parcelable itemsListState; - /* Used for independent events */ private Subscription databaseSubscription; private HistoryRecordManager recordManager; - private final CompositeDisposable disposables = new CompositeDisposable(); - - private enum StatisticSortMode { - LAST_PLAYED, - MOST_PLAYED, - } - - StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED; - protected List processResult(final List results) { + private List processResult(final List results) { switch (sortMode) { case LAST_PLAYED: Collections.sort(results, (left, right) -> - right.getLatestAccessDate().compareTo(left.getLatestAccessDate())); + right.getLatestAccessDate().compareTo(left.getLatestAccessDate())); return results; case MOST_PLAYED: Collections.sort(results, (left, right) -> Long.compare(right.getWatchCount(), left.getWatchCount())); return results; - default: return null; + default: + return null; } } - /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Creation - /////////////////////////////////////////////////////////////////////////// - @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); recordManager = new HistoryRecordManager(getContext()); } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Creation + /////////////////////////////////////////////////////////////////////////// + @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_playlist, container, false); } @Override - public void setUserVisibleHint(boolean isVisibleToUser) { + public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (activity != null && isVisibleToUser) { setTitle(activity.getString(R.string.title_activity_history)); @@ -112,27 +106,27 @@ public void setUserVisibleHint(boolean isVisibleToUser) { } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_history, menu); } - /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Views - /////////////////////////////////////////////////////////////////////////// - @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - if(!useAsFrontPage) { + if (!useAsFrontPage) { setTitle(getString(R.string.title_last_played)); } } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Views + /////////////////////////////////////////////////////////////////////////// + @Override protected View getListHeader() { - final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.statistic_playlist_control, - itemsList, false); + final View headerRootLayout = activity.getLayoutInflater() + .inflate(R.layout.statistic_playlist_control, itemsList, false); playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control); headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button); headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button); @@ -149,7 +143,7 @@ protected void initListeners() { itemListAdapter.setSelectedListener(new OnClickGesture() { @Override - public void selected(LocalItem selectedItem) { + public void selected(final LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem; NavigationHelper.openVideoDetailFragment(getFM(), @@ -160,7 +154,7 @@ public void selected(LocalItem selectedItem) { } @Override - public void held(LocalItem selectedItem) { + public void held(final LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { showStreamDialog((StreamStatisticsEntry) selectedItem); } @@ -169,7 +163,7 @@ public void held(LocalItem selectedItem) { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_history_clear: new AlertDialog.Builder(activity) @@ -194,7 +188,8 @@ public boolean onOptionsItemSelected(MenuItem item) { final Disposable onClearOrphans = recordManager.removeOrphanedRecords() .observeOn(AndroidSchedulers.mainThread()) .subscribe( - howManyDeleted -> {}, + howManyDeleted -> { + }, throwable -> ErrorActivity.reportError(getContext(), throwable, SettingsActivity.class, null, @@ -215,12 +210,8 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } - /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Loading - /////////////////////////////////////////////////////////////////////////// - @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); recordManager.getStreamStatistics() .observeOn(AndroidSchedulers.mainThread()) @@ -228,7 +219,7 @@ public void startLoading(boolean forceLoad) { } /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle - Destruction + // Fragment LifeCycle - Loading /////////////////////////////////////////////////////////////////////////// @Override @@ -237,16 +228,30 @@ public void onPause() { itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Destruction + /////////////////////////////////////////////////////////////////////////// + @Override public void onDestroyView() { super.onDestroyView(); - if (itemListAdapter != null) itemListAdapter.unsetSelectedListener(); - if (headerBackgroundButton != null) headerBackgroundButton.setOnClickListener(null); - if (headerPlayAllButton != null) headerPlayAllButton.setOnClickListener(null); - if (headerPopupButton != null) headerPopupButton.setOnClickListener(null); + if (itemListAdapter != null) { + itemListAdapter.unsetSelectedListener(); + } + if (headerBackgroundButton != null) { + headerBackgroundButton.setOnClickListener(null); + } + if (headerPlayAllButton != null) { + headerPlayAllButton.setOnClickListener(null); + } + if (headerPopupButton != null) { + headerPopupButton.setOnClickListener(null); + } - if (databaseSubscription != null) databaseSubscription.cancel(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } databaseSubscription = null; } @@ -257,29 +262,29 @@ public void onDestroy() { itemsListState = null; } - /////////////////////////////////////////////////////////////////////////// - // Statistics Loader - /////////////////////////////////////////////////////////////////////////// - private Subscriber> getHistoryObserver() { return new Subscriber>() { @Override - public void onSubscribe(Subscription s) { + public void onSubscribe(final Subscription s) { showLoading(); - if (databaseSubscription != null) databaseSubscription.cancel(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } databaseSubscription = s; databaseSubscription.request(1); } @Override - public void onNext(List streams) { + public void onNext(final List streams) { handleResult(streams); - if (databaseSubscription != null) databaseSubscription.request(1); + if (databaseSubscription != null) { + databaseSubscription.request(1); + } } @Override - public void onError(Throwable exception) { + public void onError(final Throwable exception) { StatisticsPlaylistFragment.this.onError(exception); } @@ -289,10 +294,16 @@ public void onComplete() { }; } + /////////////////////////////////////////////////////////////////////////// + // Statistics Loader + /////////////////////////////////////////////////////////////////////////// + @Override - public void handleResult(@NonNull List result) { + public void handleResult(@NonNull final List result) { super.handleResult(result); - if (itemListAdapter == null) return; + if (itemListAdapter == null) { + return; + } playlistCtrl.setVisibility(View.VISIBLE); @@ -319,52 +330,60 @@ public void handleResult(@NonNull List result) { hideLoading(); } - /////////////////////////////////////////////////////////////////////////// - // Fragment Error Handling - /////////////////////////////////////////////////////////////////////////// @Override protected void resetFragment() { super.resetFragment(); - if (databaseSubscription != null) databaseSubscription.cancel(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } } + /////////////////////////////////////////////////////////////////////////// + // Fragment Error Handling + /////////////////////////////////////////////////////////////////////////// @Override - protected boolean onError(Throwable exception) { - if (super.onError(exception)) return true; + protected boolean onError(final Throwable exception) { + if (super.onError(exception)) { + return true; + } onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "History Statistics", R.string.general_error); return true; } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - private void toggleSortMode() { - if(sortMode == StatisticSortMode.LAST_PLAYED) { + if (sortMode == StatisticSortMode.LAST_PLAYED) { sortMode = StatisticSortMode.MOST_PLAYED; setTitle(getString(R.string.title_most_played)); - sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext())); + sortButtonIcon + .setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext())); sortButtonText.setText(R.string.title_last_played); } else { sortMode = StatisticSortMode.LAST_PLAYED; setTitle(getString(R.string.title_last_played)); - sortButtonIcon.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext())); + sortButtonIcon + .setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext())); sortButtonText.setText(R.string.title_most_played); } startLoading(true); } - private PlayQueue getPlayQueueStartingAt(StreamStatisticsEntry infoItem) { + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + + private PlayQueue getPlayQueueStartingAt(final StreamStatisticsEntry infoItem) { return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); } private void showStreamDialog(final StreamStatisticsEntry item) { final Context context = getContext(); final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) return; + if (context == null || context.getResources() == null || activity == null) { + return; + } final StreamInfoItem infoItem = item.toStreamInfoItem(); if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { @@ -384,29 +403,31 @@ private void showStreamDialog(final StreamStatisticsEntry item) { StreamDialogEntry.append_playlist, StreamDialogEntry.share); - StreamDialogEntry.start_here_on_popup.setCustomAction( - (fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); + StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItemDuplicate) -> + NavigationHelper + .playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); } - StreamDialogEntry.start_here_on_background.setCustomAction( - (fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); + StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) -> + NavigationHelper + .playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) -> - deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0))); + deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0))); - new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) -> - StreamDialogEntry.clickOn(which, this, infoItem)).show(); + new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), + (dialog, which) -> StreamDialogEntry.clickOn(which, this, infoItem)).show(); } private void deleteEntry(final int index) { final LocalItem infoItem = itemListAdapter.getItemsList() .get(index); - if(infoItem instanceof StreamStatisticsEntry) { + if (infoItem instanceof StreamStatisticsEntry) { final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem; final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( howManyDeleted -> { - if(getView() != null) { + if (getView() != null) { Snackbar.make(getView(), R.string.one_item_deleted, Snackbar.LENGTH_SHORT).show(); } else { @@ -441,5 +462,10 @@ private PlayQueue getPlayQueue(final int index) { } return new SinglePlayQueue(streamInfoItems, index); } + + private enum StatisticSortMode { + LAST_PLAYED, + MOST_PLAYED, + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java index f9da969a5..c4307fcde 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalItemHolder.java @@ -1,9 +1,10 @@ package org.schabi.newpipe.local.holder; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -33,14 +34,15 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder { protected final LocalItemBuilder itemBuilder; - public LocalItemHolder(LocalItemBuilder itemBuilder, int layoutId, ViewGroup parent) { - super(LayoutInflater.from(itemBuilder.getContext()) - .inflate(layoutId, parent, false)); + public LocalItemHolder(final LocalItemBuilder itemBuilder, final int layoutId, + final ViewGroup parent) { + super(LayoutInflater.from(itemBuilder.getContext()).inflate(layoutId, parent, false)); this.itemBuilder = itemBuilder; } - public abstract void updateFromItem(final LocalItem item, HistoryRecordManager historyRecordManager, final DateFormat dateFormat); + public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager, + DateFormat dateFormat); - public void updateState(final LocalItem localItem, HistoryRecordManager historyRecordManager) { - } + public void updateState(final LocalItem localItem, + final HistoryRecordManager historyRecordManager) { } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java index 4276cf721..2b493f4ee 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistGridItemHolder.java @@ -6,8 +6,8 @@ import org.schabi.newpipe.local.LocalItemBuilder; public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder { - - public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); - } + public LocalPlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder, + final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index 1366bd02e..3ff4f707a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -12,18 +12,22 @@ import java.text.DateFormat; public class LocalPlaylistItemHolder extends PlaylistItemHolder { - - public LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { + public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) { super(infoItemBuilder, parent); } - LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); } @Override - public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { - if (!(localItem instanceof PlaylistMetadataEntry)) return; + public void updateFromItem(final LocalItem localItem, + final HistoryRecordManager historyRecordManager, + final DateFormat dateFormat) { + if (!(localItem instanceof PlaylistMetadataEntry)) { + return; + } final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; itemTitleView.setText(item.name); diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java index 6986713bb..e2f936792 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamGridItemHolder.java @@ -6,8 +6,8 @@ import org.schabi.newpipe.local.LocalItemBuilder; public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder { - - public LocalPlaylistStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); //TODO - } + public LocalPlaylistStreamGridItemHolder(final LocalItemBuilder infoItemBuilder, + final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); // TODO + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index 7eef3e67e..ece5f0994 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -1,12 +1,13 @@ package org.schabi.newpipe.local.holder; -import androidx.core.content.ContextCompat; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.core.content.ContextCompat; + import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; @@ -24,15 +25,15 @@ import java.util.concurrent.TimeUnit; public class LocalPlaylistStreamItemHolder extends LocalItemHolder { - public final ImageView itemThumbnailView; public final TextView itemVideoTitleView; - public final TextView itemAdditionalDetailsView; + private final TextView itemAdditionalDetailsView; public final TextView itemDurationView; - public final View itemHandleView; - public final AnimatedProgressBar itemProgressView; + private final View itemHandleView; + private final AnimatedProgressBar itemProgressView; - LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -43,30 +44,41 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { itemProgressView = itemView.findViewById(R.id.itemProgressView); } - public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { + public LocalPlaylistStreamItemHolder(final LocalItemBuilder infoItemBuilder, + final ViewGroup parent) { this(infoItemBuilder, R.layout.list_stream_playlist_item, parent); } @Override - public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { - if (!(localItem instanceof PlaylistStreamEntry)) return; + public void updateFromItem(final LocalItem localItem, + final HistoryRecordManager historyRecordManager, + final DateFormat dateFormat) { + if (!(localItem instanceof PlaylistStreamEntry)) { + return; + } final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; itemVideoTitleView.setText(item.getStreamEntity().getTitle()); - itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getStreamEntity().getUploader(), - NewPipe.getNameOfService(item.getStreamEntity().getServiceId()))); + itemAdditionalDetailsView.setText(Localization + .concatenateStrings(item.getStreamEntity().getUploader(), + NewPipe.getNameOfService(item.getStreamEntity().getServiceId()))); if (item.getStreamEntity().getDuration() > 0) { - itemDurationView.setText(Localization.getDurationString(item.getStreamEntity().getDuration())); + itemDurationView.setText(Localization + .getDurationString(item.getStreamEntity().getDuration())); itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); - StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList() {{ add(localItem); }}).blockingGet().get(0); + StreamStateEntity state = historyRecordManager + .loadLocalStreamStateBatch(new ArrayList() {{ + add(localItem); + }}).blockingGet().get(0); if (state != null) { itemProgressView.setVisibility(View.VISIBLE); itemProgressView.setMax((int) item.getStreamEntity().getDuration()); - itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgress((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); } else { itemProgressView.setVisibility(View.GONE); } @@ -97,17 +109,25 @@ public void updateFromItem(final LocalItem localItem, HistoryRecordManager histo } @Override - public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) { - if (!(localItem instanceof PlaylistStreamEntry)) return; + public void updateState(final LocalItem localItem, + final HistoryRecordManager historyRecordManager) { + if (!(localItem instanceof PlaylistStreamEntry)) { + return; + } final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; - StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList() {{ add(localItem); }}).blockingGet().get(0); + StreamStateEntity state = historyRecordManager + .loadLocalStreamStateBatch(new ArrayList() {{ + add(localItem); + }}).blockingGet().get(0); if (state != null && item.getStreamEntity().getDuration() > 0) { itemProgressView.setMax((int) item.getStreamEntity().getDuration()); if (itemProgressView.getVisibility() == View.VISIBLE) { - itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); } else { - itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgress((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); AnimationUtils.animateView(itemProgressView, true, 500); } } else if (itemProgressView.getVisibility() == View.VISIBLE) { @@ -118,8 +138,8 @@ public void updateState(LocalItem localItem, HistoryRecordManager historyRecordM private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) { return (view, motionEvent) -> { view.performClick(); - if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null && - motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null + && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { itemBuilder.getOnItemSelectedListener().drag(item, LocalPlaylistStreamItemHolder.this); } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java index 792ad92f0..39a43b034 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamGridItemHolder.java @@ -6,8 +6,8 @@ import org.schabi.newpipe.local.LocalItemBuilder; public class LocalStatisticStreamGridItemHolder extends LocalStatisticStreamItemHolder { - - public LocalStatisticStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_stream_grid_item, parent); - } + public LocalStatisticStreamGridItemHolder(final LocalItemBuilder infoItemBuilder, + final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_stream_grid_item, parent); + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java index 77f947031..a83c6ba67 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java @@ -1,12 +1,13 @@ package org.schabi.newpipe.local.holder; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; @@ -44,20 +45,21 @@ */ public class LocalStatisticStreamItemHolder extends LocalItemHolder { - public final ImageView itemThumbnailView; public final TextView itemVideoTitleView; public final TextView itemUploaderView; public final TextView itemDurationView; @Nullable public final TextView itemAdditionalDetails; - public final AnimatedProgressBar itemProgressView; + private final AnimatedProgressBar itemProgressView; - public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) { + public LocalStatisticStreamItemHolder(final LocalItemBuilder itemBuilder, + final ViewGroup parent) { this(itemBuilder, R.layout.list_stream_item, parent); } - LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + LocalStatisticStreamItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -70,32 +72,41 @@ public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup pa private String getStreamInfoDetailLine(final StreamStatisticsEntry entry, final DateFormat dateFormat) { - final String watchCount = Localization.shortViewCount(itemBuilder.getContext(), - entry.getWatchCount()); + final String watchCount = Localization + .shortViewCount(itemBuilder.getContext(), entry.getWatchCount()); final String uploadDate = dateFormat.format(entry.getLatestAccessDate()); final String serviceName = NewPipe.getNameOfService(entry.getStreamEntity().getServiceId()); return Localization.concatenateStrings(watchCount, uploadDate, serviceName); } @Override - public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { - if (!(localItem instanceof StreamStatisticsEntry)) return; + public void updateFromItem(final LocalItem localItem, + final HistoryRecordManager historyRecordManager, + final DateFormat dateFormat) { + if (!(localItem instanceof StreamStatisticsEntry)) { + return; + } final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; itemVideoTitleView.setText(item.getStreamEntity().getTitle()); itemUploaderView.setText(item.getStreamEntity().getUploader()); if (item.getStreamEntity().getDuration() > 0) { - itemDurationView.setText(Localization.getDurationString(item.getStreamEntity().getDuration())); + itemDurationView. + setText(Localization.getDurationString(item.getStreamEntity().getDuration())); itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); - StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList() {{ add(localItem); }}).blockingGet().get(0); + StreamStateEntity state = historyRecordManager + .loadLocalStreamStateBatch(new ArrayList() {{ + add(localItem); + }}).blockingGet().get(0); if (state != null) { itemProgressView.setVisibility(View.VISIBLE); itemProgressView.setMax((int) item.getStreamEntity().getDuration()); - itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgress((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); } else { itemProgressView.setVisibility(View.GONE); } @@ -128,17 +139,25 @@ public void updateFromItem(final LocalItem localItem, HistoryRecordManager histo } @Override - public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) { - if (!(localItem instanceof StreamStatisticsEntry)) return; + public void updateState(final LocalItem localItem, + final HistoryRecordManager historyRecordManager) { + if (!(localItem instanceof StreamStatisticsEntry)) { + return; + } final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; - StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList() {{ add(localItem); }}).blockingGet().get(0); + StreamStateEntity state = historyRecordManager + .loadLocalStreamStateBatch(new ArrayList() {{ + add(localItem); + }}).blockingGet().get(0); if (state != null && item.getStreamEntity().getDuration() > 0) { itemProgressView.setMax((int) item.getStreamEntity().getDuration()); if (itemProgressView.getVisibility() == View.VISIBLE) { - itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); } else { - itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); + itemProgressView.setProgress((int) TimeUnit.MILLISECONDS + .toSeconds(state.getProgressTime())); AnimationUtils.animateView(itemProgressView, true, 500); } } else if (itemProgressView.getVisibility() == View.VISIBLE) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java index c5f1813c7..11e3deb67 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/PlaylistItemHolder.java @@ -13,12 +13,12 @@ public abstract class PlaylistItemHolder extends LocalItemHolder { public final ImageView itemThumbnailView; - public final TextView itemStreamCountView; + final TextView itemStreamCountView; public final TextView itemTitleView; public final TextView itemUploaderView; - public PlaylistItemHolder(LocalItemBuilder infoItemBuilder, - int layoutId, ViewGroup parent) { + public PlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); @@ -27,12 +27,14 @@ public PlaylistItemHolder(LocalItemBuilder infoItemBuilder, itemUploaderView = itemView.findViewById(R.id.itemUploaderView); } - public PlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { + public PlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) { this(infoItemBuilder, R.layout.list_playlist_mini_item, parent); } @Override - public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { + public void updateFromItem(final LocalItem localItem, + final HistoryRecordManager historyRecordManager, + final DateFormat dateFormat) { itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { itemBuilder.getOnItemSelectedListener().selected(localItem); diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java index 5ac18fccb..00dcefbda 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistGridItemHolder.java @@ -6,8 +6,8 @@ import org.schabi.newpipe.local.LocalItemBuilder; public class RemotePlaylistGridItemHolder extends RemotePlaylistItemHolder { - - public RemotePlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { - super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); - } + public RemotePlaylistGridItemHolder(final LocalItemBuilder infoItemBuilder, + final ViewGroup parent) { + super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index 8bb16c318..c6d387fd4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.local.holder; +import android.text.TextUtils; import android.view.ViewGroup; import org.schabi.newpipe.database.LocalItem; @@ -10,22 +11,26 @@ import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; -import android.text.TextUtils; - import java.text.DateFormat; public class RemotePlaylistItemHolder extends PlaylistItemHolder { - public RemotePlaylistItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { + public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, + final ViewGroup parent) { super(infoItemBuilder, parent); } - RemotePlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId, + final ViewGroup parent) { super(infoItemBuilder, layoutId, parent); } @Override - public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { - if (!(localItem instanceof PlaylistRemoteEntity)) return; + public void updateFromItem(final LocalItem localItem, + final HistoryRecordManager historyRecordManager, + final DateFormat dateFormat) { + if (!(localItem instanceof PlaylistRemoteEntity)) { + return; + } final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; itemTitleView.setText(item.getName()); @@ -33,7 +38,7 @@ public void updateFromItem(final LocalItem localItem, HistoryRecordManager histo // Here is where the uploader name is set in the bookmarked playlists library if (!TextUtils.isEmpty(item.getUploader())) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), - NewPipe.getNameOfService(item.getServiceId()))); + NewPipe.getNameOfService(item.getServiceId()))); } else { itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index dd9958486..c0b7b0ec2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -53,27 +53,22 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; public class LocalPlaylistFragment extends BaseLocalListFragment, Void> { - // Save the list 10 seconds after the last change occurred private static final long SAVE_DEBOUNCE_MILLIS = 10000; private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12; - + @State + protected Long playlistId; + @State + protected String name; + @State + protected Parcelable itemsListState; private View headerRootLayout; private TextView headerTitleView; private TextView headerStreamCount; - private View playlistControl; private View headerPlayAllButton; private View headerPopupButton; private View headerBackgroundButton; - - @State - protected Long playlistId; - @State - protected String name; - @State - protected Parcelable itemsListState; - private ItemTouchHelper itemTouchHelper; private LocalPlaylistManager playlistManager; @@ -87,7 +82,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() { @Override - public void selected(LocalItem selectedItem) { + public void selected(final LocalItem selectedItem) { if (selectedItem instanceof PlaylistStreamEntry) { final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem; NavigationHelper.openVideoDetailFragment(getFragmentManager(), - item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(), item.getStreamEntity().getTitle()); + item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(), + item.getStreamEntity().getTitle()); } } @Override - public void held(LocalItem selectedItem) { + public void held(final LocalItem selectedItem) { if (selectedItem instanceof PlaylistStreamEntry) { showStreamItemDialog((PlaylistStreamEntry) selectedItem); } } @Override - public void drag(LocalItem selectedItem, RecyclerView.ViewHolder viewHolder) { - if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder); + public void drag(final LocalItem selectedItem, + final RecyclerView.ViewHolder viewHolder) { + if (itemTouchHelper != null) { + itemTouchHelper.startDrag(viewHolder); + } } }); } @@ -193,22 +192,32 @@ public void drag(LocalItem selectedItem, RecyclerView.ViewHolder viewHolder) { @Override public void showLoading() { super.showLoading(); - if (headerRootLayout != null) animateView(headerRootLayout, false, 200); - if (playlistControl != null) animateView(playlistControl, false, 200); + if (headerRootLayout != null) { + animateView(headerRootLayout, false, 200); + } + if (playlistControl != null) { + animateView(playlistControl, false, 200); + } } @Override public void hideLoading() { super.hideLoading(); - if (headerRootLayout != null) animateView(headerRootLayout, true, 200); - if (playlistControl != null) animateView(playlistControl, true, 200); + if (headerRootLayout != null) { + animateView(headerRootLayout, true, 200); + } + if (playlistControl != null) { + animateView(playlistControl, true, 200); + } } @Override - public void startLoading(boolean forceLoad) { + public void startLoading(final boolean forceLoad) { super.startLoading(forceLoad); - if (disposables != null) disposables.clear(); + if (disposables != null) { + disposables.clear(); + } disposables.add(getDebouncedSaver()); isLoadingComplete.set(false); @@ -237,13 +246,25 @@ public void onPause() { public void onDestroyView() { super.onDestroyView(); - if (itemListAdapter != null) itemListAdapter.unsetSelectedListener(); - if (headerBackgroundButton != null) headerBackgroundButton.setOnClickListener(null); - if (headerPlayAllButton != null) headerPlayAllButton.setOnClickListener(null); - if (headerPopupButton != null) headerPopupButton.setOnClickListener(null); + if (itemListAdapter != null) { + itemListAdapter.unsetSelectedListener(); + } + if (headerBackgroundButton != null) { + headerBackgroundButton.setOnClickListener(null); + } + if (headerPlayAllButton != null) { + headerPlayAllButton.setOnClickListener(null); + } + if (headerPopupButton != null) { + headerPopupButton.setOnClickListener(null); + } - if (databaseSubscription != null) databaseSubscription.cancel(); - if (disposables != null) disposables.clear(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } + if (disposables != null) { + disposables.clear(); + } databaseSubscription = null; itemTouchHelper = null; @@ -252,8 +273,12 @@ public void onDestroyView() { @Override public void onDestroy() { super.onDestroy(); - if (debouncedSaveSignal != null) debouncedSaveSignal.onComplete(); - if (disposables != null) disposables.dispose(); + if (debouncedSaveSignal != null) { + debouncedSaveSignal.onComplete(); + } + if (disposables != null) { + disposables.dispose(); + } debouncedSaveSignal = null; playlistManager = null; @@ -270,40 +295,46 @@ public void onDestroy() { private Subscriber> getPlaylistObserver() { return new Subscriber>() { @Override - public void onSubscribe(Subscription s) { + public void onSubscribe(final Subscription s) { showLoading(); isLoadingComplete.set(false); - if (databaseSubscription != null) databaseSubscription.cancel(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } databaseSubscription = s; databaseSubscription.request(1); } @Override - public void onNext(List streams) { + public void onNext(final List streams) { // Skip handling the result after it has been modified if (isModified == null || !isModified.get()) { handleResult(streams); isLoadingComplete.set(true); } - if (databaseSubscription != null) databaseSubscription.request(1); + if (databaseSubscription != null) { + databaseSubscription.request(1); + } } @Override - public void onError(Throwable exception) { + public void onError(final Throwable exception) { LocalPlaylistFragment.this.onError(exception); } @Override - public void onComplete() {} + public void onComplete() { } }; } @Override - public void handleResult(@NonNull List result) { + public void handleResult(@NonNull final List result) { super.handleResult(result); - if (itemListAdapter == null) return; + if (itemListAdapter == null) { + return; + } itemListAdapter.clearStreamItemList(); @@ -346,12 +377,16 @@ public void handleResult(@NonNull List result) { @Override protected void resetFragment() { super.resetFragment(); - if (databaseSubscription != null) databaseSubscription.cancel(); + if (databaseSubscription != null) { + databaseSubscription.cancel(); + } } @Override - protected boolean onError(Throwable exception) { - if (super.onError(exception)) return true; + protected boolean onError(final Throwable exception) { + if (super.onError(exception)) { + return true; + } onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Local Playlist", R.string.general_error); @@ -363,7 +398,9 @@ protected boolean onError(Throwable exception) { //////////////////////////////////////////////////////////////////////////*/ private void createRenameDialog() { - if (playlistId == null || name == null || getContext() == null) return; + if (playlistId == null || name == null || getContext() == null) { + return; + } final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null); EditText nameEdit = dialogView.findViewById(R.id.playlist_name); @@ -382,33 +419,37 @@ private void createRenameDialog() { dialogBuilder.show(); } - private void changePlaylistName(final String name) { - if (playlistManager == null) return; + private void changePlaylistName(final String title) { + if (playlistManager == null) { + return; + } - this.name = name; - setTitle(name); + this.name = title; + setTitle(title); if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + playlistId + - "] with new name=[" + name + "] items"); + Log.d(TAG, "Updating playlist id=[" + playlistId + "] " + + "with new title=[" + title + "] items"); } - final Disposable disposable = playlistManager.renamePlaylist(playlistId, name) + final Disposable disposable = playlistManager.renamePlaylist(playlistId, title) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(longs -> {/*Do nothing on success*/}, this::onError); + .subscribe(longs -> { /*Do nothing on success*/ }, this::onError); disposables.add(disposable); } private void changeThumbnailUrl(final String thumbnailUrl) { - if (playlistManager == null) return; + if (playlistManager == null) { + return; + } final Toast successToast = Toast.makeText(getActivity(), R.string.playlist_thumbnail_change_success, Toast.LENGTH_SHORT); if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + playlistId + - "] with new thumbnail url=[" + thumbnailUrl + "]"); + Log.d(TAG, "Updating playlist id=[" + playlistId + "] " + + "with new thumbnail url=[" + thumbnailUrl + "]"); } final Disposable disposable = playlistManager @@ -422,7 +463,8 @@ private void updateThumbnailUrl() { String newThumbnailUrl; if (!itemListAdapter.getItemsList().isEmpty()) { - newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0)).getStreamEntity().getThumbnailUrl(); + newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0)) + .getStreamEntity().getThumbnailUrl(); } else { newThumbnailUrl = "drawable://" + R.drawable.dummy_thumbnail_playlist; } @@ -431,25 +473,33 @@ private void updateThumbnailUrl() { } private void deleteItem(final PlaylistStreamEntry item) { - if (itemListAdapter == null) return; + if (itemListAdapter == null) { + return; + } itemListAdapter.removeItem(item); - if (playlistManager.getPlaylistThumbnail(playlistId).equals(item.getStreamEntity().getThumbnailUrl())) + if (playlistManager.getPlaylistThumbnail(playlistId) + .equals(item.getStreamEntity().getThumbnailUrl())) { updateThumbnailUrl(); + } setVideoCount(itemListAdapter.getItemsList().size()); saveChanges(); } private void saveChanges() { - if (isModified == null || debouncedSaveSignal == null) return; + if (isModified == null || debouncedSaveSignal == null) { + return; + } isModified.set(true); debouncedSaveSignal.onNext(System.currentTimeMillis()); } private Disposable getDebouncedSaver() { - if (debouncedSaveSignal == null) return Disposables.empty(); + if (debouncedSaveSignal == null) { + return Disposables.empty(); + } return debouncedSaveSignal .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) @@ -458,13 +508,15 @@ private Disposable getDebouncedSaver() { } private void saveImmediate() { - if (playlistManager == null || itemListAdapter == null) return; + if (playlistManager == null || itemListAdapter == null) { + return; + } // List must be loaded and modified in order to save - if (isLoadingComplete == null || isModified == null || - !isLoadingComplete.get() || !isModified.get()) { - Log.w(TAG, "Attempting to save playlist when local playlist " + - "is not loaded or not modified: playlist id=[" + playlistId + "]"); + if (isLoadingComplete == null || isModified == null + || !isLoadingComplete.get() || !isModified.get()) { + Log.w(TAG, "Attempting to save playlist when local playlist " + + "is not loaded or not modified: playlist id=[" + playlistId + "]"); return; } @@ -477,14 +529,18 @@ private void saveImmediate() { } if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + playlistId + - "] with [" + streamIds.size() + "] items"); + Log.d(TAG, "Updating playlist id=[" + playlistId + "] " + + "with [" + streamIds.size() + "] items"); } final Disposable disposable = playlistManager.updateJoin(playlistId, streamIds) .observeOn(AndroidSchedulers.mainThread()) .subscribe( - () -> { if (isModified != null) isModified.set(false); }, + () -> { + if (isModified != null) { + isModified.set(false); + } + }, this::onError ); disposables.add(disposable); @@ -499,28 +555,33 @@ private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new ItemTouchHelper.SimpleCallback(directions, ItemTouchHelper.ACTION_STATE_IDLE) { @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, - int viewSizeOutOfBounds, int totalSize, - long msSinceStartScroll) { - final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, - viewSizeOutOfBounds, totalSize, msSinceStartScroll); + public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, + final int viewSize, + final int viewSizeOutOfBounds, + final int totalSize, + final long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, + viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); final int minimumAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, Math.abs(standardSpeed)); return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, - RecyclerView.ViewHolder target) { - if (source.getItemViewType() != target.getItemViewType() || - itemListAdapter == null) { + public boolean onMove(final RecyclerView recyclerView, + final RecyclerView.ViewHolder source, + final RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() + || itemListAdapter == null) { return false; } final int sourceIndex = source.getAdapterPosition(); final int targetIndex = target.getAdapterPosition(); final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex); - if (isSwapped) saveChanges(); + if (isSwapped) { + saveChanges(); + } return isSwapped; } @@ -535,7 +596,7 @@ public boolean isItemViewSwipeEnabled() { } @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {} + public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { } }; } @@ -543,14 +604,16 @@ public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {} // Utils //////////////////////////////////////////////////////////////////////////*/ - private PlayQueue getPlayQueueStartingAt(PlaylistStreamEntry infoItem) { + private PlayQueue getPlayQueueStartingAt(final PlaylistStreamEntry infoItem) { return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); } protected void showStreamItemDialog(final PlaylistStreamEntry item) { final Context context = getContext(); final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) return; + if (context == null || context.getResources() == null || activity == null) { + return; + } final StreamInfoItem infoItem = item.toStreamInfoItem(); if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { @@ -573,23 +636,26 @@ protected void showStreamItemDialog(final PlaylistStreamEntry item) { StreamDialogEntry.share); StreamDialogEntry.start_here_on_popup.setCustomAction( - (fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); + (fragment, infoItemDuplicate) -> NavigationHelper. + playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); } - StreamDialogEntry.start_here_on_background.setCustomAction( - (fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); + StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) -> + NavigationHelper.playOnBackgroundPlayer(context, + getPlayQueueStartingAt(item), true)); StreamDialogEntry.set_as_playlist_thumbnail.setCustomAction( - (fragment, infoItemDuplicate) -> changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl())); - StreamDialogEntry.delete.setCustomAction( - (fragment, infoItemDuplicate) -> deleteItem(item)); + (fragment, infoItemDuplicate) -> + changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl())); + StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) -> + deleteItem(item)); - new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) -> - StreamDialogEntry.clickOn(which, this, infoItem)).show(); + new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), + (dialog, which) -> StreamDialogEntry.clickOn(which, this, infoItem)).show(); } - private void setInitialData(long playlistId, String name) { - this.playlistId = playlistId; - this.name = !TextUtils.isEmpty(name) ? name : ""; + private void setInitialData(final long pid, final String title) { + this.playlistId = pid; + this.name = !TextUtils.isEmpty(title) ? title : ""; } private void setVideoCount(final long count) { diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java index a856fbae5..21164497a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java @@ -22,7 +22,6 @@ import io.reactivex.schedulers.Schedulers; public class LocalPlaylistManager { - private final AppDatabase database; private final StreamDAO streamTable; private final PlaylistDAO playlistTable; @@ -37,7 +36,9 @@ public LocalPlaylistManager(final AppDatabase db) { public Maybe> createPlaylist(final String name, final List streams) { // Disallow creation of empty playlists - if (streams.isEmpty()) return Maybe.empty(); + if (streams.isEmpty()) { + return Maybe.empty(); + } final StreamEntity defaultStream = streams.get(0); final PlaylistEntity newPlaylist = new PlaylistEntity(name, defaultStream.getThumbnailUrl()); @@ -115,8 +116,12 @@ private Maybe modifyPlaylist(final long playlistId, .filter(playlistEntities -> !playlistEntities.isEmpty()) .map(playlistEntities -> { PlaylistEntity playlist = playlistEntities.get(0); - if (name != null) playlist.setName(name); - if (thumbnailUrl != null) playlist.setThumbnailUrl(thumbnailUrl); + if (name != null) { + playlist.setName(name); + } + if (thumbnailUrl != null) { + playlist.setThumbnailUrl(thumbnailUrl); + } return playlistTable.update(playlist); }).subscribeOn(Schedulers.io()); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java b/app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java index a44efa1d3..17ae7b1c0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java @@ -4,6 +4,7 @@ import android.app.Dialog; import android.content.Intent; import android.os.Bundle; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; @@ -21,21 +22,24 @@ public class ImportConfirmationDialog extends DialogFragment { @State protected Intent resultServiceIntent; - public void setResultServiceIntent(Intent resultServiceIntent) { - this.resultServiceIntent = resultServiceIntent; - } - - public static void show(@NonNull Fragment fragment, @NonNull Intent resultServiceIntent) { - if (fragment.getFragmentManager() == null) return; + public static void show(@NonNull final Fragment fragment, + @NonNull final Intent resultServiceIntent) { + if (fragment.getFragmentManager() == null) { + return; + } final ImportConfirmationDialog confirmationDialog = new ImportConfirmationDialog(); confirmationDialog.setResultServiceIntent(resultServiceIntent); confirmationDialog.show(fragment.getFragmentManager(), null); } + public void setResultServiceIntent(final Intent resultServiceIntent) { + this.resultServiceIntent = resultServiceIntent; + } + @NonNull @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); return new AlertDialog.Builder(getContext(), ThemeHelper.getDialogTheme(getContext())) .setMessage(R.string.import_network_expensive_warning) @@ -51,16 +55,18 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { } @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (resultServiceIntent == null) throw new IllegalStateException("Result intent is null"); + if (resultServiceIntent == null) { + throw new IllegalStateException("Result intent is null"); + } Icepick.restoreInstanceState(this, savedInstanceState); } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 98e20a02f..295c9d0a7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -59,9 +59,15 @@ class SubscriptionFragment : BaseStateFragment() { private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem private val subscriptionsSection = Section() - @State @JvmField var itemsListState: Parcelable? = null - @State @JvmField var feedGroupsListState: Parcelable? = null - @State @JvmField var importExportItemExpandedState: Boolean? = null + @State + @JvmField + var itemsListState: Parcelable? = null + @State + @JvmField + var feedGroupsListState: Parcelable? = null + @State + @JvmField + var importExportItemExpandedState: Boolean? = null init { setHasOptionsMenu(true) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java index 0a45e680a..d6cc231aa 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java @@ -3,11 +3,6 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.text.util.LinkifyCompat; -import androidx.appcompat.app.ActionBar; import android.text.TextUtils; import android.text.util.Linkify; import android.view.LayoutInflater; @@ -17,6 +12,12 @@ import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.core.text.util.LinkifyCompat; + import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.BaseFragment; @@ -24,9 +25,9 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; +import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ServiceHelper; @@ -52,45 +53,44 @@ public class SubscriptionsImportFragment extends BaseFragment { private String relatedUrl; @StringRes private int instructionsString; + private TextView infoTextView; + private EditText inputText; + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + private Button inputButton; - public static SubscriptionsImportFragment getInstance(int serviceId) { + public static SubscriptionsImportFragment getInstance(final int serviceId) { SubscriptionsImportFragment instance = new SubscriptionsImportFragment(); instance.setInitialData(serviceId); return instance; } - public void setInitialData(int serviceId) { + public void setInitialData(final int serviceId) { this.currentServiceId = serviceId; } - /*////////////////////////////////////////////////////////////////////////// - // Views - //////////////////////////////////////////////////////////////////////////*/ - - private TextView infoTextView; - - private EditText inputText; - private Button inputButton; - /////////////////////////////////////////////////////////////////////////// // Fragment LifeCycle /////////////////////////////////////////////////////////////////////////// - @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupServiceVariables(); if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { - ErrorActivity.reportError(activity, Collections.emptyList(), null, null, ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, - NewPipe.getNameOfService(currentServiceId), "Service don't support importing", R.string.general_error)); + ErrorActivity.reportError(activity, Collections.emptyList(), null, null, + ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, + NewPipe.getNameOfService(currentServiceId), + "Service don't support importing", R.string.general_error)); activity.finish(); } } @Override - public void setUserVisibleHint(boolean isVisibleToUser) { + public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { setTitle(getString(R.string.import_title)); @@ -99,7 +99,9 @@ public void setUserVisibleHint(boolean isVisibleToUser) { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + final Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_import, container, false); } @@ -108,7 +110,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c /////////////////////////////////////////////////////////////////////////*/ @Override - protected void initViews(View rootView, Bundle savedInstanceState) { + protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); inputButton = rootView.findViewById(R.id.input_button); @@ -116,7 +118,8 @@ protected void initViews(View rootView, Bundle savedInstanceState) { infoTextView = rootView.findViewById(R.id.info_text_view); - // TODO: Support services that can import from more than one source (show the option to the user) + // TODO: Support services that can import from more than one source + // (show the option to the user) if (supportedSources.contains(CHANNEL_URL)) { inputButton.setText(R.string.import_title); inputText.setVisibility(View.VISIBLE); @@ -151,13 +154,15 @@ protected void initListeners() { private void onImportClicked() { if (inputText.getVisibility() == View.VISIBLE) { final String value = inputText.getText().toString(); - if (!value.isEmpty()) onImportUrl(value); + if (!value.isEmpty()) { + onImportUrl(value); + } } else { onImportFile(); } } - public void onImportUrl(String value) { + public void onImportUrl(final String value) { ImportConfirmationDialog.show(this, new Intent(activity, SubscriptionsImportService.class) .putExtra(KEY_MODE, CHANNEL_URL_MODE) .putExtra(KEY_VALUE, value) @@ -165,20 +170,24 @@ public void onImportUrl(String value) { } public void onImportFile() { - startActivityForResult(FilePickerActivityHelper.chooseSingleFile(activity), REQUEST_IMPORT_FILE_CODE); + startActivityForResult(FilePickerActivityHelper.chooseSingleFile(activity), + REQUEST_IMPORT_FILE_CODE); } @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (data == null) return; + if (data == null) { + return; + } - if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMPORT_FILE_CODE && data.getData() != null) { + if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMPORT_FILE_CODE + && data.getData() != null) { final String path = Utils.getFileForUri(data.getData()).getAbsolutePath(); - ImportConfirmationDialog.show(this, new Intent(activity, SubscriptionsImportService.class) - .putExtra(KEY_MODE, INPUT_STREAM_MODE) - .putExtra(KEY_VALUE, path) - .putExtra(Constants.KEY_SERVICE_ID, currentServiceId)); + ImportConfirmationDialog.show(this, + new Intent(activity, SubscriptionsImportService.class) + .putExtra(KEY_MODE, INPUT_STREAM_MODE).putExtra(KEY_VALUE, path) + .putExtra(Constants.KEY_SERVICE_ID, currentServiceId)); } } @@ -189,7 +198,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { private void setupServiceVariables() { if (currentServiceId != Constants.NO_SERVICE_ID) { try { - final SubscriptionExtractor extractor = NewPipe.getService(currentServiceId).getSubscriptionExtractor(); + final SubscriptionExtractor extractor = NewPipe.getService(currentServiceId) + .getSubscriptionExtractor(); supportedSources = extractor.getSupportedSources(); relatedUrl = extractor.getRelatedUrl(); instructionsString = ServiceHelper.getImportInstructions(currentServiceId); @@ -203,7 +213,7 @@ private void setupServiceVariables() { instructionsString = 0; } - private void setInfoText(String infoString) { + private void setInfoText(final String infoString) { infoTextView.setText(infoString); LinkifyCompat.addLinks(infoTextView, Linkify.WEB_URLS); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt index b1fef5671..8fd0b0e31 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt @@ -49,12 +49,22 @@ class FeedGroupDialog : DialogFragment() { object DeleteScreen : ScreenState() } - @State @JvmField var selectedIcon: FeedGroupIcon? = null - @State @JvmField var selectedSubscriptions: HashSet = HashSet() - @State @JvmField var currentScreen: ScreenState = InitialScreen - - @State @JvmField var subscriptionsListState: Parcelable? = null - @State @JvmField var iconsListState: Parcelable? = null + @State + @JvmField + var selectedIcon: FeedGroupIcon? = null + @State + @JvmField + var selectedSubscriptions: HashSet = HashSet() + @State + @JvmField + var currentScreen: ScreenState = InitialScreen + + @State + @JvmField + var subscriptionsListState: Parcelable? = null + @State + @JvmField + var iconsListState: Parcelable? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt index 17ee89c87..090a59f13 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt @@ -19,7 +19,8 @@ import icepick.State import kotlinx.android.synthetic.main.dialog_feed_group_reorder.* import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity -import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.* +import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent +import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem import org.schabi.newpipe.util.ThemeHelper import java.util.* @@ -28,7 +29,9 @@ import kotlin.collections.ArrayList class FeedGroupReorderDialog : DialogFragment() { private lateinit var viewModel: FeedGroupReorderDialogViewModel - @State @JvmField var groupOrderedIdList = ArrayList() + @State + @JvmField + var groupOrderedIdList = ArrayList() private val groupAdapter = GroupAdapter() private val itemTouchHelper = ItemTouchHelper(getItemTouchCallback()) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index 928f93a47..d1988dc29 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -2,8 +2,8 @@ package org.schabi.newpipe.local.subscription.item import android.content.Context import com.nostra13.universalimageloader.core.ImageLoader -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import kotlinx.android.synthetic.main.list_channel_item.* import org.schabi.newpipe.R import org.schabi.newpipe.extractor.channel.ChannelInfoItem diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt index 0c651dc69..38151774b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt @@ -1,7 +1,7 @@ package org.schabi.newpipe.local.subscription.item -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import org.schabi.newpipe.R class EmptyPlaceholderItem : Item() { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt index 309f82bbc..2190bed76 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt @@ -1,7 +1,7 @@ package org.schabi.newpipe.local.subscription.item -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import org.schabi.newpipe.R class FeedGroupAddItem : Item() { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt index a757dc5b3..e6f0e0807 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt @@ -1,7 +1,7 @@ package org.schabi.newpipe.local.subscription.item -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import kotlinx.android.synthetic.main.feed_group_card_item.* import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt index bde3c604a..ae93d149d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt @@ -6,8 +6,8 @@ import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.xwray.groupie.GroupAdapter -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import kotlinx.android.synthetic.main.feed_item_carousel.* import org.schabi.newpipe.R import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt index 367605f46..bbbc57f62 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt @@ -1,8 +1,8 @@ package org.schabi.newpipe.local.subscription.item import android.view.View.OnClickListener -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import kotlinx.android.synthetic.main.header_item.* import org.schabi.newpipe.R diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt index fedec9880..546441669 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt @@ -2,14 +2,15 @@ package org.schabi.newpipe.local.subscription.item import android.content.Context import androidx.annotation.DrawableRes -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import kotlinx.android.synthetic.main.picker_icon_item.* import org.schabi.newpipe.R import org.schabi.newpipe.local.subscription.FeedGroupIcon class PickerIconItem(context: Context, val icon: FeedGroupIcon) : Item() { - @DrawableRes val iconRes: Int = icon.getDrawableRes(context) + @DrawableRes + val iconRes: Int = icon.getDrawableRes(context) override fun getLayout(): Int = R.layout.picker_icon_item diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt index 21c74b09f..e0754e078 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt @@ -3,8 +3,8 @@ package org.schabi.newpipe.local.subscription.item import android.view.View import com.nostra13.universalimageloader.core.DisplayImageOptions import com.nostra13.universalimageloader.core.ImageLoader -import com.xwray.groupie.kotlinandroidextensions.Item import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder +import com.xwray.groupie.kotlinandroidextensions.Item import kotlinx.android.synthetic.main.picker_subscription_item.* import org.schabi.newpipe.R import org.schabi.newpipe.database.subscription.SubscriptionEntity diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index e970ebfa4..cdabea2cb 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -23,13 +23,14 @@ import android.content.Intent; import android.os.Build; import android.os.IBinder; +import android.text.TextUtils; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import android.text.TextUtils; -import android.widget.Toast; import org.reactivestreams.Publisher; import org.schabi.newpipe.R; @@ -52,17 +53,36 @@ public abstract class BaseImportExportService extends Service { protected final String TAG = this.getClass().getSimpleName(); + private static final int NOTIFICATION_SAMPLING_PERIOD = 2500; + protected final CompositeDisposable disposables = new CompositeDisposable(); + protected final PublishProcessor notificationUpdater = PublishProcessor.create(); + protected final AtomicInteger currentProgress = new AtomicInteger(-1); + protected final AtomicInteger maxProgress = new AtomicInteger(-1); + protected final ImportExportEventListener eventListener = new ImportExportEventListener() { + @Override + public void onSizeReceived(final int size) { + maxProgress.set(size); + currentProgress.set(0); + } + @Override + public void onItemCompleted(final String itemName) { + currentProgress.incrementAndGet(); + notificationUpdater.onNext(itemName); + } + }; protected NotificationManagerCompat notificationManager; protected NotificationCompat.Builder notificationBuilder; - protected SubscriptionManager subscriptionManager; - protected final CompositeDisposable disposables = new CompositeDisposable(); - protected final PublishProcessor notificationUpdater = PublishProcessor.create(); + + /*////////////////////////////////////////////////////////////////////////// + // Notification Impl + //////////////////////////////////////////////////////////////////////////*/ + protected Toast toast; @Nullable @Override - public IBinder onBind(Intent intent) { + public IBinder onBind(final Intent intent) { return null; } @@ -83,29 +103,8 @@ protected void disposeAll() { disposables.clear(); } - /*////////////////////////////////////////////////////////////////////////// - // Notification Impl - //////////////////////////////////////////////////////////////////////////*/ - - private static final int NOTIFICATION_SAMPLING_PERIOD = 2500; - - protected final AtomicInteger currentProgress = new AtomicInteger(-1); - protected final AtomicInteger maxProgress = new AtomicInteger(-1); - protected final ImportExportEventListener eventListener = new ImportExportEventListener() { - @Override - public void onSizeReceived(int size) { - maxProgress.set(size); - currentProgress.set(0); - } - - @Override - public void onItemCompleted(String itemName) { - currentProgress.incrementAndGet(); - notificationUpdater.onNext(itemName); - } - }; - protected abstract int getNotificationId(); + @StringRes public abstract int getTitle(); @@ -114,8 +113,9 @@ protected void setupNotification() { notificationBuilder = createNotification(); startForeground(getNotificationId(), notificationBuilder.build()); - final Function, Publisher> throttleAfterFirstEmission = flow -> flow.limit(1) - .concatWith(flow.skip(1).throttleLast(NOTIFICATION_SAMPLING_PERIOD, TimeUnit.MILLISECONDS)); + final Function, Publisher> throttleAfterFirstEmission = flow -> + flow.limit(1).concatWith(flow.skip(1) + .throttleLast(NOTIFICATION_SAMPLING_PERIOD, TimeUnit.MILLISECONDS)); disposables.add(notificationUpdater .filter(s -> !s.isEmpty()) @@ -124,17 +124,20 @@ protected void setupNotification() { .subscribe(this::updateNotification)); } - protected void updateNotification(String text) { - notificationBuilder.setProgress(maxProgress.get(), currentProgress.get(), maxProgress.get() == -1); + protected void updateNotification(final String text) { + notificationBuilder + .setProgress(maxProgress.get(), currentProgress.get(), maxProgress.get() == -1); final String progressText = currentProgress + "/" + maxProgress; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!TextUtils.isEmpty(text)) text = text + " (" + progressText + ")"; + if (!TextUtils.isEmpty(text)) { + notificationBuilder.setContentText(text + " (" + progressText + ")"); + } } else { notificationBuilder.setContentInfo(progressText); + notificationBuilder.setContentText(text); } - if (!TextUtils.isEmpty(text)) notificationBuilder.setContentText(text); notificationManager.notify(getNotificationId(), notificationBuilder.build()); } @@ -142,16 +145,16 @@ protected void stopService() { postErrorResult(null, null); } - protected void stopAndReportError(@Nullable Throwable error, String request) { + protected void stopAndReportError(@Nullable final Throwable error, final String request) { stopService(); - final ErrorActivity.ErrorInfo errorInfo = ErrorActivity.ErrorInfo.make(UserAction.SUBSCRIPTION, "unknown", - request, R.string.general_error); - ErrorActivity.reportError(this, error != null ? Collections.singletonList(error) : Collections.emptyList(), - null, null, errorInfo); + final ErrorActivity.ErrorInfo errorInfo = ErrorActivity.ErrorInfo + .make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error); + ErrorActivity.reportError(this, error != null ? Collections.singletonList(error) + : Collections.emptyList(), null, null, errorInfo); } - protected void postErrorResult(String title, String text) { + protected void postErrorResult(final String title, final String text) { disposeAll(); stopForeground(true); stopSelf(); @@ -160,16 +163,21 @@ protected void postErrorResult(String title, String text) { return; } - text = text == null ? "" : text; - notificationBuilder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) + final String textOrEmpty = text == null ? "" : text; + notificationBuilder = new NotificationCompat + .Builder(this, getString(R.string.notification_channel_id)) .setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentTitle(title) - .setStyle(new NotificationCompat.BigTextStyle().bigText(text)) - .setContentText(text); + .setStyle(new NotificationCompat.BigTextStyle().bigText(textOrEmpty)) + .setContentText(textOrEmpty); notificationManager.notify(getNotificationId(), notificationBuilder.build()); } + /*////////////////////////////////////////////////////////////////////////// + // Toast + //////////////////////////////////////////////////////////////////////////*/ + protected NotificationCompat.Builder createNotification() { return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) .setOngoing(true) @@ -179,18 +187,14 @@ protected NotificationCompat.Builder createNotification() { .setContentTitle(getString(getTitle())); } - /*////////////////////////////////////////////////////////////////////////// - // Toast - //////////////////////////////////////////////////////////////////////////*/ - - protected Toast toast; - - protected void showToast(@StringRes int message) { + protected void showToast(@StringRes final int message) { showToast(getString(message)); } - protected void showToast(String message) { - if (toast != null) toast.cancel(); + protected void showToast(final String message) { + if (toast != null) { + toast.cancel(); + } toast = Toast.makeText(this, message, Toast.LENGTH_SHORT); toast.show(); @@ -200,7 +204,7 @@ protected void showToast(String message) { // Error handling //////////////////////////////////////////////////////////////////////////*/ - protected void handleError(@StringRes int errorTitle, @NonNull Throwable error) { + protected void handleError(@StringRes final int errorTitle, @NonNull final Throwable error) { String message = getErrorMessage(error); if (TextUtils.isEmpty(message)) { @@ -212,7 +216,7 @@ protected void handleError(@StringRes int errorTitle, @NonNull Throwable error) postErrorResult(getString(errorTitle), message); } - protected String getErrorMessage(Throwable error) { + protected String getErrorMessage(final Throwable error) { String message = null; if (error instanceof SubscriptionExtractor.InvalidSourceException) { message = getString(R.string.invalid_source); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java index 788073ee5..34bd68f5e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportEventListener.java @@ -14,4 +14,4 @@ public interface ImportExportEventListener { * @param itemName the name of the subscription item */ void onItemCompleted(String itemName); -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelper.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelper.java index 5b5ebf702..5d9a54ce7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelper.java @@ -41,8 +41,7 @@ * A JSON implementation capable of importing and exporting subscriptions, it has the advantage * of being able to transfer subscriptions to any device. */ -public class ImportExportJsonHelper { - +public final class ImportExportJsonHelper { /*////////////////////////////////////////////////////////////////////////// // Json implementation //////////////////////////////////////////////////////////////////////////*/ @@ -56,21 +55,30 @@ public class ImportExportJsonHelper { private static final String JSON_URL_KEY = "url"; private static final String JSON_NAME_KEY = "name"; + private ImportExportJsonHelper() { } + /** - * Read a JSON source through the input stream and return the parsed subscription items. + * Read a JSON source through the input stream. * * @param in the input stream (e.g. a file) * @param eventListener listener for the events generated + * @return the parsed subscription items */ - public static List readFrom(InputStream in, @Nullable ImportExportEventListener eventListener) throws InvalidSourceException { - if (in == null) throw new InvalidSourceException("input is null"); + public static List readFrom( + final InputStream in, @Nullable final ImportExportEventListener eventListener) + throws InvalidSourceException { + if (in == null) { + throw new InvalidSourceException("input is null"); + } final List channels = new ArrayList<>(); try { JsonObject parentObject = JsonParser.object().from(in); JsonArray channelsArray = parentObject.getArray(JSON_SUBSCRIPTIONS_ARRAY_KEY); - if (eventListener != null) eventListener.onSizeReceived(channelsArray.size()); + if (eventListener != null) { + eventListener.onSizeReceived(channelsArray.size()); + } if (channelsArray == null) { throw new InvalidSourceException("Channels array is null"); @@ -85,7 +93,9 @@ public static List readFrom(InputStream in, @Nullable ImportEx if (url != null && name != null && !url.isEmpty() && !name.isEmpty()) { channels.add(new SubscriptionItem(serviceId, url, name)); - if (eventListener != null) eventListener.onItemCompleted(name); + if (eventListener != null) { + eventListener.onItemCompleted(name); + } } } } @@ -103,7 +113,8 @@ public static List readFrom(InputStream in, @Nullable ImportEx * @param out the output stream (e.g. a file) * @param eventListener listener for the events generated */ - public static void writeTo(List items, OutputStream out, @Nullable ImportExportEventListener eventListener) { + public static void writeTo(final List items, final OutputStream out, + @Nullable final ImportExportEventListener eventListener) { JsonAppendableWriter writer = JsonWriter.on(out); writeTo(items, writer, eventListener); writer.done(); @@ -111,9 +122,15 @@ public static void writeTo(List items, OutputStream out, @Null /** * @see #writeTo(List, OutputStream, ImportExportEventListener) + * @param items the list of subscriptions items + * @param writer the output {@link JsonSink} + * @param eventListener listener for the events generated */ - public static void writeTo(List items, JsonSink writer, @Nullable ImportExportEventListener eventListener) { - if (eventListener != null) eventListener.onSizeReceived(items.size()); + public static void writeTo(final List items, final JsonSink writer, + @Nullable final ImportExportEventListener eventListener) { + if (eventListener != null) { + eventListener.onSizeReceived(items.size()); + } writer.object(); @@ -128,11 +145,12 @@ public static void writeTo(List items, JsonSink writer, @Nulla writer.value(JSON_NAME_KEY, item.getName()); writer.end(); - if (eventListener != null) eventListener.onItemCompleted(item.getName()); + if (eventListener != null) { + eventListener.onItemCompleted(item.getName()); + } } writer.end(); writer.end(); } - } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java index 358024574..12b64d89d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java @@ -20,10 +20,11 @@ package org.schabi.newpipe.local.subscription.services; import android.content.Intent; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.R; @@ -46,26 +47,33 @@ public class SubscriptionsExportService extends BaseImportExportService { public static final String KEY_FILE_PATH = "key_file_path"; /** - * A {@link LocalBroadcastManager local broadcast} will be made with this action when the export is successfully completed. + * A {@link LocalBroadcastManager local broadcast} will be made with this action + * when the export is successfully completed. */ - public static final String EXPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE"; + public static final String EXPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription" + + ".services.SubscriptionsExportService.EXPORT_COMPLETE"; private Subscription subscription; private File outFile; private FileOutputStream outputStream; @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent == null || subscription != null) return START_NOT_STICKY; + public int onStartCommand(final Intent intent, final int flags, final int startId) { + if (intent == null || subscription != null) { + return START_NOT_STICKY; + } final String path = intent.getStringExtra(KEY_FILE_PATH); if (TextUtils.isEmpty(path)) { - stopAndReportError(new IllegalStateException("Exporting to a file, but the path is empty or null"), "Exporting subscriptions"); + stopAndReportError(new IllegalStateException( + "Exporting to a file, but the path is empty or null"), + "Exporting subscriptions"); return START_NOT_STICKY; } try { - outputStream = new FileOutputStream(outFile = new File(path)); + outFile = new File(path); + outputStream = new FileOutputStream(outFile); } catch (FileNotFoundException e) { handleError(e); return START_NOT_STICKY; @@ -89,19 +97,21 @@ public int getTitle() { @Override protected void disposeAll() { super.disposeAll(); - if (subscription != null) subscription.cancel(); + if (subscription != null) { + subscription.cancel(); + } } private void startExport() { showToast(R.string.export_ongoing); - subscriptionManager.subscriptionTable() - .getAll() - .take(1) + subscriptionManager.subscriptionTable().getAll().take(1) .map(subscriptionEntities -> { - final List result = new ArrayList<>(subscriptionEntities.size()); + final List result + = new ArrayList<>(subscriptionEntities.size()); for (SubscriptionEntity entity : subscriptionEntities) { - result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(), entity.getName())); + result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(), + entity.getName())); } return result; }) @@ -114,25 +124,28 @@ private void startExport() { private Subscriber getSubscriber() { return new Subscriber() { @Override - public void onSubscribe(Subscription s) { + public void onSubscribe(final Subscription s) { subscription = s; s.request(1); } @Override - public void onNext(File file) { - if (DEBUG) Log.d(TAG, "startExport() success: file = " + file); + public void onNext(final File file) { + if (DEBUG) { + Log.d(TAG, "startExport() success: file = " + file); + } } @Override - public void onError(Throwable error) { + public void onError(final Throwable error) { Log.e(TAG, "onError() called with: error = [" + error + "]", error); handleError(error); } @Override public void onComplete() { - LocalBroadcastManager.getInstance(SubscriptionsExportService.this).sendBroadcast(new Intent(EXPORT_COMPLETE_ACTION)); + LocalBroadcastManager.getInstance(SubscriptionsExportService.this) + .sendBroadcast(new Intent(EXPORT_COMPLETE_ACTION)); showToast(R.string.export_complete_toast); stopService(); } @@ -146,7 +159,7 @@ private Function, File> exportToFile() { }; } - protected void handleError(Throwable error) { + protected void handleError(final Throwable error) { super.handleError(R.string.subscriptions_export_unsuccessful, error); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java index 0d2f3757f..379df7151 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java @@ -20,11 +20,12 @@ package org.schabi.newpipe.local.subscription.services; import android.content.Intent; +import android.text.TextUtils; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.text.TextUtils; -import android.util.Log; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -61,22 +62,33 @@ public class SubscriptionsImportService extends BaseImportExportService { public static final String KEY_VALUE = "key_value"; /** - * A {@link LocalBroadcastManager local broadcast} will be made with this action when the import is successfully completed. + * A {@link LocalBroadcastManager local broadcast} will be made with this action + * when the import is successfully completed. */ - public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE"; - + public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription" + + ".services.SubscriptionsImportService.IMPORT_COMPLETE"; + /** + * How many extractions running in parallel. + */ + public static final int PARALLEL_EXTRACTIONS = 8; + /** + * Number of items to buffer to mass-insert in the subscriptions table, + * this leads to a better performance as we can then use db transactions. + */ + public static final int BUFFER_COUNT_BEFORE_INSERT = 50; private Subscription subscription; private int currentMode; private int currentServiceId; - @Nullable private String channelUrl; @Nullable private InputStream inputStream; @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent == null || subscription != null) return START_NOT_STICKY; + public int onStartCommand(final Intent intent, final int flags, final int startId) { + if (intent == null || subscription != null) { + return START_NOT_STICKY; + } currentMode = intent.getIntExtra(KEY_MODE, -1); currentServiceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, Constants.NO_SERVICE_ID); @@ -86,7 +98,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { } else { final String filePath = intent.getStringExtra(KEY_VALUE); if (TextUtils.isEmpty(filePath)) { - stopAndReportError(new IllegalStateException("Importing from input stream, but file path is empty or null"), "Importing subscriptions"); + stopAndReportError(new IllegalStateException( + "Importing from input stream, but file path is empty or null"), + "Importing subscriptions"); return START_NOT_STICKY; } @@ -99,8 +113,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { } if (currentMode == -1 || currentMode == CHANNEL_URL_MODE && channelUrl == null) { - final String errorDescription = "Some important field is null or in illegal state: currentMode=[" + currentMode + "], channelUrl=[" + channelUrl + "], inputStream=[" + inputStream + "]"; - stopAndReportError(new IllegalStateException(errorDescription), "Importing subscriptions"); + final String errorDescription = "Some important field is null or in illegal state: " + + "currentMode=[" + currentMode + "], " + + "channelUrl=[" + channelUrl + "], " + + "inputStream=[" + inputStream + "]"; + stopAndReportError(new IllegalStateException(errorDescription), + "Importing subscriptions"); return START_NOT_STICKY; } @@ -113,6 +131,10 @@ protected int getNotificationId() { return 4568; } + /*////////////////////////////////////////////////////////////////////////// + // Imports + //////////////////////////////////////////////////////////////////////////*/ + @Override public int getTitle() { return R.string.import_ongoing; @@ -121,24 +143,11 @@ public int getTitle() { @Override protected void disposeAll() { super.disposeAll(); - if (subscription != null) subscription.cancel(); + if (subscription != null) { + subscription.cancel(); + } } - /*////////////////////////////////////////////////////////////////////////// - // Imports - //////////////////////////////////////////////////////////////////////////*/ - - /** - * How many extractions running in parallel. - */ - public static final int PARALLEL_EXTRACTIONS = 8; - - /** - * Number of items to buffer to mass-insert in the subscriptions table, this leads to - * a better performance as we can then use db transactions. - */ - public static final int BUFFER_COUNT_BEFORE_INSERT = 50; - private void startImport() { showToast(R.string.import_ongoing); @@ -156,12 +165,14 @@ private void startImport() { } if (flowable == null) { - final String message = "Flowable given by \"importFrom\" is null (current mode: " + currentMode + ")"; + final String message = "Flowable given by \"importFrom\" is null " + + "(current mode: " + currentMode + ")"; stopAndReportError(new IllegalStateException(message), "Importing subscriptions"); return; } - flowable.doOnNext(subscriptionItems -> eventListener.onSizeReceived(subscriptionItems.size())) + flowable.doOnNext(subscriptionItems -> + eventListener.onSizeReceived(subscriptionItems.size())) .flatMap(Flowable::fromIterable) .parallel(PARALLEL_EXTRACTIONS) @@ -169,7 +180,8 @@ private void startImport() { .map((Function>) subscriptionItem -> { try { return Notification.createOnNext(ExtractorHelper - .getChannelInfo(subscriptionItem.getServiceId(), subscriptionItem.getUrl(), true) + .getChannelInfo(subscriptionItem.getServiceId(), + subscriptionItem.getUrl(), true) .blockingGet()); } catch (Throwable e) { return Notification.createOnError(e); @@ -190,27 +202,30 @@ private void startImport() { private Subscriber> getSubscriber() { return new Subscriber>() { - @Override - public void onSubscribe(Subscription s) { + public void onSubscribe(final Subscription s) { subscription = s; s.request(Long.MAX_VALUE); } @Override - public void onNext(List successfulInserted) { - if (DEBUG) Log.d(TAG, "startImport() " + successfulInserted.size() + " items successfully inserted into the database"); + public void onNext(final List successfulInserted) { + if (DEBUG) { + Log.d(TAG, "startImport() " + successfulInserted.size() + + " items successfully inserted into the database"); + } } @Override - public void onError(Throwable error) { + public void onError(final Throwable error) { Log.e(TAG, "Got an error!", error); handleError(error); } @Override public void onComplete() { - LocalBroadcastManager.getInstance(SubscriptionsImportService.this).sendBroadcast(new Intent(IMPORT_COMPLETE_ACTION)); + LocalBroadcastManager.getInstance(SubscriptionsImportService.this) + .sendBroadcast(new Intent(IMPORT_COMPLETE_ACTION)); showToast(R.string.import_complete_toast); stopService(); } @@ -240,7 +255,9 @@ private Function>, List> upse return notificationList -> { final List infoList = new ArrayList<>(notificationList.size()); for (Notification n : notificationList) { - if (n.isOnNext()) infoList.add(n.getValue()); + if (n.isOnNext()) { + infoList.add(n.getValue()); + } } return subscriptionManager.upsertAll(infoList); @@ -263,7 +280,7 @@ private Flowable> importFromPreviousExport() { return Flowable.fromCallable(() -> ImportExportJsonHelper.readFrom(inputStream, null)); } - protected void handleError(@NonNull Throwable error) { + protected void handleError(@NonNull final Throwable error) { super.handleError(R.string.subscriptions_import_unsuccessful, error); } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/player/AudioServiceLeakFix.java b/app/src/main/java/org/schabi/newpipe/player/AudioServiceLeakFix.java index 9f0c849f5..c36a77421 100644 --- a/app/src/main/java/org/schabi/newpipe/player/AudioServiceLeakFix.java +++ b/app/src/main/java/org/schabi/newpipe/player/AudioServiceLeakFix.java @@ -11,20 +11,19 @@ * https://gist.github.com/jankovd/891d96f476f7a9ce24e2 */ public class AudioServiceLeakFix extends ContextWrapper { + AudioServiceLeakFix(final Context base) { + super(base); + } - AudioServiceLeakFix(Context base) { - super(base); - } + public static ContextWrapper preventLeakOf(final Context base) { + return new AudioServiceLeakFix(base); + } - public static ContextWrapper preventLeakOf(Context base) { - return new AudioServiceLeakFix(base); - } - - @Override - public Object getSystemService(String name) { - if (Context.AUDIO_SERVICE.equals(name)) { - return getApplicationContext().getSystemService(name); - } - return super.getSystemService(name); - } -} \ No newline at end of file + @Override + public Object getSystemService(final String name) { + if (Context.AUDIO_SERVICE.equals(name)) { + return getApplicationContext().getSystemService(name); + } + return super.getSystemService(name); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 4eaa2a73b..72cc75a66 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -61,48 +61,49 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; /** - * Base players joining the common properties + * Service Background Player implementing {@link VideoPlayer}. * * @author mauriciocolli */ public final class BackgroundPlayer extends Service { - private static final String TAG = "BackgroundPlayer"; - private static final boolean DEBUG = BasePlayer.DEBUG; - - public static final String ACTION_CLOSE = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE"; - public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT"; - public static final String ACTION_PLAY_NEXT = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT"; - public static final String ACTION_PLAY_PREVIOUS = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS"; - public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND"; - public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_CLOSE + = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE"; + public static final String ACTION_PLAY_PAUSE + = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT + = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT + = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS + = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND + = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD + = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD"; public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource"; - + private static final String TAG = "BackgroundPlayer"; + private static final boolean DEBUG = BasePlayer.DEBUG; + private static final int NOTIFICATION_ID = 123789; + private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60; private BasePlayerImpl basePlayerImpl; - private LockManager lockManager; - private SharedPreferences sharedPreferences; /*////////////////////////////////////////////////////////////////////////// // Service-Activity Binder //////////////////////////////////////////////////////////////////////////*/ - - private PlayerEventListener activityListener; - private IBinder mBinder; + private LockManager lockManager; + private SharedPreferences sharedPreferences; /*////////////////////////////////////////////////////////////////////////// // Notification //////////////////////////////////////////////////////////////////////////*/ - - private static final int NOTIFICATION_ID = 123789; + private PlayerEventListener activityListener; + private IBinder mBinder; private NotificationManager notificationManager; private NotificationCompat.Builder notBuilder; private RemoteViews notRemoteView; private RemoteViews bigNotRemoteView; - private boolean shouldUpdateOnProgress; - - private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60; private int timesNotificationUpdated; /*////////////////////////////////////////////////////////////////////////// @@ -111,7 +112,9 @@ public final class BackgroundPlayer extends Service { @Override public void onCreate() { - if (DEBUG) Log.d(TAG, "onCreate() called"); + if (DEBUG) { + Log.d(TAG, "onCreate() called"); + } notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); lockManager = new LockManager(this); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); @@ -125,9 +128,11 @@ public void onCreate() { } @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + - "], flags = [" + flags + "], startId = [" + startId + "]"); + public int onStartCommand(final Intent intent, final int flags, final int startId) { + if (DEBUG) { + Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], " + + "flags = [" + flags + "], startId = [" + startId + "]"); + } basePlayerImpl.handleIntent(intent); if (basePlayerImpl.mediaSessionManager != null) { basePlayerImpl.mediaSessionManager.handleMediaButtonIntent(intent); @@ -137,17 +142,19 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public void onDestroy() { - if (DEBUG) Log.d(TAG, "destroy() called"); + if (DEBUG) { + Log.d(TAG, "destroy() called"); + } onClose(); } @Override - protected void attachBaseContext(Context base) { + protected void attachBaseContext(final Context base) { super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base)); } @Override - public IBinder onBind(Intent intent) { + public IBinder onBind(final Intent intent) { return mBinder; } @@ -155,7 +162,9 @@ public IBinder onBind(Intent intent) { // Actions //////////////////////////////////////////////////////////////////////////*/ private void onClose() { - if (DEBUG) Log.d(TAG, "onClose() called"); + if (DEBUG) { + Log.d(TAG, "onClose() called"); + } if (lockManager != null) { lockManager.releaseWifiAndCpu(); @@ -165,7 +174,9 @@ private void onClose() { basePlayerImpl.stopActivityBinding(); basePlayerImpl.destroy(); } - if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); + if (notificationManager != null) { + notificationManager.cancel(NOTIFICATION_ID); + } mBinder = null; basePlayerImpl = null; lockManager = null; @@ -174,8 +185,10 @@ private void onClose() { stopSelf(); } - private void onScreenOnOff(boolean on) { - if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]"); + private void onScreenOnOff(final boolean on) { + if (DEBUG) { + Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]"); + } shouldUpdateOnProgress = on; basePlayerImpl.triggerProgressUpdate(); if (on) { @@ -196,12 +209,14 @@ private void resetNotification() { private NotificationCompat.Builder createNotification() { notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification); - bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded); + bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, + R.layout.player_notification_expanded); setupNotification(notRemoteView); setupNotification(bigNotRemoteView); - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) + NotificationCompat.Builder builder = new NotificationCompat + .Builder(this, getString(R.string.notification_channel_id)) .setOngoing(true) .setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -219,11 +234,9 @@ private NotificationCompat.Builder createNotification() { } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - private void setLockScreenThumbnail(NotificationCompat.Builder builder) { + private void setLockScreenThumbnail(final NotificationCompat.Builder builder) { boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean( - getString(R.string.enable_lock_screen_video_thumbnail_key), - true - ); + getString(R.string.enable_lock_screen_video_thumbnail_key), true); if (isLockScreenThumbnailEnabled) { basePlayerImpl.mediaSessionManager.setLockScreenArt( @@ -237,47 +250,58 @@ private void setLockScreenThumbnail(NotificationCompat.Builder builder) { @Nullable private Bitmap getCenteredThumbnailBitmap() { - int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; - int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; + final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; + final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; - return BitmapUtils.centerCrop( - basePlayerImpl.getThumbnail(), - screenWidth, - screenHeight); + return BitmapUtils.centerCrop(basePlayerImpl.getThumbnail(), screenWidth, screenHeight); } - private void setupNotification(RemoteViews remoteViews) { - if (basePlayerImpl == null) return; + private void setupNotification(final RemoteViews remoteViews) { + if (basePlayerImpl == null) { + return; + } remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getUploaderName()); remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationStop, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationRepeat, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT)); // Starts background player activity -- attempts to unlock lockscreen final Intent intent = NavigationHelper.getBackgroundPlayerActivityIntent(this); remoteViews.setOnClickPendingIntent(R.id.notificationContent, - PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getActivity(this, NOTIFICATION_ID, intent, + PendingIntent.FLAG_UPDATE_CURRENT)); if (basePlayerImpl.playQueue != null && basePlayerImpl.playQueue.size() > 1) { - remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_previous); - remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_next); + remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_previous); + remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_next); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); } else { - remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_rewind); - remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_fastforward); + remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_rewind); + remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_fastforward); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, + new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); } setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); @@ -289,14 +313,20 @@ private void setupNotification(RemoteViews remoteViews) { * * @param drawableId if != -1, sets the drawable with that id on the play/pause button */ - private synchronized void updateNotification(int drawableId) { - //if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]"); - if (notBuilder == null) return; + private synchronized void updateNotification(final int drawableId) { +// if (DEBUG) { +// Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]"); +// } + if (notBuilder == null) { + return; + } if (drawableId != -1) { - if (notRemoteView != null) + if (notRemoteView != null) { notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId); - if (bigNotRemoteView != null) + } + if (bigNotRemoteView != null) { bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId); + } } notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); timesNotificationUpdated++; @@ -309,44 +339,48 @@ private synchronized void updateNotification(int drawableId) { private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) { switch (repeatMode) { case Player.REPEAT_MODE_OFF: - remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off); + remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_repeat_off); break; case Player.REPEAT_MODE_ONE: - remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one); + remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_repeat_one); break; case Player.REPEAT_MODE_ALL: - remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all); + remoteViews.setInt(R.id.notificationRepeat, SET_IMAGE_RESOURCE_METHOD, + R.drawable.exo_controls_repeat_all); break; } } ////////////////////////////////////////////////////////////////////////// protected class BasePlayerImpl extends BasePlayer { - @NonNull - final private AudioPlaybackResolver resolver; + private final AudioPlaybackResolver resolver; private int cachedDuration; private String cachedDurationString; - BasePlayerImpl(Context context) { + BasePlayerImpl(final Context context) { super(context); this.resolver = new AudioPlaybackResolver(context, dataSource); } @Override - public void initPlayer(boolean playOnReady) { + public void initPlayer(final boolean playOnReady) { super.initPlayer(playOnReady); } @Override public void handleIntent(final Intent intent) { super.handleIntent(intent); - + resetNotification(); - if (bigNotRemoteView != null) + if (bigNotRemoteView != null) { bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false); - if (notRemoteView != null) + } + if (notRemoteView != null) { notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false); + } startForeground(NOTIFICATION_ID, notBuilder.build()); } @@ -355,7 +389,9 @@ public void handleIntent(final Intent intent) { //////////////////////////////////////////////////////////////////////////*/ private void updateNotificationThumbnail() { - if (basePlayerImpl == null) return; + if (basePlayerImpl == null) { + return; + } if (notRemoteView != null) { notRemoteView.setImageViewBitmap(R.id.notificationCover, basePlayerImpl.getThumbnail()); @@ -367,7 +403,8 @@ private void updateNotificationThumbnail() { } @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + public void onLoadingComplete(final String imageUri, final View view, + final Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); resetNotification(); updateNotificationThumbnail(); @@ -375,7 +412,8 @@ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { } @Override - public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + public void onLoadingFailed(final String imageUri, final View view, + final FailReason failReason) { super.onLoadingFailed(imageUri, view, failReason); resetNotification(); updateNotificationThumbnail(); @@ -387,7 +425,7 @@ public void onLoadingFailed(String imageUri, View view, FailReason failReason) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onPrepared(boolean playWhenReady) { + public void onPrepared(final boolean playWhenReady) { super.onPrepared(playWhenReady); } @@ -404,10 +442,13 @@ public void onMuteUnmuteButtonClicked() { } @Override - public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { + public void onUpdateProgress(final int currentProgress, final int duration, + final int bufferPercent) { updateProgress(currentProgress, duration, bufferPercent); - if (!shouldUpdateOnProgress) return; + if (!shouldUpdateOnProgress) { + return; + } if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) { resetNotification(); @@ -420,11 +461,14 @@ public void onUpdateProgress(int currentProgress, int duration, int bufferPercen cachedDuration = duration; cachedDurationString = getTimeString(duration); } - bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); - bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString); + bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, + currentProgress, false); + bigNotRemoteView.setTextViewText(R.id.notificationTime, + getTimeString(currentProgress) + " / " + cachedDurationString); } if (notRemoteView != null) { - notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); + notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, + currentProgress, false); } updateNotification(-1); } @@ -444,10 +488,12 @@ public void onPlayNext() { @Override public void destroy() { super.destroy(); - if (notRemoteView != null) + if (notRemoteView != null) { notRemoteView.setImageViewBitmap(R.id.notificationCover, null); - if (bigNotRemoteView != null) + } + if (bigNotRemoteView != null) { bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null); + } } /*////////////////////////////////////////////////////////////////////////// @@ -455,18 +501,18 @@ public void destroy() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) { super.onPlaybackParametersChanged(playbackParameters); updatePlayback(); } @Override - public void onLoadingChanged(boolean isLoading) { + public void onLoadingChanged(final boolean isLoading) { // Disable default behavior } @Override - public void onRepeatModeChanged(int i) { + public void onRepeatModeChanged(final int i) { resetNotification(); updateNotification(-1); updatePlayback(); @@ -500,14 +546,14 @@ public void onPlaybackShutdown() { // Activity Event Listener //////////////////////////////////////////////////////////////////////////*/ - /*package-private*/ void setActivityListener(PlayerEventListener listener) { + /*package-private*/ void setActivityListener(final PlayerEventListener listener) { activityListener = listener; updateMetadata(); updatePlayback(); triggerProgressUpdate(); } - /*package-private*/ void removeActivityListener(PlayerEventListener listener) { + /*package-private*/ void removeActivityListener(final PlayerEventListener listener) { if (activityListener == listener) { activityListener = null; } @@ -526,7 +572,8 @@ private void updatePlayback() { } } - private void updateProgress(int currentProgress, int duration, int bufferPercent) { + private void updateProgress(final int currentProgress, final int duration, + final int bufferPercent) { if (activityListener != null) { activityListener.onProgressUpdate(currentProgress, duration, bufferPercent); } @@ -544,27 +591,31 @@ private void stopActivityBinding() { //////////////////////////////////////////////////////////////////////////*/ @Override - protected void setupBroadcastReceiver(IntentFilter intentFilter) { - super.setupBroadcastReceiver(intentFilter); - intentFilter.addAction(ACTION_CLOSE); - intentFilter.addAction(ACTION_PLAY_PAUSE); - intentFilter.addAction(ACTION_REPEAT); - intentFilter.addAction(ACTION_PLAY_PREVIOUS); - intentFilter.addAction(ACTION_PLAY_NEXT); - intentFilter.addAction(ACTION_FAST_REWIND); - intentFilter.addAction(ACTION_FAST_FORWARD); + protected void setupBroadcastReceiver(final IntentFilter intentFltr) { + super.setupBroadcastReceiver(intentFltr); + intentFltr.addAction(ACTION_CLOSE); + intentFltr.addAction(ACTION_PLAY_PAUSE); + intentFltr.addAction(ACTION_REPEAT); + intentFltr.addAction(ACTION_PLAY_PREVIOUS); + intentFltr.addAction(ACTION_PLAY_NEXT); + intentFltr.addAction(ACTION_FAST_REWIND); + intentFltr.addAction(ACTION_FAST_FORWARD); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFltr.addAction(Intent.ACTION_SCREEN_ON); + intentFltr.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(Intent.ACTION_HEADSET_PLUG); + intentFltr.addAction(Intent.ACTION_HEADSET_PLUG); } @Override - public void onBroadcastReceived(Intent intent) { + public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); - if (intent == null || intent.getAction() == null) return; - if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]"); + if (intent == null || intent.getAction() == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]"); + } switch (intent.getAction()) { case ACTION_CLOSE: onClose(); @@ -601,7 +652,7 @@ public void onBroadcastReceived(Intent intent) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void changeState(int state) { + public void changeState(final int state) { super.changeState(state); updatePlayback(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java index 59f6e1e6d..9da3c3c86 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java @@ -47,7 +47,7 @@ public int getPlayerOptionMenuResource() { } @Override - public boolean onPlayerOptionSelected(MenuItem item) { + public boolean onPlayerOptionSelected(final MenuItem item) { if (item.getItemId() == R.id.action_switch_popup) { if (!PermissionHelper.isPopupEnabled(this)) { @@ -58,8 +58,8 @@ public boolean onPlayerOptionSelected(MenuItem item) { this.player.setRecovery(); getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); getApplicationContext().startService( - getSwitchIntent(PopupVideoPlayer.class) - .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()) + getSwitchIntent(PopupVideoPlayer.class) + .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()) ); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 08fdb9258..d4a7e7851 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -92,7 +92,7 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; /** - * Base for the players, joining the common properties + * Base for the players, joining the common properties. * * @author mauriciocolli */ @@ -103,36 +103,6 @@ public abstract class BasePlayer implements public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); @NonNull public static final String TAG = "BasePlayer"; - - @NonNull - final protected Context context; - - @NonNull - final protected BroadcastReceiver broadcastReceiver; - @NonNull - final protected IntentFilter intentFilter; - - @NonNull - final protected HistoryRecordManager recordManager; - - @NonNull - final protected CustomTrackSelector trackSelector; - @NonNull - final protected PlayerDataSource dataSource; - - @NonNull - final private LoadControl loadControl; - @NonNull - final private RenderersFactory renderFactory; - - @NonNull - final private SerialDisposable progressUpdateReactor; - @NonNull - final private CompositeDisposable databaseUpdateReactor; - /*////////////////////////////////////////////////////////////////////////// - // Intent - //////////////////////////////////////////////////////////////////////////*/ - @NonNull public static final String REPEAT_MODE = "repeat_mode"; @NonNull @@ -153,46 +123,73 @@ public abstract class BasePlayer implements public static final String START_PAUSED = "start_paused"; @NonNull public static final String SELECT_ON_APPEND = "select_on_append"; + /*////////////////////////////////////////////////////////////////////////// + // Intent + //////////////////////////////////////////////////////////////////////////*/ @NonNull public static final String IS_MUTED = "is_muted"; + public static final int STATE_PREFLIGHT = -1; + public static final int STATE_BLOCKED = 123; + public static final int STATE_PLAYING = 124; + public static final int STATE_BUFFERING = 125; + public static final int STATE_PAUSED = 126; + public static final int STATE_PAUSED_SEEK = 127; + public static final int STATE_COMPLETED = 128; + protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f}; + protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds + protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; /*////////////////////////////////////////////////////////////////////////// // Playback //////////////////////////////////////////////////////////////////////////*/ + protected static final int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds + @NonNull + protected final Context context; + @NonNull + protected final BroadcastReceiver broadcastReceiver; + @NonNull + protected final IntentFilter intentFilter; + @NonNull + protected final HistoryRecordManager recordManager; + @NonNull + protected final CustomTrackSelector trackSelector; + @NonNull + protected final PlayerDataSource dataSource; + @NonNull + private final LoadControl loadControl; - protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f}; - + /*////////////////////////////////////////////////////////////////////////// + // Player + //////////////////////////////////////////////////////////////////////////*/ + @NonNull + private final RenderersFactory renderFactory; + @NonNull + private final SerialDisposable progressUpdateReactor; + @NonNull + private final CompositeDisposable databaseUpdateReactor; protected PlayQueue playQueue; protected PlayQueueAdapter playQueueAdapter; - @Nullable protected MediaSourceManager playbackManager; + @Nullable + protected Toast errorToast; + protected SimpleExoPlayer simpleExoPlayer; + //////////////////////////////////////////////////////////////////////////*/ + protected AudioReactor audioReactor; + protected MediaSessionManager mediaSessionManager; + protected int currentState = STATE_PREFLIGHT; @Nullable private PlayQueueItem currentItem; @Nullable private MediaSourceTag currentMetadata; @Nullable private Bitmap currentThumbnail; - - @Nullable - protected Toast errorToast; - - /*////////////////////////////////////////////////////////////////////////// - // Player - //////////////////////////////////////////////////////////////////////////*/ - - protected final static int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds - protected final static int PROGRESS_LOOP_INTERVAL_MILLIS = 500; - protected final static int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds - - protected SimpleExoPlayer simpleExoPlayer; - protected AudioReactor audioReactor; - protected MediaSessionManager mediaSessionManager; - private boolean isPrepared = false; private Disposable stateLoader; + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail Loading //////////////////////////////////////////////////////////////////////////*/ public BasePlayer(@NonNull final Context context) { @@ -200,7 +197,7 @@ public BasePlayer(@NonNull final Context context) { this.broadcastReceiver = new BroadcastReceiver() { @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(final Context ctx, final Intent intent) { onBroadcastReceived(intent); } }; @@ -213,13 +210,15 @@ public void onReceive(Context context, Intent intent) { this.databaseUpdateReactor = new CompositeDisposable(); final String userAgent = DownloaderImpl.USER_AGENT; - final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); + final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context) + .build(); this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter); - final TrackSelection.Factory trackSelectionFactory = PlayerHelper.getQualitySelector(context); + final TrackSelection.Factory trackSelectionFactory = PlayerHelper + .getQualitySelector(context); this.trackSelector = new CustomTrackSelector(trackSelectionFactory); - this.loadControl = new LoadController(context); + this.loadControl = new LoadController(); this.renderFactory = new DefaultRenderersFactory(context); } @@ -231,9 +230,12 @@ public void setup() { } public void initPlayer(final boolean playOnReady) { - if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]"); + if (DEBUG) { + Log.d(TAG, "initPlayer() called with: context = [" + context + "]"); + } - simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderFactory, trackSelector, loadControl); + simpleExoPlayer = ExoPlayerFactory + .newSimpleInstance(context, renderFactory, trackSelector, loadControl); simpleExoPlayer.addListener(this); simpleExoPlayer.setPlayWhenReady(playOnReady); simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); @@ -248,24 +250,31 @@ public void initPlayer(final boolean playOnReady) { public void initListeners() { } - public void handleIntent(Intent intent) { - if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); - if (intent == null) return; + public void handleIntent(final Intent intent) { + if (DEBUG) { + Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); + } + if (intent == null) { + return; + } // Resolve play queue - if (!intent.hasExtra(PLAY_QUEUE_KEY)) return; + if (!intent.hasExtra(PLAY_QUEUE_KEY)) { + return; + } final String intentCacheKey = intent.getStringExtra(PLAY_QUEUE_KEY); final PlayQueue queue = SerializedCache.getInstance().take(intentCacheKey, PlayQueue.class); - if (queue == null) return; + if (queue == null) { + return; + } // Resolve append intents if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) { int sizeBeforeAppend = playQueue.size(); playQueue.append(queue.getStreams()); - if ((intent.getBooleanExtra(SELECT_ON_APPEND, false) || - getCurrentState() == STATE_COMPLETED) && - queue.getStreams().size() > 0) { + if ((intent.getBooleanExtra(SELECT_ON_APPEND, false) + || getCurrentState() == STATE_COMPLETED) && queue.getStreams().size() > 0) { playQueue.setIndex(sizeBeforeAppend); } @@ -277,7 +286,8 @@ public void handleIntent(Intent intent) { final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch()); final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE, getPlaybackSkipSilence()); - final boolean isMuted = intent.getBooleanExtra(IS_MUTED, simpleExoPlayer == null ? false : isMuted()); + final boolean isMuted = intent + .getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted()); // seek to timestamp if stream is already playing if (simpleExoPlayer != null @@ -289,18 +299,20 @@ public void handleIntent(Intent intent) { ) { simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition()); return; - } else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) { final PlayQueueItem item = queue.getItem(); if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) { stateLoader = recordManager.loadStreamState(item) .observeOn(AndroidSchedulers.mainThread()) - .doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, - /*playOnInit=*/true, isMuted)) + .doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed, + playbackPitch, playbackSkipSilence, true, isMuted)) .subscribe( - state -> queue.setRecovery(queue.getIndex(), state.getProgressTime()), + state -> queue + .setRecovery(queue.getIndex(), state.getProgressTime()), error -> { - if (DEBUG) error.printStackTrace(); + if (DEBUG) { + error.printStackTrace(); + } } ); databaseUpdateReactor.add(stateLoader); @@ -312,6 +324,10 @@ public void handleIntent(Intent intent) { /*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted); } + /*////////////////////////////////////////////////////////////////////////// + // Broadcast Receiver + //////////////////////////////////////////////////////////////////////////*/ + protected void initPlayback(@NonNull final PlayQueue queue, @Player.RepeatMode final int repeatMode, final float playbackSpeed, @@ -326,28 +342,46 @@ protected void initPlayback(@NonNull final PlayQueue queue, playQueue = queue; playQueue.init(); - if (playbackManager != null) playbackManager.dispose(); + if (playbackManager != null) { + playbackManager.dispose(); + } playbackManager = new MediaSourceManager(this, playQueue); - if (playQueueAdapter != null) playQueueAdapter.dispose(); + if (playQueueAdapter != null) { + playQueueAdapter.dispose(); + } playQueueAdapter = new PlayQueueAdapter(context, playQueue); simpleExoPlayer.setVolume(isMuted ? 0 : 1); } public void destroyPlayer() { - if (DEBUG) Log.d(TAG, "destroyPlayer() called"); + if (DEBUG) { + Log.d(TAG, "destroyPlayer() called"); + } if (simpleExoPlayer != null) { simpleExoPlayer.removeListener(this); simpleExoPlayer.stop(); simpleExoPlayer.release(); } - if (isProgressLoopRunning()) stopProgressLoop(); - if (playQueue != null) playQueue.dispose(); - if (audioReactor != null) audioReactor.dispose(); - if (playbackManager != null) playbackManager.dispose(); - if (mediaSessionManager != null) mediaSessionManager.dispose(); - if (stateLoader != null) stateLoader.dispose(); + if (isProgressLoopRunning()) { + stopProgressLoop(); + } + if (playQueue != null) { + playQueue.dispose(); + } + if (audioReactor != null) { + audioReactor.dispose(); + } + if (playbackManager != null) { + playbackManager.dispose(); + } + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + } + if (stateLoader != null) { + stateLoader.dispose(); + } if (playQueueAdapter != null) { playQueueAdapter.unsetSelectedListener(); @@ -356,7 +390,9 @@ public void destroyPlayer() { } public void destroy() { - if (DEBUG) Log.d(TAG, "destroy() called"); + if (DEBUG) { + Log.d(TAG, "destroy() called"); + } destroyPlayer(); unregisterBroadcastReceiver(); @@ -365,61 +401,71 @@ public void destroy() { } - /*////////////////////////////////////////////////////////////////////////// - // Thumbnail Loading - //////////////////////////////////////////////////////////////////////////*/ - private void initThumbnail(final String url) { - if (DEBUG) Log.d(TAG, "Thumbnail - initThumbnail() called"); - if (url == null || url.isEmpty()) return; + if (DEBUG) { + Log.d(TAG, "Thumbnail - initThumbnail() called"); + } + if (url == null || url.isEmpty()) { + return; + } ImageLoader.getInstance().resume(); - ImageLoader.getInstance().loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, - this); + ImageLoader.getInstance() + .loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this); } + /*////////////////////////////////////////////////////////////////////////// + // States Implementation + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onLoadingStarted(String imageUri, View view) { - if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingStarted() called on: " + - "imageUri = [" + imageUri + "], view = [" + view + "]"); + public void onLoadingStarted(final String imageUri, final View view) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingStarted() called on: " + + "imageUri = [" + imageUri + "], view = [" + view + "]"); + } } @Override - public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + public void onLoadingFailed(final String imageUri, final View view, + final FailReason failReason) { Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]", failReason.getCause()); currentThumbnail = null; } @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { - if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " + - "imageUri = [" + imageUri + "], view = [" + view + "], " + - "loadedImage = [" + loadedImage + "]"); + public void onLoadingComplete(final String imageUri, final View view, + final Bitmap loadedImage) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " + + "imageUri = [" + imageUri + "], view = [" + view + "], " + + "loadedImage = [" + loadedImage + "]"); + } currentThumbnail = loadedImage; } @Override - public void onLoadingCancelled(String imageUri, View view) { - if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " + - "imageUri = [" + imageUri + "], view = [" + view + "]"); + public void onLoadingCancelled(final String imageUri, final View view) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " + + "imageUri = [" + imageUri + "], view = [" + view + "]"); + } currentThumbnail = null; } - /*////////////////////////////////////////////////////////////////////////// - // Broadcast Receiver - //////////////////////////////////////////////////////////////////////////*/ - /** - * Add your action in the intentFilter + * Add your action in the intentFilter. * - * @param intentFilter intent filter that will be used for register the receiver + * @param intentFltr intent filter that will be used for register the receiver */ - protected void setupBroadcastReceiver(IntentFilter intentFilter) { - intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + protected void setupBroadcastReceiver(final IntentFilter intentFltr) { + intentFltr.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); } - public void onBroadcastReceived(Intent intent) { - if (intent == null || intent.getAction() == null) return; + public void onBroadcastReceived(final Intent intent) { + if (intent == null || intent.getAction() == null) { + return; + } switch (intent.getAction()) { case AudioManager.ACTION_AUDIO_BECOMING_NOISY: onPause(); @@ -437,26 +483,15 @@ protected void unregisterBroadcastReceiver() { try { context.unregisterReceiver(broadcastReceiver); } catch (final IllegalArgumentException unregisteredException) { - Log.w(TAG, "Broadcast receiver already unregistered (" + unregisteredException.getMessage() + ")"); + Log.w(TAG, "Broadcast receiver already unregistered " + + "(" + unregisteredException.getMessage() + ")"); } } - /*////////////////////////////////////////////////////////////////////////// - // States Implementation - //////////////////////////////////////////////////////////////////////////*/ - - public static final int STATE_PREFLIGHT = -1; - public static final int STATE_BLOCKED = 123; - public static final int STATE_PLAYING = 124; - public static final int STATE_BUFFERING = 125; - public static final int STATE_PAUSED = 126; - public static final int STATE_PAUSED_SEEK = 127; - public static final int STATE_COMPLETED = 128; - - protected int currentState = STATE_PREFLIGHT; - - public void changeState(int state) { - if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]"); + public void changeState(final int state) { + if (DEBUG) { + Log.d(TAG, "changeState() called with: state = [" + state + "]"); + } currentState = state; switch (state) { case STATE_BLOCKED: @@ -481,29 +516,44 @@ public void changeState(int state) { } public void onBlocked() { - if (DEBUG) Log.d(TAG, "onBlocked() called"); - if (!isProgressLoopRunning()) startProgressLoop(); + if (DEBUG) { + Log.d(TAG, "onBlocked() called"); + } + if (!isProgressLoopRunning()) { + startProgressLoop(); + } } public void onPlaying() { - if (DEBUG) Log.d(TAG, "onPlaying() called"); - if (!isProgressLoopRunning()) startProgressLoop(); + if (DEBUG) { + Log.d(TAG, "onPlaying() called"); + } + if (!isProgressLoopRunning()) { + startProgressLoop(); + } } public void onBuffering() { } public void onPaused() { - if (isProgressLoopRunning()) stopProgressLoop(); + if (isProgressLoopRunning()) { + stopProgressLoop(); + } } - public void onPausedSeek() { - } + public void onPausedSeek() { } public void onCompleted() { - if (DEBUG) Log.d(TAG, "onCompleted() called"); - if (playQueue.getIndex() < playQueue.size() - 1) playQueue.offsetIndex(+1); - if (isProgressLoopRunning()) stopProgressLoop(); + if (DEBUG) { + Log.d(TAG, "onCompleted() called"); + } + if (playQueue.getIndex() < playQueue.size() - 1) { + playQueue.offsetIndex(+1); + } + if (isProgressLoopRunning()) { + stopProgressLoop(); + } } /*////////////////////////////////////////////////////////////////////////// @@ -511,7 +561,9 @@ public void onCompleted() { //////////////////////////////////////////////////////////////////////////*/ public void onRepeatClicked() { - if (DEBUG) Log.d(TAG, "onRepeatClicked() called"); + if (DEBUG) { + Log.d(TAG, "onRepeatClicked() called"); + } final int mode; @@ -529,13 +581,19 @@ public void onRepeatClicked() { } setRepeatMode(mode); - if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getRepeatMode()); + if (DEBUG) { + Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getRepeatMode()); + } } public void onShuffleClicked() { - if (DEBUG) Log.d(TAG, "onShuffleClicked() called"); + if (DEBUG) { + Log.d(TAG, "onShuffleClicked() called"); + } - if (simpleExoPlayer == null) return; + if (simpleExoPlayer == null) { + return; + } simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); } /*////////////////////////////////////////////////////////////////////////// @@ -543,7 +601,9 @@ public void onShuffleClicked() { //////////////////////////////////////////////////////////////////////////*/ public void onMuteUnmuteButtonClicked() { - if (DEBUG) Log.d(TAG, "onMuteUnmuteButtonClicled() called"); + if (DEBUG) { + Log.d(TAG, "onMuteUnmuteButtonClicled() called"); + } simpleExoPlayer.setVolume(isMuted() ? 1 : 0); } @@ -566,7 +626,9 @@ protected void stopProgressLoop() { } public void triggerProgressUpdate() { - if (simpleExoPlayer == null) return; + if (simpleExoPlayer == null) { + return; + } onUpdateProgress( Math.max((int) simpleExoPlayer.getCurrentPosition(), 0), (int) simpleExoPlayer.getDuration(), @@ -586,35 +648,44 @@ private Disposable getProgressReactor() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onTimelineChanged(Timeline timeline, Object manifest, + public void onTimelineChanged(final Timeline timeline, final Object manifest, @Player.TimelineChangeReason final int reason) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onTimelineChanged() called with " + - (manifest == null ? "no manifest" : "available manifest") + ", " + - "timeline size = [" + timeline.getWindowCount() + "], " + - "reason = [" + reason + "]"); + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onTimelineChanged() called with " + + (manifest == null ? "no manifest" : "available manifest") + ", " + + "timeline size = [" + timeline.getWindowCount() + "], " + + "reason = [" + reason + "]"); + } maybeUpdateCurrentMetadata(); } @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onTracksChanged(), " + - "track group size = " + trackGroups.length); + public void onTracksChanged(final TrackGroupArray trackGroups, + final TrackSelectionArray trackSelections) { + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onTracksChanged(), " + + "track group size = " + trackGroups.length); + } maybeUpdateCurrentMetadata(); } @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - if (DEBUG) Log.d(TAG, "ExoPlayer - playbackParameters(), " + - "speed: " + playbackParameters.speed + ", " + - "pitch: " + playbackParameters.pitch); + public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) { + if (DEBUG) { + Log.d(TAG, "ExoPlayer - playbackParameters(), " + + "speed: " + playbackParameters.speed + ", " + + "pitch: " + playbackParameters.pitch); + } } @Override public void onLoadingChanged(final boolean isLoading) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: " + - "isLoading = [" + isLoading + "]"); + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: " + + "isLoading = [" + isLoading + "]"); + } if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning()) { stopProgressLoop(); @@ -626,13 +697,17 @@ public void onLoadingChanged(final boolean isLoading) { } @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: " + - "playWhenReady = [" + playWhenReady + "], " + - "playbackState = [" + playbackState + "]"); + public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) { + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: " + + "playWhenReady = [" + playWhenReady + "], " + + "playbackState = [" + playbackState + "]"); + } if (getCurrentState() == STATE_PAUSED_SEEK) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onPlayerStateChanged() is currently blocked"); + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onPlayerStateChanged() is currently blocked"); + } return; } @@ -666,41 +741,47 @@ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { } private void maybeCorrectSeekPosition() { - if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) return; + if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) { + return; + } final PlayQueueItem currentSourceItem = playQueue.getItem(); - if (currentSourceItem == null) return; + if (currentSourceItem == null) { + return; + } final StreamInfo currentInfo = currentMetadata.getMetadata(); final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000; if (presetStartPositionMillis > 0L) { // Has another start position? - if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " + - "position=[" + presetStartPositionMillis + "]"); + if (DEBUG) { + Log.d(TAG, "Playback - Seeking to preset start " + + "position=[" + presetStartPositionMillis + "]"); + } seekTo(presetStartPositionMillis); } } /** - * Processes the exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}. - * There are multiple types of errors:

- *

- * {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}:

- *

- * {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}:

+ * Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}. + *

There are multiple types of errors:

+ *
    + *
  • {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}
  • + *
  • {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: * If a runtime error occurred, then we can try to recover it by restarting the playback - * after setting the timestamp recovery.

    - *

    - * {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}:

    - * If the renderer failed, treat the error as unrecoverable. + * after setting the timestamp recovery.

  • + *
  • {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: + * If the renderer failed, treat the error as unrecoverable.
  • + *
* * @see #processSourceError(IOException) * @see Player.EventListener#onPlayerError(ExoPlaybackException) */ @Override - public void onPlayerError(ExoPlaybackException error) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + - "error = [" + error + "]"); + public void onPlayerError(final ExoPlaybackException error) { + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + "error = [" + error + "]"); + } if (errorToast != null) { errorToast.cancel(); errorToast = null; @@ -726,7 +807,9 @@ public void onPlayerError(ExoPlaybackException error) { } private void processSourceError(final IOException error) { - if (simpleExoPlayer == null || playQueue == null) return; + if (simpleExoPlayer == null || playQueue == null) { + return; + } setRecovery(); final Throwable cause = error.getCause(); @@ -747,9 +830,13 @@ private void processSourceError(final IOException error) { @Override public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " + - "reason = [" + reason + "]"); - if (playQueue == null) return; + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " + + "reason = [" + reason + "]"); + } + if (playQueue == null) { + return; + } // Refresh the playback if there is a transition to the next video final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex(); @@ -757,8 +844,8 @@ public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason case DISCONTINUITY_REASON_PERIOD_TRANSITION: // When player is in single repeat mode and a period transition occurs, // we need to register a view count here since no metadata has changed - if (getRepeatMode() == Player.REPEAT_MODE_ONE && - newWindowIndex == playQueue.getIndex()) { + if (getRepeatMode() == Player.REPEAT_MODE_ONE + && newWindowIndex == playQueue.getIndex()) { registerView(); break; } @@ -777,15 +864,21 @@ public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason @Override public void onRepeatModeChanged(@Player.RepeatMode final int reason) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " + - "mode = [" + reason + "]"); + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " + + "mode = [" + reason + "]"); + } } @Override public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { - if (DEBUG) Log.d(TAG, "ExoPlayer - onShuffleModeEnabledChanged() called with: " + - "mode = [" + shuffleModeEnabled + "]"); - if (playQueue == null) return; + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onShuffleModeEnabledChanged() called with: " + + "mode = [" + shuffleModeEnabled + "]"); + } + if (playQueue == null) { + return; + } if (shuffleModeEnabled) { playQueue.shuffle(); } else { @@ -795,7 +888,9 @@ public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { @Override public void onSeekProcessed() { - if (DEBUG) Log.d(TAG, "ExoPlayer - onSeekProcessed() called"); + if (DEBUG) { + Log.d(TAG, "ExoPlayer - onSeekProcessed() called"); + } if (isPrepared) { savePlaybackState(); } @@ -808,7 +903,9 @@ public void onSeekProcessed() { public boolean isApproachingPlaybackEdge(final long timeToEndMillis) { // If live, then not near playback edge // If not playing, then not approaching playback edge - if (simpleExoPlayer == null || isLive() || !isPlaying()) return false; + if (simpleExoPlayer == null || isLive() || !isPlaying()) { + return false; + } final long currentPositionMillis = simpleExoPlayer.getCurrentPosition(); final long currentDurationMillis = simpleExoPlayer.getDuration(); @@ -817,8 +914,12 @@ public boolean isApproachingPlaybackEdge(final long timeToEndMillis) { @Override public void onPlaybackBlock() { - if (simpleExoPlayer == null) return; - if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called"); + if (simpleExoPlayer == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "Playback - onPlaybackBlock() called"); + } currentItem = null; currentMetadata = null; @@ -830,18 +931,28 @@ public void onPlaybackBlock() { @Override public void onPlaybackUnblock(final MediaSource mediaSource) { - if (simpleExoPlayer == null) return; - if (DEBUG) Log.d(TAG, "Playback - onPlaybackUnblock() called"); + if (simpleExoPlayer == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "Playback - onPlaybackUnblock() called"); + } - if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING); + if (getCurrentState() == STATE_BLOCKED) { + changeState(STATE_BUFFERING); + } simpleExoPlayer.prepare(mediaSource); } public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) { - if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " + - "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]"); - if (simpleExoPlayer == null || playQueue == null) return; + if (DEBUG) { + Log.d(TAG, "Playback - onPlaybackSynchronize() called with " + + "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]"); + } + if (simpleExoPlayer == null || playQueue == null) { + return; + } final boolean onPlaybackInitial = currentItem == null; final boolean hasPlayQueueItemChanged = currentItem != item; @@ -851,27 +962,32 @@ public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) { final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); // If nothing to synchronize - if (!hasPlayQueueItemChanged) return; + if (!hasPlayQueueItemChanged) { + return; + } currentItem = item; // Check if on wrong window if (currentPlayQueueIndex != playQueue.getIndex()) { - Log.e(TAG, "Playback - Play Queue may be desynchronized: item " + - "index=[" + currentPlayQueueIndex + "], " + - "queue index=[" + playQueue.getIndex() + "]"); + Log.e(TAG, "Playback - Play Queue may be desynchronized: item " + + "index=[" + currentPlayQueueIndex + "], " + + "queue index=[" + playQueue.getIndex() + "]"); // Check if bad seek position - } else if ((currentPlaylistSize > 0 && currentPlayQueueIndex >= currentPlaylistSize) || - currentPlayQueueIndex < 0) { - Log.e(TAG, "Playback - Trying to seek to invalid " + - "index=[" + currentPlayQueueIndex + "] with " + - "playlist length=[" + currentPlaylistSize + "]"); - - } else if (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial || - !isPlaying()) { - if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" + - " index=[" + currentPlayQueueIndex + "]," + - " from=[" + currentPlaylistIndex + "], size=[" + currentPlaylistSize + "]."); + } else if ((currentPlaylistSize > 0 && currentPlayQueueIndex >= currentPlaylistSize) + || currentPlayQueueIndex < 0) { + Log.e(TAG, "Playback - Trying to seek to invalid " + + "index=[" + currentPlayQueueIndex + "] with " + + "playlist length=[" + currentPlaylistSize + "]"); + + } else if (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial + || !isPlaying()) { + if (DEBUG) { + Log.d(TAG, "Playback - Rewinding to correct " + + "index=[" + currentPlayQueueIndex + "], " + + "from=[" + currentPlaylistIndex + "], " + + "size=[" + currentPlaylistSize + "]."); + } if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition()); @@ -894,7 +1010,9 @@ protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { @Override public void onPlaybackShutdown() { - if (DEBUG) Log.d(TAG, "Shutting down..."); + if (DEBUG) { + Log.d(TAG, "Shutting down..."); + } destroy(); } @@ -902,43 +1020,54 @@ public void onPlaybackShutdown() { // General Player //////////////////////////////////////////////////////////////////////////*/ - public void showStreamError(Exception exception) { + public void showStreamError(final Exception exception) { exception.printStackTrace(); if (errorToast == null) { - errorToast = Toast.makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT); + errorToast = Toast + .makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT); errorToast.show(); } } - public void showRecoverableError(Exception exception) { + public void showRecoverableError(final Exception exception) { exception.printStackTrace(); if (errorToast == null) { - errorToast = Toast.makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT); + errorToast = Toast + .makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT); errorToast.show(); } } - public void showUnrecoverableError(Exception exception) { + public void showUnrecoverableError(final Exception exception) { exception.printStackTrace(); if (errorToast != null) { errorToast.cancel(); } - errorToast = Toast.makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT); + errorToast = Toast + .makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT); errorToast.show(); } - public void onPrepared(boolean playWhenReady) { - if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); - if (playWhenReady) audioReactor.requestAudioFocus(); + public void onPrepared(final boolean playWhenReady) { + if (DEBUG) { + Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); + } + if (playWhenReady) { + audioReactor.requestAudioFocus(); + } changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); } public void onPlay() { - if (DEBUG) Log.d(TAG, "onPlay() called"); - if (audioReactor == null || playQueue == null || simpleExoPlayer == null) return; + if (DEBUG) { + Log.d(TAG, "onPlay() called"); + } + if (audioReactor == null || playQueue == null || simpleExoPlayer == null) { + return; + } audioReactor.requestAudioFocus(); @@ -954,15 +1083,21 @@ public void onPlay() { } public void onPause() { - if (DEBUG) Log.d(TAG, "onPause() called"); - if (audioReactor == null || simpleExoPlayer == null) return; + if (DEBUG) { + Log.d(TAG, "onPause() called"); + } + if (audioReactor == null || simpleExoPlayer == null) { + return; + } audioReactor.abandonAudioFocus(); simpleExoPlayer.setPlayWhenReady(false); } public void onPlayPause() { - if (DEBUG) Log.d(TAG, "onPlayPause() called"); + if (DEBUG) { + Log.d(TAG, "onPlayPause() called"); + } if (isPlaying()) { onPause(); @@ -972,31 +1107,40 @@ public void onPlayPause() { } public void onFastRewind() { - if (DEBUG) Log.d(TAG, "onFastRewind() called"); + if (DEBUG) { + Log.d(TAG, "onFastRewind() called"); + } seekBy(-getSeekDuration()); } public void onFastForward() { - if (DEBUG) Log.d(TAG, "onFastForward() called"); + if (DEBUG) { + Log.d(TAG, "onFastForward() called"); + } seekBy(getSeekDuration()); } private int getSeekDuration() { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String key = context.getString(R.string.seek_duration_key); - final String value = prefs.getString(key, context.getString(R.string.seek_duration_default_value)); + final String value = prefs + .getString(key, context.getString(R.string.seek_duration_default_value)); return Integer.parseInt(value); } public void onPlayPrevious() { - if (simpleExoPlayer == null || playQueue == null) return; - if (DEBUG) Log.d(TAG, "onPlayPrevious() called"); + if (simpleExoPlayer == null || playQueue == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "onPlayPrevious() called"); + } /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds, * restart current track. Also restart the track if the current track * is the first in a queue.*/ - if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS || - playQueue.getIndex() == 0) { + if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS + || playQueue.getIndex() == 0) { seekToDefault(); playQueue.offsetIndex(0); } else { @@ -1006,18 +1150,26 @@ public void onPlayPrevious() { } public void onPlayNext() { - if (playQueue == null) return; - if (DEBUG) Log.d(TAG, "onPlayNext() called"); + if (playQueue == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "onPlayNext() called"); + } savePlaybackState(); playQueue.offsetIndex(+1); } public void onSelected(final PlayQueueItem item) { - if (playQueue == null || simpleExoPlayer == null) return; + if (playQueue == null || simpleExoPlayer == null) { + return; + } final int index = playQueue.indexOf(item); - if (index == -1) return; + if (index == -1) { + return; + } if (playQueue.getIndex() == index && simpleExoPlayer.getCurrentWindowIndex() == index) { seekToDefault(); @@ -1027,13 +1179,19 @@ public void onSelected(final PlayQueueItem item) { playQueue.setIndex(index); } - public void seekTo(long positionMillis) { - if (DEBUG) Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]"); - if (simpleExoPlayer != null) simpleExoPlayer.seekTo(positionMillis); + public void seekTo(final long positionMillis) { + if (DEBUG) { + Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]"); + } + if (simpleExoPlayer != null) { + simpleExoPlayer.seekTo(positionMillis); + } } - public void seekBy(long offsetMillis) { - if (DEBUG) Log.d(TAG, "seekBy() called with: offsetMillis = [" + offsetMillis + "]"); + public void seekBy(final long offsetMillis) { + if (DEBUG) { + Log.d(TAG, "seekBy() called with: offsetMillis = [" + offsetMillis + "]"); + } seekTo(simpleExoPlayer.getCurrentPosition() + offsetMillis); } @@ -1053,11 +1211,13 @@ public void seekToDefault() { //////////////////////////////////////////////////////////////////////////*/ private void registerView() { - if (currentMetadata == null) return; + if (currentMetadata == null) { + return; + } final StreamInfo currentInfo = currentMetadata.getMetadata(); final Disposable viewRegister = recordManager.onViewed(currentInfo).onErrorComplete() .subscribe( - ignored -> {/* successful */}, + ignored -> { /* successful */ }, error -> Log.e(TAG, "Player onViewed() failure: ", error) ); databaseUpdateReactor.add(viewRegister); @@ -1074,14 +1234,20 @@ protected void reload() { } private void savePlaybackState(final StreamInfo info, final long progress) { - if (info == null) return; - if (DEBUG) Log.d(TAG, "savePlaybackState() called"); + if (info == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "savePlaybackState() called"); + } final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { final Disposable stateSaver = recordManager.saveStreamState(info, progress) .observeOn(AndroidSchedulers.mainThread()) .doOnError((e) -> { - if (DEBUG) e.printStackTrace(); + if (DEBUG) { + e.printStackTrace(); + } }) .onErrorComplete() .subscribe(); @@ -1090,14 +1256,18 @@ private void savePlaybackState(final StreamInfo info, final long progress) { } private void resetPlaybackState(final PlayQueueItem queueItem) { - if (queueItem == null) return; + if (queueItem == null) { + return; + } final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { final Disposable stateSaver = queueItem.getStream() .flatMapCompletable(info -> recordManager.saveStreamState(info, 0)) .observeOn(AndroidSchedulers.mainThread()) .doOnError((e) -> { - if (DEBUG) e.printStackTrace(); + if (DEBUG) { + e.printStackTrace(); + } }) .onErrorComplete() .subscribe(); @@ -1110,39 +1280,53 @@ public void resetPlaybackState(final StreamInfo info) { } public void savePlaybackState() { - if (simpleExoPlayer == null || currentMetadata == null) return; + if (simpleExoPlayer == null || currentMetadata == null) { + return; + } final StreamInfo currentInfo = currentMetadata.getMetadata(); savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition()); } private void maybeUpdateCurrentMetadata() { - if (simpleExoPlayer == null) return; + if (simpleExoPlayer == null) { + return; + } final MediaSourceTag metadata; try { metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag(); } catch (IndexOutOfBoundsException | ClassCastException error) { - if (DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage()); - if (DEBUG) error.printStackTrace(); + if (DEBUG) { + Log.d(TAG, "Could not update metadata: " + error.getMessage()); + error.printStackTrace(); + } return; } - if (metadata == null) return; + if (metadata == null) { + return; + } maybeAutoQueueNextStream(metadata); - if (currentMetadata == metadata) return; + if (currentMetadata == metadata) { + return; + } currentMetadata = metadata; onMetadataChanged(metadata); } - private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag currentMetadata) { - if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1 || - getRepeatMode() != Player.REPEAT_MODE_OFF || - !PlayerHelper.isAutoQueueEnabled(context)) return; + private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag metadata) { + if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1 + || getRepeatMode() != Player.REPEAT_MODE_OFF + || !PlayerHelper.isAutoQueueEnabled(context)) { + return; + } // auto queue when starting playback on the last item when not repeating - final PlayQueue autoQueue = PlayerHelper.autoQueueOf(currentMetadata.getMetadata(), + final PlayQueue autoQueue = PlayerHelper.autoQueueOf(metadata.getMetadata(), playQueue.getStreams()); - if (autoQueue != null) playQueue.append(autoQueue.getStreams()); + if (autoQueue != null) { + playQueue.append(autoQueue.getStreams()); + } } /*////////////////////////////////////////////////////////////////////////// // Getters and Setters @@ -1167,37 +1351,47 @@ public MediaSourceTag getCurrentMetadata() { @NonNull public String getVideoUrl() { - return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUrl(); + return currentMetadata == null + ? context.getString(R.string.unknown_content) + : currentMetadata.getMetadata().getUrl(); } @NonNull public String getVideoTitle() { - return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getName(); + return currentMetadata == null + ? context.getString(R.string.unknown_content) + : currentMetadata.getMetadata().getName(); } @NonNull public String getUploaderName() { - return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUploaderName(); + return currentMetadata == null + ? context.getString(R.string.unknown_content) + : currentMetadata.getMetadata().getUploaderName(); } @Nullable public Bitmap getThumbnail() { - return currentThumbnail == null ? - BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail) : - currentThumbnail; + return currentThumbnail == null + ? BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail) + : currentThumbnail; } /** - * Checks if the current playback is a livestream AND is playing at or beyond the live edge + * Checks if the current playback is a livestream AND is playing at or beyond the live edge. + * + * @return whether the livestream is playing at or beyond the edge */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isLiveEdge() { - if (simpleExoPlayer == null || !isLive()) return false; + if (simpleExoPlayer == null || !isLive()) { + return false; + } final Timeline currentTimeline = simpleExoPlayer.getCurrentTimeline(); final int currentWindowIndex = simpleExoPlayer.getCurrentWindowIndex(); - if (currentTimeline.isEmpty() || currentWindowIndex < 0 || - currentWindowIndex >= currentTimeline.getWindowCount()) { + if (currentTimeline.isEmpty() || currentWindowIndex < 0 + || currentWindowIndex >= currentTimeline.getWindowCount()) { return false; } @@ -1207,14 +1401,18 @@ public boolean isLiveEdge() { } public boolean isLive() { - if (simpleExoPlayer == null) return false; + if (simpleExoPlayer == null) { + return false; + } try { return simpleExoPlayer.isCurrentWindowDynamic(); - } catch (@NonNull IndexOutOfBoundsException ignored) { + } catch (@NonNull IndexOutOfBoundsException e) { // Why would this even happen =( // But lets log it anyway. Save is save - if (DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage()); - if (DEBUG) ignored.printStackTrace(); + if (DEBUG) { + Log.d(TAG, "Could not update metadata: " + e.getMessage()); + e.printStackTrace(); + } return false; } } @@ -1231,13 +1429,19 @@ public int getRepeatMode() { } public void setRepeatMode(@Player.RepeatMode final int repeatMode) { - if (simpleExoPlayer != null) simpleExoPlayer.setRepeatMode(repeatMode); + if (simpleExoPlayer != null) { + simpleExoPlayer.setRepeatMode(repeatMode); + } } public float getPlaybackSpeed() { return getPlaybackParameters().speed; } + public void setPlaybackSpeed(final float speed) { + setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence()); + } + public float getPlaybackPitch() { return getPlaybackParameters().pitch; } @@ -1246,17 +1450,16 @@ public boolean getPlaybackSkipSilence() { return getPlaybackParameters().skipSilence; } - public void setPlaybackSpeed(float speed) { - setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence()); - } - public PlaybackParameters getPlaybackParameters() { - if (simpleExoPlayer == null) return PlaybackParameters.DEFAULT; + if (simpleExoPlayer == null) { + return PlaybackParameters.DEFAULT; + } final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters(); return parameters == null ? PlaybackParameters.DEFAULT : parameters; } - public void setPlaybackParameters(float speed, float pitch, boolean skipSilence) { + public void setPlaybackParameters(final float speed, final float pitch, + final boolean skipSilence) { simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence)); } @@ -1277,7 +1480,9 @@ public boolean isProgressLoopRunning() { } public void setRecovery() { - if (playQueue == null || simpleExoPlayer == null) return; + if (playQueue == null || simpleExoPlayer == null) { + return; + } final int queuePos = playQueue.getIndex(); final long windowPos = simpleExoPlayer.getCurrentPosition(); @@ -1288,9 +1493,13 @@ public void setRecovery() { } public void setRecovery(final int queuePos, final long windowPos) { - if (playQueue.size() <= queuePos) return; + if (playQueue.size() <= queuePos) { + return; + } - if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos); + if (DEBUG) { + Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos); + } playQueue.setRecovery(queuePos, windowPos); } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 42759a5ed..47ea9f4e3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -34,17 +34,6 @@ import android.os.Handler; import android.preference.PreferenceManager; import android.provider.Settings; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.ItemTouchHelper; - import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; @@ -64,6 +53,15 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.app.ActivityCompat; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; @@ -104,7 +102,7 @@ import static org.schabi.newpipe.util.StateSaver.KEY_SAVED_STATE; /** - * Activity Player implementing VideoPlayer + * Activity Player implementing {@link VideoPlayer}. * * @author mauriciocolli */ @@ -131,16 +129,19 @@ public final class MainVideoPlayer extends AppCompatActivity //////////////////////////////////////////////////////////////////////////*/ @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { + protected void onCreate(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(this); super.onCreate(savedInstanceState); - if (DEBUG) - Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); + if (DEBUG) { + Log.d(TAG, "onCreate() called with: " + + "savedInstanceState = [" + savedInstanceState + "]"); + } defaultPreferences = PreferenceManager.getDefaultSharedPreferences(this); ThemeHelper.setTheme(this); getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setStatusBarColor(Color.BLACK); + } setVolumeControlStream(AudioManager.STREAM_MUSIC); WindowManager.LayoutParams lp = getWindow().getAttributes(); @@ -166,11 +167,11 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { rotationObserver = new ContentObserver(new Handler()) { @Override - public void onChange(boolean selfChange) { + public void onChange(final boolean selfChange) { super.onChange(selfChange); if (globalScreenOrientationLocked()) { - final boolean lastOrientationWasLandscape = defaultPreferences.getBoolean( - getString(R.string.last_orientation_landscape_key), false); + final boolean lastOrientationWasLandscape = defaultPreferences + .getBoolean(getString(R.string.last_orientation_landscape_key), false); setLandscape(lastOrientationWasLandscape); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); @@ -183,15 +184,19 @@ public void onChange(boolean selfChange) { } @Override - protected void onRestoreInstanceState(@NonNull Bundle bundle) { - if (DEBUG) Log.d(TAG, "onRestoreInstanceState() called"); + protected void onRestoreInstanceState(@NonNull final Bundle bundle) { + if (DEBUG) { + Log.d(TAG, "onRestoreInstanceState() called"); + } super.onRestoreInstanceState(bundle); StateSaver.tryToRestore(bundle, this); } @Override - protected void onNewIntent(Intent intent) { - if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]"); + protected void onNewIntent(final Intent intent) { + if (DEBUG) { + Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]"); + } super.onNewIntent(intent); if (intent != null) { playerState = null; @@ -201,13 +206,15 @@ protected void onNewIntent(Intent intent) { @Override protected void onResume() { - if (DEBUG) Log.d(TAG, "onResume() called"); + if (DEBUG) { + Log.d(TAG, "onResume() called"); + } assureCorrectAppLanguage(this); super.onResume(); if (globalScreenOrientationLocked()) { - boolean lastOrientationWasLandscape = defaultPreferences.getBoolean( - getString(R.string.last_orientation_landscape_key), false); + boolean lastOrientationWasLandscape = defaultPreferences + .getBoolean(getString(R.string.last_orientation_landscape_key), false); setLandscape(lastOrientationWasLandscape); } @@ -219,19 +226,22 @@ protected void onResume() { // since the first onResume needs to restore the player. // Subsequent onResume calls while multiwindow mode remains the same and the player is // prepared should be ignored. - if (isInMultiWindow) return; + if (isInMultiWindow) { + return; + } isInMultiWindow = isInMultiWindow(); if (playerState != null) { playerImpl.setPlaybackQuality(playerState.getPlaybackQuality()); playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(), playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(), - playerState.isPlaybackSkipSilence(), playerState.wasPlaying(), playerImpl.isMuted()); + playerState.isPlaybackSkipSilence(), playerState.wasPlaying(), + playerImpl.isMuted()); } } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); assureCorrectAppLanguage(this); @@ -248,10 +258,14 @@ public void onBackPressed() { } @Override - protected void onSaveInstanceState(Bundle outState) { - if (DEBUG) Log.d(TAG, "onSaveInstanceState() called"); + protected void onSaveInstanceState(final Bundle outState) { + if (DEBUG) { + Log.d(TAG, "onSaveInstanceState() called"); + } super.onSaveInstanceState(outState); - if (playerImpl == null) return; + if (playerImpl == null) { + return; + } playerImpl.setRecovery(); if (!playerImpl.gotDestroyed()) { @@ -262,27 +276,32 @@ protected void onSaveInstanceState(Bundle outState) { @Override protected void onStop() { - if (DEBUG) Log.d(TAG, "onStop() called"); + if (DEBUG) { + Log.d(TAG, "onStop() called"); + } super.onStop(); PlayerHelper.setScreenBrightness(getApplicationContext(), getWindow().getAttributes().screenBrightness); - if (playerImpl == null) return; + if (playerImpl == null) { + return; + } if (!isBackPressed) { playerImpl.minimize(); } playerState = createPlayerState(); playerImpl.destroy(); - if (rotationObserver != null) + if (rotationObserver != null) { getContentResolver().unregisterContentObserver(rotationObserver); + } isInMultiWindow = false; isBackPressed = false; } @Override - protected void attachBaseContext(Context newBase) { + protected void attachBaseContext(final Context newBase) { super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase)); } @@ -309,14 +328,16 @@ public String generateSuffix() { } @Override - public void writeTo(Queue objectsToSave) { - if (objectsToSave == null) return; + public void writeTo(final Queue objectsToSave) { + if (objectsToSave == null) { + return; + } objectsToSave.add(playerState); } @Override @SuppressWarnings("unchecked") - public void readFrom(@NonNull Queue savedObjects) { + public void readFrom(@NonNull final Queue savedObjects) { playerState = (PlayerState) savedObjects.poll(); } @@ -325,8 +346,12 @@ public void readFrom(@NonNull Queue savedObjects) { //////////////////////////////////////////////////////////////////////////*/ private void showSystemUi() { - if (DEBUG) Log.d(TAG, "showSystemUi() called"); - if (playerImpl != null && playerImpl.queueVisible) return; + if (DEBUG) { + Log.d(TAG, "showSystemUi() called"); + } + if (playerImpl != null && playerImpl.queueVisible) { + return; + } final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN @@ -344,7 +369,9 @@ private void showSystemUi() { } private void hideSystemUi() { - if (DEBUG) Log.d(TAG, "hideSystemUi() called"); + if (DEBUG) { + Log.d(TAG, "hideSystemUi() called"); + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN @@ -368,10 +395,11 @@ private void toggleOrientation() { } private boolean isLandscape() { - return getResources().getDisplayMetrics().heightPixels < getResources().getDisplayMetrics().widthPixels; + return getResources().getDisplayMetrics().heightPixels + < getResources().getDisplayMetrics().widthPixels; } - private void setLandscape(boolean v) { + private void setLandscape(final boolean v) { setRequestedOrientation(v ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); @@ -380,7 +408,8 @@ private void setLandscape(boolean v) { private boolean globalScreenOrientationLocked() { // 1: Screen orientation changes using accelerometer // 0: Screen orientation is locked - return !(android.provider.Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1); + return !(android.provider.Settings.System + .getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1); } protected void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) { @@ -403,8 +432,8 @@ protected void setShuffleButton(final ImageButton shuffleButton, final boolean s } protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) { - muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), - isMuted ? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp)); + muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), isMuted + ? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp)); } @@ -417,8 +446,8 @@ private boolean isInMultiWindow() { //////////////////////////////////////////////////////////////////////////// @Override - public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, - boolean playbackSkipSilence) { + public void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch, + final boolean playbackSkipSilence) { if (playerImpl != null) { playerImpl.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence); } @@ -428,7 +457,7 @@ public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, @SuppressWarnings({"unused", "WeakerAccess"}) private class VideoPlayerImpl extends VideoPlayer { - private final float MAX_GESTURE_LENGTH = 0.75f; + private static final float MAX_GESTURE_LENGTH = 0.75f; private TextView titleTextView; private TextView channelTextView; @@ -472,33 +501,33 @@ private class VideoPlayerImpl extends VideoPlayer { } @Override - public void initViews(View rootView) { - super.initViews(rootView); - this.titleTextView = rootView.findViewById(R.id.titleTextView); - this.channelTextView = rootView.findViewById(R.id.channelTextView); - this.volumeRelativeLayout = rootView.findViewById(R.id.volumeRelativeLayout); - this.volumeProgressBar = rootView.findViewById(R.id.volumeProgressBar); - this.volumeImageView = rootView.findViewById(R.id.volumeImageView); - this.brightnessRelativeLayout = rootView.findViewById(R.id.brightnessRelativeLayout); - this.brightnessProgressBar = rootView.findViewById(R.id.brightnessProgressBar); - this.brightnessImageView = rootView.findViewById(R.id.brightnessImageView); - this.queueButton = rootView.findViewById(R.id.queueButton); - this.repeatButton = rootView.findViewById(R.id.repeatButton); - this.shuffleButton = rootView.findViewById(R.id.shuffleButton); - - this.playPauseButton = rootView.findViewById(R.id.playPauseButton); - this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton); - this.playNextButton = rootView.findViewById(R.id.playNextButton); - this.closeButton = rootView.findViewById(R.id.closeButton); - - this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton); - this.secondaryControls = rootView.findViewById(R.id.secondaryControls); - this.kodiButton = rootView.findViewById(R.id.kodi); - this.shareButton = rootView.findViewById(R.id.share); - this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation); - this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground); - this.muteButton = rootView.findViewById(R.id.switchMute); - this.switchPopupButton = rootView.findViewById(R.id.switchPopup); + public void initViews(final View view) { + super.initViews(view); + this.titleTextView = view.findViewById(R.id.titleTextView); + this.channelTextView = view.findViewById(R.id.channelTextView); + this.volumeRelativeLayout = view.findViewById(R.id.volumeRelativeLayout); + this.volumeProgressBar = view.findViewById(R.id.volumeProgressBar); + this.volumeImageView = view.findViewById(R.id.volumeImageView); + this.brightnessRelativeLayout = view.findViewById(R.id.brightnessRelativeLayout); + this.brightnessProgressBar = view.findViewById(R.id.brightnessProgressBar); + this.brightnessImageView = view.findViewById(R.id.brightnessImageView); + this.queueButton = view.findViewById(R.id.queueButton); + this.repeatButton = view.findViewById(R.id.repeatButton); + this.shuffleButton = view.findViewById(R.id.shuffleButton); + + this.playPauseButton = view.findViewById(R.id.playPauseButton); + this.playPreviousButton = view.findViewById(R.id.playPreviousButton); + this.playNextButton = view.findViewById(R.id.playNextButton); + this.closeButton = view.findViewById(R.id.closeButton); + + this.moreOptionsButton = view.findViewById(R.id.moreOptionsButton); + this.secondaryControls = view.findViewById(R.id.secondaryControls); + this.kodiButton = view.findViewById(R.id.kodi); + this.shareButton = view.findViewById(R.id.share); + this.toggleOrientationButton = view.findViewById(R.id.toggleOrientation); + this.switchBackgroundButton = view.findViewById(R.id.switchBackground); + this.muteButton = view.findViewById(R.id.switchMute); + this.switchPopupButton = view.findViewById(R.id.switchPopup); this.queueLayout = findViewById(R.id.playQueuePanel); this.itemsListCloseButton = findViewById(R.id.playQueueClose); @@ -506,15 +535,15 @@ public void initViews(View rootView) { titleTextView.setSelected(true); channelTextView.setSelected(true); - boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context).getBoolean( - this.context.getString(R.string.show_play_with_kodi_key), false); + boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context) + .getBoolean(this.context.getString(R.string.show_play_with_kodi_key), false); kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE); getRootView().setKeepScreenOn(true); } @Override - protected void setupSubtitleView(@NonNull SubtitleView view, + protected void setupSubtitleView(@NonNull final SubtitleView view, final float captionScale, @NonNull final CaptionStyleCompat captionStyle) { final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); @@ -556,10 +585,13 @@ public void initListeners() { if (l != ol || t != ot || r != or || b != ob) { // Use smaller value to be consistent between screen orientations // (and to make usage easier) - int width = r - l, height = b - t; + int width = r - l; + int height = b - t; maxGestureLength = (int) (Math.min(width, height) * MAX_GESTURE_LENGTH); - if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength); + if (DEBUG) { + Log.d(TAG, "maxGestureLength = " + maxGestureLength); + } volumeProgressBar.setMax(maxGestureLength); brightnessProgressBar.setMax(maxGestureLength); @@ -571,11 +603,13 @@ public void initListeners() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { queueLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override - public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { + public WindowInsets onApplyWindowInsets(final View view, + final WindowInsets windowInsets) { final DisplayCutout cutout = windowInsets.getDisplayCutout(); - if (cutout != null) + if (cutout != null) { view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), cutout.getSafeInsetRight(), cutout.getSafeInsetBottom()); + } return windowInsets; } }); @@ -602,7 +636,7 @@ public void minimize() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onRepeatModeChanged(int i) { + public void onRepeatModeChanged(final int i) { super.onRepeatModeChanged(i); updatePlaybackButtons(); } @@ -633,10 +667,12 @@ public void onPlaybackShutdown() { public void onKodiShare() { onPause(); try { - NavigationHelper.playWithKore(this.context, Uri.parse( - playerImpl.getVideoUrl().replace("https", "http"))); + NavigationHelper.playWithKore(this.context, + Uri.parse(playerImpl.getVideoUrl().replace("https", "http"))); } catch (Exception e) { - if (DEBUG) Log.i(TAG, "Failed to start kore", e); + if (DEBUG) { + Log.i(TAG, "Failed to start kore", e); + } KoreUtil.showInstallKoreDialog(this.context); } } @@ -649,8 +685,12 @@ public void onKodiShare() { public void onFullScreenButtonClicked() { super.onFullScreenButtonClicked(); - if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); - if (simpleExoPlayer == null) return; + if (DEBUG) { + Log.d(TAG, "onFullScreenButtonClicked() called"); + } + if (simpleExoPlayer == null) { + return; + } if (!PermissionHelper.isPopupEnabled(context)) { PermissionHelper.showPopupEnablementToast(context); @@ -679,8 +719,12 @@ public void onFullScreenButtonClicked() { } public void onPlayBackgroundButtonClicked() { - if (DEBUG) Log.d(TAG, "onPlayBackgroundButtonClicked() called"); - if (playerImpl.getPlayer() == null) return; + if (DEBUG) { + Log.d(TAG, "onPlayBackgroundButtonClicked() called"); + } + if (playerImpl.getPlayer() == null) { + return; + } setRecovery(); final Intent intent = NavigationHelper.getPlayerIntent( @@ -711,17 +755,14 @@ public void onMuteUnmuteButtonClicked() { @Override - public void onClick(View v) { + public void onClick(final View v) { super.onClick(v); if (v.getId() == playPauseButton.getId()) { onPlayPause(); - } else if (v.getId() == playPreviousButton.getId()) { onPlayPrevious(); - } else if (v.getId() == playNextButton.getId()) { onPlayNext(); - } else if (v.getId() == queueButton.getId()) { onQueueClicked(); return; @@ -733,22 +774,16 @@ public void onClick(View v) { return; } else if (v.getId() == moreOptionsButton.getId()) { onMoreOptionsClicked(); - } else if (v.getId() == shareButton.getId()) { onShareClicked(); - } else if (v.getId() == toggleOrientationButton.getId()) { onScreenRotationClicked(); - } else if (v.getId() == switchPopupButton.getId()) { onFullScreenButtonClicked(); - } else if (v.getId() == switchBackgroundButton.getId()) { onPlayBackgroundButtonClicked(); - } else if (v.getId() == muteButton.getId()) { onMuteUnmuteButtonClicked(); - } else if (v.getId() == closeButton.getId()) { onPlaybackShutdown(); return; @@ -774,22 +809,23 @@ private void onQueueClicked() { updatePlaybackButtons(); getControlsRoot().setVisibility(View.INVISIBLE); - animateView(queueLayout, SLIDE_AND_ALPHA, /*visible=*/true, - DEFAULT_CONTROLS_DURATION); + animateView(queueLayout, SLIDE_AND_ALPHA, true, DEFAULT_CONTROLS_DURATION); itemsList.scrollToPosition(playQueue.getIndex()); } private void onQueueClosed() { - animateView(queueLayout, SLIDE_AND_ALPHA, /*visible=*/false, - DEFAULT_CONTROLS_DURATION); + animateView(queueLayout, SLIDE_AND_ALPHA, false, DEFAULT_CONTROLS_DURATION); queueVisible = false; } private void onMoreOptionsClicked() { - if (DEBUG) Log.d(TAG, "onMoreOptionsClicked() called"); + if (DEBUG) { + Log.d(TAG, "onMoreOptionsClicked() called"); + } - final boolean isMoreControlsVisible = secondaryControls.getVisibility() == View.VISIBLE; + final boolean isMoreControlsVisible + = secondaryControls.getVisibility() == View.VISIBLE; animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, isMoreControlsVisible ? 0 : 180); @@ -801,13 +837,15 @@ private void onMoreOptionsClicked() { private void onShareClicked() { // share video at the current time (youtube.com/watch?v=ID&t=SECONDS) - ShareUtils.shareUrl(MainVideoPlayer.this, - playerImpl.getVideoTitle(), - playerImpl.getVideoUrl() + "&t=" + String.valueOf(playerImpl.getPlaybackSeekBar().getProgress() / 1000)); + ShareUtils.shareUrl(MainVideoPlayer.this, playerImpl.getVideoTitle(), + playerImpl.getVideoUrl() + + "&t=" + playerImpl.getPlaybackSeekBar().getProgress() / 1000); } private void onScreenRotationClicked() { - if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called"); + if (DEBUG) { + Log.d(TAG, "onScreenRotationClicked() called"); + } toggleOrientation(); showControlsThenHide(); } @@ -820,20 +858,24 @@ public void onPlaybackSpeedClicked() { } @Override - public void onStopTrackingTouch(SeekBar seekBar) { + public void onStopTrackingTouch(final SeekBar seekBar) { super.onStopTrackingTouch(seekBar); - if (wasPlaying()) showControlsThenHide(); + if (wasPlaying()) { + showControlsThenHide(); + } } @Override - public void onDismiss(PopupMenu menu) { + public void onDismiss(final PopupMenu menu) { super.onDismiss(menu); - if (isPlaying()) hideControls(DEFAULT_CONTROLS_DURATION, 0); + if (isPlaying()) { + hideControls(DEFAULT_CONTROLS_DURATION, 0); + } hideSystemUi(); } @Override - protected int nextResizeMode(int currentResizeMode) { + protected int nextResizeMode(final int currentResizeMode) { final int newResizeMode; switch (currentResizeMode) { case AspectRatioFrameLayout.RESIZE_MODE_FIT: @@ -851,7 +893,7 @@ protected int nextResizeMode(int currentResizeMode) { return newResizeMode; } - private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) { + private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { defaultPreferences.edit() .putInt(getString(R.string.last_resize_mode), resizeMode) .apply(); @@ -861,13 +903,13 @@ private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) protected VideoPlaybackResolver.QualityResolver getQualityResolver() { return new VideoPlaybackResolver.QualityResolver() { @Override - public int getDefaultResolutionIndex(List sortedVideos) { + public int getDefaultResolutionIndex(final List sortedVideos) { return ListHelper.getDefaultResolutionIndex(context, sortedVideos); } @Override - public int getOverrideResolutionIndex(List sortedVideos, - String playbackQuality) { + public int getOverrideResolutionIndex(final List sortedVideos, + final String playbackQuality) { return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality); } }; @@ -948,40 +990,52 @@ public void onCompleted() { private void setInitialGestureValues() { if (getAudioReactor() != null) { - final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume(); - volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized)); + final float currentVolumeNormalized + = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume(); + volumeProgressBar.setProgress( + (int) (volumeProgressBar.getMax() * currentVolumeNormalized)); } float screenBrightness = getWindow().getAttributes().screenBrightness; - if (screenBrightness < 0) + if (screenBrightness < 0) { screenBrightness = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 0) / 255.0f; + } - brightnessProgressBar.setProgress((int) (brightnessProgressBar.getMax() * screenBrightness)); + brightnessProgressBar.setProgress( + (int) (brightnessProgressBar.getMax() * screenBrightness)); - if (DEBUG) Log.d(TAG, "setInitialGestureValues: volumeProgressBar.getProgress() [" - + volumeProgressBar.getProgress() + "] " - + "brightnessProgressBar.getProgress() [" - + brightnessProgressBar.getProgress() + "]"); + if (DEBUG) { + Log.d(TAG, "setInitialGestureValues: volumeProgressBar.getProgress() [" + + volumeProgressBar.getProgress() + "] " + + "brightnessProgressBar.getProgress() [" + + brightnessProgressBar.getProgress() + "]"); + } } @Override public void showControlsThenHide() { - if (queueVisible) return; + if (queueVisible) { + return; + } super.showControlsThenHide(); } @Override - public void showControls(long duration) { - if (queueVisible) return; + public void showControls(final long duration) { + if (queueVisible) { + return; + } super.showControls(duration); } @Override - public void hideControls(final long duration, long delay) { - if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + public void hideControls(final long duration, final long delay) { + if (DEBUG) { + Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + } getControlsVisibilityHandler().removeCallbacksAndMessages(null); getControlsVisibilityHandler().postDelayed(() -> animateView(getControlsRoot(), false, duration, 0, @@ -991,8 +1045,10 @@ public void hideControls(final long duration, long delay) { } private void updatePlaybackButtons() { - if (repeatButton == null || shuffleButton == null || - simpleExoPlayer == null || playQueue == null) return; + if (repeatButton == null || shuffleButton == null + || simpleExoPlayer == null || playQueue == null) { + return; + } setRepeatModeButton(repeatButton, getRepeatMode()); setShuffleButton(shuffleButton, playQueue.isShuffled()); @@ -1017,7 +1073,7 @@ private void buildQueue() { private OnScrollBelowItemsListener getQueueScrollListener() { return new OnScrollBelowItemsListener() { @Override - public void onScrolledDown(RecyclerView recyclerView) { + public void onScrolledDown(final RecyclerView recyclerView) { if (playQueue != null && !playQueue.isComplete()) { playQueue.fetch(); } else if (itemsList != null) { @@ -1030,13 +1086,17 @@ public void onScrolledDown(RecyclerView recyclerView) { private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new PlayQueueItemTouchCallback() { @Override - public void onMove(int sourceIndex, int targetIndex) { - if (playQueue != null) playQueue.move(sourceIndex, targetIndex); + public void onMove(final int sourceIndex, final int targetIndex) { + if (playQueue != null) { + playQueue.move(sourceIndex, targetIndex); + } } @Override - public void onSwiped(int index) { - if (index != -1) playQueue.remove(index); + public void onSwiped(final int index) { + if (index != -1) { + playQueue.remove(index); + } } }; } @@ -1044,19 +1104,23 @@ public void onSwiped(int index) { private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { return new PlayQueueItemBuilder.OnSelectedListener() { @Override - public void selected(PlayQueueItem item, View view) { + public void selected(final PlayQueueItem item, final View view) { onSelected(item); } @Override - public void held(PlayQueueItem item, View view) { + public void held(final PlayQueueItem item, final View view) { final int index = playQueue.indexOf(item); - if (index != -1) playQueue.remove(index); + if (index != -1) { + playQueue.remove(index); + } } @Override - public void onStartDrag(PlayQueueItemHolder viewHolder) { - if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder); + public void onStartDrag(final PlayQueueItemHolder viewHolder) { + if (itemTouchHelper != null) { + itemTouchHelper.startDrag(viewHolder); + } } }; } @@ -1114,13 +1178,24 @@ public int getMaxGestureLength() { } } - private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { + private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener + implements View.OnTouchListener { + private static final int MOVEMENT_THRESHOLD = 40; + private final boolean isVolumeGestureEnabled = PlayerHelper + .isVolumeGestureEnabled(getApplicationContext()); + private final boolean isBrightnessGestureEnabled = PlayerHelper + .isBrightnessGestureEnabled(getApplicationContext()); + private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume(); private boolean isMoving; @Override - public boolean onDoubleTap(MotionEvent e) { - if (DEBUG) - Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); + public boolean onDoubleTap(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onDoubleTap() called with: " + + "e = [" + e + "], " + + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", " + + "xy = " + e.getX() + ", " + e.getY()); + } if (e.getX() > playerImpl.getRootView().getWidth() * 2 / 3) { playerImpl.onFastForward(); @@ -1134,9 +1209,13 @@ public boolean onDoubleTap(MotionEvent e) { } @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); - if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) return true; + public boolean onSingleTapConfirmed(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); + } + if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) { + return true; + } if (playerImpl.isControlsVisible()) { playerImpl.hideControls(150, 0); @@ -1148,30 +1227,32 @@ public boolean onSingleTapConfirmed(MotionEvent e) { } @Override - public boolean onDown(MotionEvent e) { - if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]"); + public boolean onDown(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onDown() called with: e = [" + e + "]"); + } return super.onDown(e); } - private static final int MOVEMENT_THRESHOLD = 40; - - private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext()); - private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext()); - - private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume(); - @Override - public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) { - if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false; - - //noinspection PointlessBooleanExpression - if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " + - ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + - ", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + - ", distanceXy = [" + distanceX + ", " + distanceY + "]"); + public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent, + final float distanceX, final float distanceY) { + if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) { + return false; + } - final boolean insideThreshold = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD; +// if (DEBUG) { +// Log.d(TAG, "MainVideoPlayer.onScroll = " + +// "e1.getRaw = [" + initialEvent.getRawX() + ", " +// + initialEvent.getRawY() + "], " + +// "e2.getRaw = [" + movingEvent.getRawX() + ", " +// + movingEvent.getRawY() + "], " + +// "distanceXy = [" + distanceX + ", " + distanceY + "]"); +// } + + final boolean insideThreshold + = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD; if (!isMoving && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY)) || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) { return false; @@ -1180,23 +1261,29 @@ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float isMoving = true; boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled; - boolean acceptVolumeArea = acceptAnyArea || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2; + boolean acceptVolumeArea = acceptAnyArea + || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2; boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea; if (isVolumeGestureEnabled && acceptVolumeArea) { playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY); float currentProgressPercent = - (float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength(); + (float) playerImpl.getVolumeProgressBar().getProgress() + / playerImpl.getMaxGestureLength(); int currentVolume = (int) (maxVolume * currentProgressPercent); playerImpl.getAudioReactor().setVolume(currentVolume); - if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); + if (DEBUG) { + Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); + } - final int resId = - currentProgressPercent <= 0 ? R.drawable.ic_volume_off_white_72dp - : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp - : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp - : R.drawable.ic_volume_up_white_72dp; + final int resId = currentProgressPercent <= 0 + ? R.drawable.ic_volume_off_white_72dp + : currentProgressPercent < 0.25 + ? R.drawable.ic_volume_mute_white_72dp + : currentProgressPercent < 0.75 + ? R.drawable.ic_volume_down_white_72dp + : R.drawable.ic_volume_up_white_72dp; playerImpl.getVolumeImageView().setImageDrawable( AppCompatResources.getDrawable(getApplicationContext(), resId) @@ -1210,18 +1297,22 @@ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float } } else if (isBrightnessGestureEnabled && acceptBrightnessArea) { playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY); - float currentProgressPercent = - (float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength(); + float currentProgressPercent + = (float) playerImpl.getBrightnessProgressBar().getProgress() + / playerImpl.getMaxGestureLength(); WindowManager.LayoutParams layoutParams = getWindow().getAttributes(); layoutParams.screenBrightness = currentProgressPercent; getWindow().setAttributes(layoutParams); - if (DEBUG) - Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentProgressPercent); + if (DEBUG) { + Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + + currentProgressPercent); + } - final int resId = - currentProgressPercent < 0.25 ? R.drawable.ic_brightness_low_white_72dp - : currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp + final int resId = currentProgressPercent < 0.25 + ? R.drawable.ic_brightness_low_white_72dp + : currentProgressPercent < 0.75 + ? R.drawable.ic_brightness_medium_white_72dp : R.drawable.ic_brightness_high_white_72dp; playerImpl.getBrightnessImageView().setImageDrawable( @@ -1229,7 +1320,8 @@ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float ); if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) { - animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200); + animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, + 200); } if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE); @@ -1239,13 +1331,17 @@ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float } private void onScrollEnd() { - if (DEBUG) Log.d(TAG, "onScrollEnd() called"); + if (DEBUG) { + Log.d(TAG, "onScrollEnd() called"); + } if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200); + animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, + 200, 200); } if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200); + animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, + 200, 200); } if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { @@ -1254,10 +1350,10 @@ private void onScrollEnd() { } @Override - public boolean onTouch(View v, MotionEvent event) { - //noinspection PointlessBooleanExpression - if (DEBUG && false) - Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]"); + public boolean onTouch(final View v, final MotionEvent event) { +// if (DEBUG) { +// Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]"); +// } gestureDetector.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP && isMoving) { isMoving = false; diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java index ef9d92aa0..e8bd7dc85 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.player; import android.os.Binder; + import androidx.annotation.NonNull; class PlayerServiceBinder extends Binder { diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java index 308e8100e..af875a32b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java @@ -9,11 +9,13 @@ public class PlayerState implements Serializable { - @NonNull private final PlayQueue playQueue; + @NonNull + private final PlayQueue playQueue; private final int repeatMode; private final float playbackSpeed; private final float playbackPitch; - @Nullable private final String playbackQuality; + @Nullable + private final String playbackQuality; private final boolean playbackSkipSilence; private final boolean wasPlaying; diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index b7638eda7..008aaaf9b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -35,9 +35,6 @@ import android.os.Build; import android.os.IBinder; import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import androidx.core.app.NotificationCompat; import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; @@ -54,12 +51,16 @@ import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.nostra13.universalimageloader.core.assist.FailReason; import org.schabi.newpipe.BuildConfig; @@ -83,29 +84,28 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; /** - * Service Popup Player implementing VideoPlayer + * Service Popup Player implementing {@link VideoPlayer}. * * @author mauriciocolli */ public final class PopupVideoPlayer extends Service { + public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE"; + public static final String ACTION_PLAY_PAUSE + = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT"; private static final String TAG = ".PopupVideoPlayer"; private static final boolean DEBUG = BasePlayer.DEBUG; - private static final int NOTIFICATION_ID = 40028922; - public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE"; - public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT"; - private static final String POPUP_SAVED_WIDTH = "popup_saved_width"; private static final String POPUP_SAVED_X = "popup_saved_x"; private static final String POPUP_SAVED_Y = "popup_saved_y"; private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300; - private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS | - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; private WindowManager windowManager; private WindowManager.LayoutParams popupLayoutParams; @@ -116,11 +116,15 @@ public final class PopupVideoPlayer extends Service { private int tossFlingVelocity; - private float screenWidth, screenHeight; - private float popupWidth, popupHeight; + private float screenWidth; + private float screenHeight; + private float popupWidth; + private float popupHeight; - private float minimumWidth, minimumHeight; - private float maximumWidth, maximumHeight; + private float minimumWidth; + private float minimumHeight; + private float maximumWidth; + private float maximumHeight; private NotificationManager notificationManager; private NotificationCompat.Builder notBuilder; @@ -155,14 +159,18 @@ public void onCreate() { } @Override - public int onStartCommand(final Intent intent, int flags, int startId) { - if (DEBUG) - Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]"); + public int onStartCommand(final Intent intent, final int flags, final int startId) { + if (DEBUG) { + Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], " + + "flags = [" + flags + "], startId = [" + startId + "]"); + } if (playerImpl.getPlayer() == null) { initPopup(); initPopupCloseOverlay(); } - if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true); + if (!playerImpl.isPlaying()) { + playerImpl.getPlayer().setPlayWhenReady(true); + } playerImpl.handleIntent(intent); @@ -170,9 +178,12 @@ public int onStartCommand(final Intent intent, int flags, int startId) { } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(final Configuration newConfig) { assureCorrectAppLanguage(this); - if (DEBUG) Log.d(TAG, "onConfigurationChanged() called with: newConfig = [" + newConfig + "]"); + if (DEBUG) { + Log.d(TAG, "onConfigurationChanged() called with: " + + "newConfig = [" + newConfig + "]"); + } updateScreenSize(); updatePopupSize(popupLayoutParams.width, -1); checkPopupPositionBounds(); @@ -180,17 +191,19 @@ public void onConfigurationChanged(Configuration newConfig) { @Override public void onDestroy() { - if (DEBUG) Log.d(TAG, "onDestroy() called"); + if (DEBUG) { + Log.d(TAG, "onDestroy() called"); + } closePopup(); } @Override - protected void attachBaseContext(Context base) { + protected void attachBaseContext(final Context base) { super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base)); } @Override - public IBinder onBind(Intent intent) { + public IBinder onBind(final Intent intent) { return mBinder; } @@ -200,7 +213,9 @@ public IBinder onBind(Intent intent) { @SuppressLint("RtlHardcoded") private void initPopup() { - if (DEBUG) Log.d(TAG, "initPopup() called"); + if (DEBUG) { + Log.d(TAG, "initPopup() called"); + } View rootView = View.inflate(this, R.layout.player_popup, null); playerImpl.setup(rootView); @@ -211,11 +226,12 @@ private void initPopup() { final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this); final float defaultSize = getResources().getDimension(R.dimen.popup_default_width); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize; + popupWidth = popupRememberSizeAndPos + ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize; - final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? - WindowManager.LayoutParams.TYPE_PHONE : - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O + ? WindowManager.LayoutParams.TYPE_PHONE + : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; popupLayoutParams = new WindowManager.LayoutParams( (int) popupWidth, (int) getMinimumVideoHeight(popupWidth), @@ -227,8 +243,10 @@ private void initPopup() { int centerX = (int) (screenWidth / 2f - popupWidth / 2f); int centerY = (int) (screenHeight / 2f - popupHeight / 2f); - popupLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX; - popupLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY; + popupLayoutParams.x = popupRememberSizeAndPos + ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX; + popupLayoutParams.y = popupRememberSizeAndPos + ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY; checkPopupPositionBounds(); @@ -243,14 +261,17 @@ private void initPopup() { @SuppressLint("RtlHardcoded") private void initPopupCloseOverlay() { - if (DEBUG) Log.d(TAG, "initPopupCloseOverlay() called"); + if (DEBUG) { + Log.d(TAG, "initPopupCloseOverlay() called"); + } closeOverlayView = View.inflate(this, R.layout.player_popup_close_overlay, null); closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton); - final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? - WindowManager.LayoutParams.TYPE_PHONE : - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O + ? WindowManager.LayoutParams.TYPE_PHONE + : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( @@ -259,7 +280,8 @@ private void initPopupCloseOverlay() { flags, PixelFormat.TRANSLUCENT); closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + closeOverlayLayoutParams.softInputMode = WindowManager + .LayoutParams.SOFT_INPUT_ADJUST_RESIZE; closeOverlayButton.setVisibility(View.GONE); windowManager.addView(closeOverlayView, closeOverlayLayoutParams); @@ -274,27 +296,33 @@ private void resetNotification() { } private NotificationCompat.Builder createNotification() { - notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification); + notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, + R.layout.player_popup_notification); notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle()); notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName()); notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail()); notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), + PendingIntent.FLAG_UPDATE_CURRENT)); notRemoteView.setOnClickPendingIntent(R.id.notificationStop, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), + PendingIntent.FLAG_UPDATE_CURRENT)); notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), + PendingIntent.FLAG_UPDATE_CURRENT)); // Starts popup player activity -- attempts to unlock lockscreen final Intent intent = NavigationHelper.getPopupPlayerActivityIntent(this); notRemoteView.setOnClickPendingIntent(R.id.notificationContent, - PendingIntent.getActivity(this, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getActivity(this, NOTIFICATION_ID, intent, + PendingIntent.FLAG_UPDATE_CURRENT)); setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode()); - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) + NotificationCompat.Builder builder = new NotificationCompat + .Builder(this, getString(R.string.notification_channel_id)) .setOngoing(true) .setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -311,10 +339,16 @@ private NotificationCompat.Builder createNotification() { * * @param drawableId if != -1, sets the drawable with that id on the play/pause button */ - private void updateNotification(int drawableId) { - if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]"); - if (notBuilder == null || notRemoteView == null) return; - if (drawableId != -1) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId); + private void updateNotification(final int drawableId) { + if (DEBUG) { + Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]"); + } + if (notBuilder == null || notRemoteView == null) { + return; + } + if (drawableId != -1) { + notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId); + } notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); } @@ -323,8 +357,12 @@ private void updateNotification(int drawableId) { //////////////////////////////////////////////////////////////////////////*/ public void closePopup() { - if (DEBUG) Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); - if (isPopupClosing) return; + if (DEBUG) { + Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); + } + if (isPopupClosing) { + return; + } isPopupClosing = true; if (playerImpl != null) { @@ -339,14 +377,19 @@ public void closePopup() { } mBinder = null; - if (lockManager != null) lockManager.releaseWifiAndCpu(); - if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); + if (lockManager != null) { + lockManager.releaseWifiAndCpu(); + } + if (notificationManager != null) { + notificationManager.cancel(NOTIFICATION_ID); + } animateOverlayAndFinishService(); } private void animateOverlayAndFinishService() { - final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY()); + final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() + - closeOverlayButton.getY()); closeOverlayButton.animate().setListener(null).cancel(); closeOverlayButton.animate() @@ -355,12 +398,12 @@ private void animateOverlayAndFinishService() { .setDuration(400) .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(final Animator animation) { end(); } @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { end(); } @@ -379,6 +422,7 @@ private void end() { /** * @see #checkPopupPositionBounds(float, float) + * @return if the popup was out of bounds and have been moved back to it */ @SuppressWarnings("UnusedReturnValue") private boolean checkPopupPositionBounds() { @@ -386,16 +430,23 @@ private boolean checkPopupPositionBounds() { } /** - * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary that goes from (0,0) to (boundaryWidth,boundaryHeight). + * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary + * that goes from (0, 0) to (boundaryWidth, boundaryHeight). *

- * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed and {@code true} is returned - * to represent this change. + * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed + * and {@code true} is returned to represent this change. + *

* + * @param boundaryWidth width of the boundary + * @param boundaryHeight height of the boundary * @return if the popup was out of bounds and have been moved back to it */ - private boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) { + private boolean checkPopupPositionBounds(final float boundaryWidth, + final float boundaryHeight) { if (DEBUG) { - Log.d(TAG, "checkPopupPositionBounds() called with: boundaryWidth = [" + boundaryWidth + "], boundaryHeight = [" + boundaryHeight + "]"); + Log.d(TAG, "checkPopupPositionBounds() called with: " + + "boundaryWidth = [" + boundaryWidth + "], " + + "boundaryHeight = [" + boundaryHeight + "]"); } if (popupLayoutParams.x < 0) { @@ -418,15 +469,20 @@ private boolean checkPopupPositionBounds(final float boundaryWidth, final float } private void savePositionAndSize() { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this); + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(PopupVideoPlayer.this); sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply(); sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply(); sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply(); } - private float getMinimumVideoHeight(float width) { - //if (DEBUG) Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], returned: " + height); - return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have + private float getMinimumVideoHeight(final float width) { + final float height = width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have +// if (DEBUG) { +// Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], " +// + "returned: " + height); +// } + return height; } private void updateScreenSize() { @@ -435,7 +491,10 @@ private void updateScreenSize() { screenWidth = metrics.widthPixels; screenHeight = metrics.heightPixels; - if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight); + if (DEBUG) { + Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", " + + "screenHeight = " + screenHeight); + } popupWidth = getResources().getDimension(R.dimen.popup_default_width); popupHeight = getMinimumVideoHeight(popupWidth); @@ -447,44 +506,65 @@ private void updateScreenSize() { maximumHeight = screenHeight; } - private void updatePopupSize(int width, int height) { - if (playerImpl == null) return; - if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]"); + private void updatePopupSize(final int width, final int height) { + if (playerImpl == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "updatePopupSize() called with: " + + "width = [" + width + "], height = [" + height + "]"); + } - width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width); + final int actualWidth = (int) (width > maximumWidth ? maximumWidth + : width < minimumWidth ? minimumWidth : width); - if (height == -1) height = (int) getMinimumVideoHeight(width); - else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height); + final int actualHeight; + if (height == -1) { + actualHeight = (int) getMinimumVideoHeight(width); + } else { + actualHeight = (int) (height > maximumHeight ? maximumHeight + : height < minimumHeight ? minimumHeight : height); + } - popupLayoutParams.width = width; - popupLayoutParams.height = height; - popupWidth = width; - popupHeight = height; + popupLayoutParams.width = actualWidth; + popupLayoutParams.height = actualHeight; + popupWidth = actualWidth; + popupHeight = actualHeight; - if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]"); + if (DEBUG) { + Log.d(TAG, "updatePopupSize() updated values: " + + "width = [" + actualWidth + "], height = [" + actualHeight + "]"); + } windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); } protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) { final String methodName = "setImageResource"; - if (remoteViews == null) return; + if (remoteViews == null) { + return; + } switch (repeatMode) { case Player.REPEAT_MODE_OFF: - remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off); + remoteViews.setInt(R.id.notificationRepeat, methodName, + R.drawable.exo_controls_repeat_off); break; case Player.REPEAT_MODE_ONE: - remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one); + remoteViews.setInt(R.id.notificationRepeat, methodName, + R.drawable.exo_controls_repeat_one); break; case Player.REPEAT_MODE_ALL: - remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all); + remoteViews.setInt(R.id.notificationRepeat, methodName, + R.drawable.exo_controls_repeat_all); break; } } private void updateWindowFlags(final int flags) { - if (popupLayoutParams == null || windowManager == null || playerImpl == null) return; + if (popupLayoutParams == null || windowManager == null || playerImpl == null) { + return; + } popupLayoutParams.flags = flags; windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); @@ -499,29 +579,29 @@ protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChan private View extraOptionsView; private View closingOverlayView; + VideoPlayerImpl(final Context context) { + super("VideoPlayerImpl" + PopupVideoPlayer.TAG, context); + } + @Override - public void handleIntent(Intent intent) { + public void handleIntent(final Intent intent) { super.handleIntent(intent); resetNotification(); startForeground(NOTIFICATION_ID, notBuilder.build()); } - VideoPlayerImpl(final Context context) { - super("VideoPlayerImpl" + PopupVideoPlayer.TAG, context); - } - @Override - public void initViews(View rootView) { - super.initViews(rootView); - resizingIndicator = rootView.findViewById(R.id.resizing_indicator); - fullScreenButton = rootView.findViewById(R.id.fullScreenButton); + public void initViews(final View view) { + super.initViews(view); + resizingIndicator = view.findViewById(R.id.resizing_indicator); + fullScreenButton = view.findViewById(R.id.fullScreenButton); fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked()); - videoPlayPause = rootView.findViewById(R.id.videoPlayPause); + videoPlayPause = view.findViewById(R.id.videoPlayPause); - extraOptionsView = rootView.findViewById(R.id.extraOptionsView); - closingOverlayView = rootView.findViewById(R.id.closingOverlay); - rootView.addOnLayoutChangeListener(this); + extraOptionsView = view.findViewById(R.id.extraOptionsView); + closingOverlayView = view.findViewById(R.id.closingOverlay); + view.addOnLayoutChangeListener(this); } @Override @@ -531,8 +611,7 @@ public void initListeners() { } @Override - protected void setupSubtitleView(@NonNull SubtitleView view, - final float captionScale, + protected void setupSubtitleView(@NonNull final SubtitleView view, final float captionScale, @NonNull final CaptionStyleCompat captionStyle) { float captionRatio = (captionScale - 1f) / 5f + 1f; view.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); @@ -541,8 +620,9 @@ protected void setupSubtitleView(@NonNull SubtitleView view, } @Override - public void onLayoutChange(final View view, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { + public void onLayoutChange(final View view, final int left, final int top, final int right, + final int bottom, final int oldLeft, final int oldTop, + final int oldRight, final int oldBottom) { float widthDp = Math.abs(right - left) / getResources().getDisplayMetrics().density; final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP ? View.VISIBLE : View.GONE; extraOptionsView.setVisibility(visibility); @@ -550,7 +630,9 @@ public void onLayoutChange(final View view, int left, int top, int right, int bo @Override public void destroy() { - if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null); + if (notRemoteView != null) { + notRemoteView.setImageViewBitmap(R.id.notificationCover, null); + } super.destroy(); } @@ -558,7 +640,9 @@ public void destroy() { public void onFullScreenButtonClicked() { super.onFullScreenButtonClicked(); - if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); + if (DEBUG) { + Log.d(TAG, "onFullScreenButtonClicked() called"); + } setRecovery(); final Intent intent = NavigationHelper.getPlayerIntent( @@ -580,13 +664,15 @@ public void onFullScreenButtonClicked() { } @Override - public void onDismiss(PopupMenu menu) { + public void onDismiss(final PopupMenu menu) { super.onDismiss(menu); - if (isPlaying()) hideControls(500, 0); + if (isPlaying()) { + hideControls(500, 0); + } } @Override - protected int nextResizeMode(int resizeMode) { + protected int nextResizeMode(final int resizeMode) { if (resizeMode == AspectRatioFrameLayout.RESIZE_MODE_FILL) { return AspectRatioFrameLayout.RESIZE_MODE_FIT; } else { @@ -595,7 +681,7 @@ protected int nextResizeMode(int resizeMode) { } @Override - public void onStopTrackingTouch(SeekBar seekBar) { + public void onStopTrackingTouch(final SeekBar seekBar) { super.onStopTrackingTouch(seekBar); if (wasPlaying()) { hideControls(100, 0); @@ -615,7 +701,8 @@ public void onMuteUnmuteButtonClicked() { } @Override - public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { + public void onUpdateProgress(final int currentProgress, final int duration, + final int bufferPercent) { updateProgress(currentProgress, duration, bufferPercent); super.onUpdateProgress(currentProgress, duration, bufferPercent); } @@ -624,13 +711,13 @@ public void onUpdateProgress(int currentProgress, int duration, int bufferPercen protected VideoPlaybackResolver.QualityResolver getQualityResolver() { return new VideoPlaybackResolver.QualityResolver() { @Override - public int getDefaultResolutionIndex(List sortedVideos) { + public int getDefaultResolutionIndex(final List sortedVideos) { return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos); } @Override - public int getOverrideResolutionIndex(List sortedVideos, - String playbackQuality) { + public int getOverrideResolutionIndex(final List sortedVideos, + final String playbackQuality) { return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality); } @@ -642,9 +729,12 @@ public int getOverrideResolutionIndex(List sortedVideos, //////////////////////////////////////////////////////////////////////////*/ @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + public void onLoadingComplete(final String imageUri, final View view, + final Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); - if (playerImpl == null) return; + if (playerImpl == null) { + return; + } // rebuild notification here since remote view does not release bitmaps, // causing memory leaks resetNotification(); @@ -652,14 +742,15 @@ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { } @Override - public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + public void onLoadingFailed(final String imageUri, final View view, + final FailReason failReason) { super.onLoadingFailed(imageUri, view, failReason); resetNotification(); updateNotification(-1); } @Override - public void onLoadingCancelled(String imageUri, View view) { + public void onLoadingCancelled(final String imageUri, final View view) { super.onLoadingCancelled(imageUri, view); resetNotification(); updateNotification(-1); @@ -669,14 +760,14 @@ public void onLoadingCancelled(String imageUri, View view) { // Activity Event Listener //////////////////////////////////////////////////////////////////////////*/ - /*package-private*/ void setActivityListener(PlayerEventListener listener) { + /*package-private*/ void setActivityListener(final PlayerEventListener listener) { activityListener = listener; updateMetadata(); updatePlayback(); triggerProgressUpdate(); } - /*package-private*/ void removeActivityListener(PlayerEventListener listener) { + /*package-private*/ void removeActivityListener(final PlayerEventListener listener) { if (activityListener == listener) { activityListener = null; } @@ -695,7 +786,8 @@ private void updatePlayback() { } } - private void updateProgress(int currentProgress, int duration, int bufferPercent) { + private void updateProgress(final int currentProgress, final int duration, + final int bufferPercent) { if (activityListener != null) { activityListener.onProgressUpdate(currentProgress, duration, bufferPercent); } @@ -713,7 +805,7 @@ private void stopActivityBinding() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onRepeatModeChanged(int i) { + public void onRepeatModeChanged(final int i) { super.onRepeatModeChanged(i); setRepeatModeRemote(notRemoteView, i); updatePlayback(); @@ -722,7 +814,7 @@ public void onRepeatModeChanged(int i) { } @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) { super.onPlaybackParametersChanged(playbackParameters); updatePlayback(); } @@ -749,22 +841,29 @@ public void onPlaybackShutdown() { //////////////////////////////////////////////////////////////////////////*/ @Override - protected void setupBroadcastReceiver(IntentFilter intentFilter) { - super.setupBroadcastReceiver(intentFilter); - if (DEBUG) Log.d(TAG, "setupBroadcastReceiver() called with: intentFilter = [" + intentFilter + "]"); - intentFilter.addAction(ACTION_CLOSE); - intentFilter.addAction(ACTION_PLAY_PAUSE); - intentFilter.addAction(ACTION_REPEAT); + protected void setupBroadcastReceiver(final IntentFilter intentFltr) { + super.setupBroadcastReceiver(intentFltr); + if (DEBUG) { + Log.d(TAG, "setupBroadcastReceiver() called with: " + + "intentFilter = [" + intentFltr + "]"); + } + intentFltr.addAction(ACTION_CLOSE); + intentFltr.addAction(ACTION_PLAY_PAUSE); + intentFltr.addAction(ACTION_REPEAT); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFltr.addAction(Intent.ACTION_SCREEN_ON); + intentFltr.addAction(Intent.ACTION_SCREEN_OFF); } @Override - public void onBroadcastReceived(Intent intent) { + public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); - if (intent == null || intent.getAction() == null) return; - if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]"); + if (intent == null || intent.getAction() == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]"); + } switch (intent.getAction()) { case ACTION_CLOSE: closePopup(); @@ -789,7 +888,7 @@ public void onBroadcastReceived(Intent intent) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void changeState(int state) { + public void changeState(final int state) { super.changeState(state); updatePlayback(); } @@ -869,12 +968,12 @@ public void showControlsThenHide() { super.showControlsThenHide(); } - public void showControls(long duration) { + public void showControls(final long duration) { videoPlayPause.setVisibility(View.VISIBLE); super.showControls(duration); } - public void hideControls(final long duration, long delay) { + public void hideControls(final long duration, final long delay) { super.hideControlsAndButton(duration, delay, videoPlayPause); } @@ -904,16 +1003,23 @@ public View getClosingOverlayView() { } } - private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { - private int initialPopupX, initialPopupY; + private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener + implements View.OnTouchListener { + private int initialPopupX; + private int initialPopupY; private boolean isMoving; private boolean isResizing; @Override - public boolean onDoubleTap(MotionEvent e) { - if (DEBUG) - Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); - if (playerImpl == null || !playerImpl.isPlaying()) return false; + public boolean onDoubleTap(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onDoubleTap() called with: e = [" + e + "], " + + "rawXy = " + e.getRawX() + ", " + e.getRawY() + + ", xy = " + e.getX() + ", " + e.getY()); + } + if (playerImpl == null || !playerImpl.isPlaying()) { + return false; + } playerImpl.hideControls(0, 0); @@ -927,9 +1033,13 @@ public boolean onDoubleTap(MotionEvent e) { } @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); - if (playerImpl == null || playerImpl.getPlayer() == null) return false; + public boolean onSingleTapConfirmed(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); + } + if (playerImpl == null || playerImpl.getPlayer() == null) { + return false; + } if (playerImpl.isControlsVisible()) { playerImpl.hideControls(100, 100); } else { @@ -940,8 +1050,10 @@ public boolean onSingleTapConfirmed(MotionEvent e) { } @Override - public boolean onDown(MotionEvent e) { - if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]"); + public boolean onDown(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onDown() called with: e = [" + e + "]"); + } // Fix popup position when the user touch it, it may have the wrong one // because the soft input is visible (the draggable area is currently resized). @@ -955,16 +1067,21 @@ public boolean onDown(MotionEvent e) { } @Override - public void onLongPress(MotionEvent e) { - if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]"); + public void onLongPress(final MotionEvent e) { + if (DEBUG) { + Log.d(TAG, "onLongPress() called with: e = [" + e + "]"); + } updateScreenSize(); checkPopupPositionBounds(); updatePopupSize((int) screenWidth, -1); } @Override - public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) { - if (isResizing || playerImpl == null) return super.onScroll(initialEvent, movingEvent, distanceX, distanceY); + public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent, + final float distanceX, final float distanceY) { + if (isResizing || playerImpl == null) { + return super.onScroll(initialEvent, movingEvent, distanceX, distanceY); + } if (!isMoving) { animateView(closeOverlayButton, true, 200); @@ -972,14 +1089,22 @@ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float isMoving = true; - float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()), posX = (int) (initialPopupX + diffX); - float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()), posY = (int) (initialPopupY + diffY); + float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()); + float posX = (int) (initialPopupX + diffX); + float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()); + float posY = (int) (initialPopupY + diffY); - if (posX > (screenWidth - popupWidth)) posX = (int) (screenWidth - popupWidth); - else if (posX < 0) posX = 0; + if (posX > (screenWidth - popupWidth)) { + posX = (int) (screenWidth - popupWidth); + } else if (posX < 0) { + posX = 0; + } - if (posY > (screenHeight - popupHeight)) posY = (int) (screenHeight - popupHeight); - else if (posY < 0) posY = 0; + if (posY > (screenHeight - popupHeight)) { + posY = (int) (screenHeight - popupHeight); + } else if (posY < 0) { + posY = 0; + } popupLayoutParams.x = (int) posX; popupLayoutParams.y = (int) posY; @@ -995,22 +1120,30 @@ public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float } } - //noinspection PointlessBooleanExpression - if (DEBUG && false) { - Log.d(TAG, "PopupVideoPlayer.onScroll = " + - ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + ", e1.getX,Y = [" + initialEvent.getX() + ", " + initialEvent.getY() + "]" + - ", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + ", e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "]" + - ", distanceX,Y = [" + distanceX + ", " + distanceY + "]" + - ", posX,Y = [" + posX + ", " + posY + "]" + - ", popupW,H = [" + popupWidth + " x " + popupHeight + "]"); - } +// if (DEBUG) { +// Log.d(TAG, "PopupVideoPlayer.onScroll = " +// + "e1.getRaw = [" + initialEvent.getRawX() + ", " +// + initialEvent.getRawY() + "], " +// + "e1.getX,Y = [" + initialEvent.getX() + ", " +// + initialEvent.getY() + "], " +// + "e2.getRaw = [" + movingEvent.getRawX() + ", " +// + movingEvent.getRawY() + "], " +// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], " +// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], " +// + "posX,Y = [" + posX + ", " + posY + "], " +// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]"); +// } windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); return true; } - private void onScrollEnd(MotionEvent event) { - if (DEBUG) Log.d(TAG, "onScrollEnd() called"); - if (playerImpl == null) return; + private void onScrollEnd(final MotionEvent event) { + if (DEBUG) { + Log.d(TAG, "onScrollEnd() called"); + } + if (playerImpl == null) { + return; + } if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); } @@ -1027,15 +1160,24 @@ private void onScrollEnd(MotionEvent event) { } @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (DEBUG) Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]"); - if (playerImpl == null) return false; + public boolean onFling(final MotionEvent e1, final MotionEvent e2, + final float velocityX, final float velocityY) { + if (DEBUG) { + Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]"); + } + if (playerImpl == null) { + return false; + } final float absVelocityX = Math.abs(velocityX); final float absVelocityY = Math.abs(velocityY); if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) { - if (absVelocityX > tossFlingVelocity) popupLayoutParams.x = (int) velocityX; - if (absVelocityY > tossFlingVelocity) popupLayoutParams.y = (int) velocityY; + if (absVelocityX > tossFlingVelocity) { + popupLayoutParams.x = (int) velocityX; + } + if (absVelocityY > tossFlingVelocity) { + popupLayoutParams.y = (int) velocityY; + } checkPopupPositionBounds(); windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); return true; @@ -1044,11 +1186,15 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve } @Override - public boolean onTouch(View v, MotionEvent event) { + public boolean onTouch(final View v, final MotionEvent event) { popupGestureDetector.onTouchEvent(event); - if (playerImpl == null) return false; + if (playerImpl == null) { + return false; + } if (event.getPointerCount() == 2 && !isMoving && !isResizing) { - if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing."); + if (DEBUG) { + Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing."); + } playerImpl.showAndAnimateControl(-1, true); playerImpl.getLoadingPanel().setVisibility(View.GONE); @@ -1059,13 +1205,18 @@ public boolean onTouch(View v, MotionEvent event) { } if (event.getAction() == MotionEvent.ACTION_MOVE && !isMoving && isResizing) { - if (DEBUG) Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); + if (DEBUG) { + Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], " + + "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); + } return handleMultiDrag(event); } if (event.getAction() == MotionEvent.ACTION_UP) { - if (DEBUG) - Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); + if (DEBUG) { + Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], " + + "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); + } if (isMoving) { isMoving = false; onScrollEnd(event); @@ -1087,7 +1238,9 @@ public boolean onTouch(View v, MotionEvent event) { } private boolean handleMultiDrag(final MotionEvent event) { - if (event.getPointerCount() != 2) return false; + if (event.getPointerCount() != 2) { + return false; + } final float firstPointerX = event.getX(0); final float secondPointerX = event.getX(1); @@ -1114,14 +1267,17 @@ private boolean handleMultiDrag(final MotionEvent event) { // Utils //////////////////////////////////////////////////////////////////////////*/ - private int distanceFromCloseButton(MotionEvent popupMotionEvent) { - final int closeOverlayButtonX = closeOverlayButton.getLeft() + closeOverlayButton.getWidth() / 2; - final int closeOverlayButtonY = closeOverlayButton.getTop() + closeOverlayButton.getHeight() / 2; + private int distanceFromCloseButton(final MotionEvent popupMotionEvent) { + final int closeOverlayButtonX = closeOverlayButton.getLeft() + + closeOverlayButton.getWidth() / 2; + final int closeOverlayButtonY = closeOverlayButton.getTop() + + closeOverlayButton.getHeight() / 2; float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); - return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + Math.pow(closeOverlayButtonY - fingerY, 2)); + return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + + Math.pow(closeOverlayButtonY - fingerY, 2)); } private float getClosingRadius() { @@ -1130,7 +1286,7 @@ private float getClosingRadius() { return buttonRadius * 1.2f; } - private boolean isInsideClosingRadius(MotionEvent popupMotionEvent) { + private boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) { return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java index 5000d07e2..efb4176a6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java @@ -46,13 +46,13 @@ public int getPlayerOptionMenuResource() { } @Override - public boolean onPlayerOptionSelected(MenuItem item) { + public boolean onPlayerOptionSelected(final MenuItem item) { if (item.getItemId() == R.id.action_switch_background) { this.player.setRecovery(); getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); getApplicationContext().startService( - getSwitchIntent(BackgroundPlayer.class) - .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()) + getSwitchIntent(BackgroundPlayer.class) + .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()) ); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index aff3586c8..3a33772d6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -52,22 +52,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, PlaybackParameterDialog.Callback { - + private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47; + private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; + protected BasePlayer player; private boolean serviceBound; private ServiceConnection serviceConnection; - - protected BasePlayer player; - - private boolean seeking; - private boolean redraw; //////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////// - - private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47; - - private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; - + private boolean seeking; + private boolean redraw; private View rootView; private RecyclerView itemsList; @@ -119,7 +113,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { assureCorrectAppLanguage(this); super.onCreate(savedInstanceState); ThemeHelper.setTheme(this); @@ -147,16 +141,16 @@ protected void onResume() { } @Override - public boolean onCreateOptionsMenu(Menu menu) { - this.menu = menu; - getMenuInflater().inflate(R.menu.menu_play_queue, menu); - getMenuInflater().inflate(getPlayerOptionMenuResource(), menu); + public boolean onCreateOptionsMenu(final Menu m) { + this.menu = m; + getMenuInflater().inflate(R.menu.menu_play_queue, m); + getMenuInflater().inflate(getPlayerOptionMenuResource(), m); onMaybeMuteChanged(); return true; } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); @@ -192,19 +186,11 @@ protected void onDestroy() { } protected Intent getSwitchIntent(final Class clazz) { - return NavigationHelper.getPlayerIntent( - getApplicationContext(), - clazz, - this.player.getPlayQueue(), - this.player.getRepeatMode(), - this.player.getPlaybackSpeed(), - this.player.getPlaybackPitch(), - this.player.getPlaybackSkipSilence(), - null, - false, - false, - this.player.isMuted() - ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz, + this.player.getPlayQueue(), this.player.getRepeatMode(), + this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(), + this.player.getPlaybackSkipSilence(), null, false, false, this.player.isMuted()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()); } @@ -229,8 +215,12 @@ private void unbind() { if (player != null && player.getPlayQueueAdapter() != null) { player.getPlayQueueAdapter().unsetSelectedListener(); } - if (itemsList != null) itemsList.setAdapter(null); - if (itemTouchHelper != null) itemTouchHelper.attachToRecyclerView(null); + if (itemsList != null) { + itemsList.setAdapter(null); + } + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } itemsList = null; itemTouchHelper = null; @@ -241,20 +231,20 @@ private void unbind() { private ServiceConnection getServiceConnection() { return new ServiceConnection() { @Override - public void onServiceDisconnected(ComponentName name) { + public void onServiceDisconnected(final ComponentName name) { Log.d(getTag(), "Player service is disconnected"); } @Override - public void onServiceConnected(ComponentName name, IBinder service) { + public void onServiceConnected(final ComponentName name, final IBinder service) { Log.d(getTag(), "Player service is connected"); if (service instanceof PlayerServiceBinder) { player = ((PlayerServiceBinder) service).getPlayerInstance(); } - if (player == null || player.getPlayQueue() == null || - player.getPlayQueueAdapter() == null || player.getPlayer() == null) { + if (player == null || player.getPlayQueue() == null + || player.getPlayQueueAdapter() == null || player.getPlayer() == null) { unbind(); finish(); } else { @@ -332,39 +322,43 @@ private void buildControls() { } private void buildItemPopupMenu(final PlayQueueItem item, final View view) { - final PopupMenu menu = new PopupMenu(this, view); - final MenuItem remove = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/0, + final PopupMenu popupMenu = new PopupMenu(this, view); + final MenuItem remove = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 0, Menu.NONE, R.string.play_queue_remove); remove.setOnMenuItemClickListener(menuItem -> { - if (player == null) return false; + if (player == null) { + return false; + } final int index = player.getPlayQueue().indexOf(item); - if (index != -1) player.getPlayQueue().remove(index); + if (index != -1) { + player.getPlayQueue().remove(index); + } return true; }); - final MenuItem detail = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/1, + final MenuItem detail = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1, Menu.NONE, R.string.play_queue_stream_detail); detail.setOnMenuItemClickListener(menuItem -> { onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle()); return true; }); - final MenuItem append = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/2, + final MenuItem append = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 2, Menu.NONE, R.string.append_playlist); append.setOnMenuItemClickListener(menuItem -> { openPlaylistAppendDialog(Collections.singletonList(item)); return true; }); - final MenuItem share = menu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, /*pos=*/3, + final MenuItem share = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 3, Menu.NONE, R.string.share); share.setOnMenuItemClickListener(menuItem -> { shareUrl(item.getTitle(), item.getUrl()); return true; }); - menu.show(); + popupMenu.show(); } //////////////////////////////////////////////////////////////////////////// @@ -374,8 +368,9 @@ private void buildItemPopupMenu(final PlayQueueItem item, final View view) { private OnScrollBelowItemsListener getQueueScrollListener() { return new OnScrollBelowItemsListener() { @Override - public void onScrolledDown(RecyclerView recyclerView) { - if (player != null && player.getPlayQueue() != null && !player.getPlayQueue().isComplete()) { + public void onScrolledDown(final RecyclerView recyclerView) { + if (player != null && player.getPlayQueue() != null + && !player.getPlayQueue().isComplete()) { player.getPlayQueue().fetch(); } else if (itemsList != null) { itemsList.clearOnScrollListeners(); @@ -387,13 +382,17 @@ public void onScrolledDown(RecyclerView recyclerView) { private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new PlayQueueItemTouchCallback() { @Override - public void onMove(int sourceIndex, int targetIndex) { - if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex); + public void onMove(final int sourceIndex, final int targetIndex) { + if (player != null) { + player.getPlayQueue().move(sourceIndex, targetIndex); + } } @Override - public void onSwiped(int index) { - if (index != -1) player.getPlayQueue().remove(index); + public void onSwiped(final int index) { + if (index != -1) { + player.getPlayQueue().remove(index); + } } }; } @@ -401,31 +400,42 @@ public void onSwiped(int index) { private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { return new PlayQueueItemBuilder.OnSelectedListener() { @Override - public void selected(PlayQueueItem item, View view) { - if (player != null) player.onSelected(item); + public void selected(final PlayQueueItem item, final View view) { + if (player != null) { + player.onSelected(item); + } } @Override - public void held(PlayQueueItem item, View view) { - if (player == null) return; + public void held(final PlayQueueItem item, final View view) { + if (player == null) { + return; + } final int index = player.getPlayQueue().indexOf(item); - if (index != -1) buildItemPopupMenu(item, view); + if (index != -1) { + buildItemPopupMenu(item, view); + } } @Override - public void onStartDrag(PlayQueueItemHolder viewHolder) { - if (itemTouchHelper != null) itemTouchHelper.startDrag(viewHolder); + public void onStartDrag(final PlayQueueItemHolder viewHolder) { + if (itemTouchHelper != null) { + itemTouchHelper.startDrag(viewHolder); + } } }; } - private void onOpenDetail(int serviceId, String videoUrl, String videoTitle) { + private void onOpenDetail(final int serviceId, final String videoUrl, + final String videoTitle) { NavigationHelper.openVideoDetail(this, serviceId, videoUrl, videoTitle); } private void scrollToSelected() { - if (player == null) return; + if (player == null) { + return; + } final int currentPlayingIndex = player.getPlayQueue().getIndex(); final int currentVisibleIndex; @@ -449,36 +459,29 @@ private void scrollToSelected() { //////////////////////////////////////////////////////////////////////////// @Override - public void onClick(View view) { - if (player == null) return; + public void onClick(final View view) { + if (player == null) { + return; + } if (view.getId() == repeatButton.getId()) { player.onRepeatClicked(); - } else if (view.getId() == backwardButton.getId()) { player.onPlayPrevious(); - } else if (view.getId() == playPauseButton.getId()) { player.onPlayPause(); - } else if (view.getId() == forwardButton.getId()) { player.onPlayNext(); - } else if (view.getId() == shuffleButton.getId()) { player.onShuffleClicked(); - } else if (view.getId() == playbackSpeedButton.getId()) { openPlaybackParameterDialog(); - } else if (view.getId() == playbackPitchButton.getId()) { openPlaybackParameterDialog(); - } else if (view.getId() == metadata.getId()) { scrollToSelected(); - } else if (view.getId() == progressLiveSync.getId()) { player.seekToDefault(); - } } @@ -487,14 +490,16 @@ public void onClick(View view) { //////////////////////////////////////////////////////////////////////////// private void openPlaybackParameterDialog() { - if (player == null) return; + if (player == null) { + return; + } PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag()); } @Override - public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, - boolean playbackSkipSilence) { + public void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch, + final boolean playbackSkipSilence) { if (player != null) { player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence); } @@ -505,7 +510,8 @@ public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, //////////////////////////////////////////////////////////////////////////// @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { if (fromUser) { final String seekTime = Localization.getDurationString(progress / 1000); progressCurrentTime.setText(seekTime); @@ -514,14 +520,16 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override - public void onStartTrackingTouch(SeekBar seekBar) { + public void onStartTrackingTouch(final SeekBar seekBar) { seeking = true; seekDisplay.setVisibility(View.VISIBLE); } @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (player != null) player.seekTo(seekBar.getProgress()); + public void onStopTrackingTouch(final SeekBar seekBar) { + if (player != null) { + player.seekTo(seekBar.getProgress()); + } seekDisplay.setVisibility(View.GONE); seeking = false; } @@ -545,7 +553,7 @@ private void openPlaylistAppendDialog(final List playlist) { // Share //////////////////////////////////////////////////////////////////////////// - private void shareUrl(String subject, String url) { + private void shareUrl(final String subject, final String url) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, subject); @@ -558,7 +566,8 @@ private void shareUrl(String subject, String url) { //////////////////////////////////////////////////////////////////////////// @Override - public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters) { + public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled, + final PlaybackParameters parameters) { onStateChanged(state); onPlayModeChanged(repeatMode, shuffled); onPlaybackParameterChanged(parameters); @@ -567,9 +576,11 @@ public void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, Playba } @Override - public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) { + public void onProgressUpdate(final int currentProgress, final int duration, + final int bufferPercent) { // Set buffer progress - progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax() * ((float) bufferPercent / 100))); + progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax() + * ((float) bufferPercent / 100))); // Set Duration progressSeekBar.setMax(duration); @@ -593,7 +604,7 @@ public void onProgressUpdate(int currentProgress, int duration, int bufferPercen } @Override - public void onMetadataUpdate(StreamInfo info) { + public void onMetadataUpdate(final StreamInfo info) { if (info != null) { metadataTitle.setText(info.getName()); metadataArtist.setText(info.getUploaderName()); @@ -680,7 +691,9 @@ private void onPlaybackParameterChanged(final PlaybackParameters parameters) { } private void onMaybePlaybackAdapterChanged() { - if (itemsList == null || player == null) return; + if (itemsList == null || player == null) { + return; + } final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter(); if (maybeNewAdapter != null && itemsList.getAdapter() != maybeNewAdapter) { itemsList.setAdapter(maybeNewAdapter); @@ -698,8 +711,10 @@ private void onMaybeMuteChanged() { //2) Icon change accordingly to current App Theme // using rootView.getContext() because getApplicationContext() didn't work item.setIcon(player.isMuted() - ? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(), R.attr.volume_off) - : ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(), R.attr.volume_on)); + ? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(), + R.attr.volume_off) + : ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(), + R.attr.volume_on)); } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 0734139e1..b75d39b84 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -78,7 +78,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; /** - * Base for video players + * Base for video players. * * @author mauriciocolli */ @@ -91,115 +91,113 @@ public abstract class VideoPlayer extends BasePlayer PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { public static final boolean DEBUG = BasePlayer.DEBUG; - public final String TAG; + public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis /*////////////////////////////////////////////////////////////////////////// // Player //////////////////////////////////////////////////////////////////////////*/ - - protected static final int RENDERER_UNAVAILABLE = -1; - public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds - - private List availableStreams; - private int selectedStreamIndex; - - protected boolean wasPlaying = false; - - @NonNull final private VideoPlaybackResolver resolver; + protected static final int RENDERER_UNAVAILABLE = -1; + public final String TAG; + @NonNull + private final VideoPlaybackResolver resolver; + private final Handler controlsVisibilityHandler = new Handler(); + private final int qualityPopupMenuGroupId = 69; + private final int playbackSpeedPopupMenuGroupId = 79; /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ - + private final int captionPopupMenuGroupId = 89; + protected boolean wasPlaying = false; + boolean isSomePopupMenuVisible = false; + private List availableStreams; + private int selectedStreamIndex; private View rootView; - private AspectRatioFrameLayout aspectRatioFrameLayout; private SurfaceView surfaceView; private View surfaceForeground; - private View loadingPanel; private ImageView endScreen; private ImageView controlAnimationView; - private View controlsRoot; private TextView currentDisplaySeek; - private View bottomControlsRoot; private SeekBar playbackSeekBar; private TextView playbackCurrentTime; private TextView playbackEndTime; private TextView playbackLiveSync; private TextView playbackSpeedTextView; - private View topControlsRoot; private TextView qualityTextView; - private SubtitleView subtitleView; - private TextView resizeView; private TextView captionTextView; - private ValueAnimator controlViewAnimator; - private final Handler controlsVisibilityHandler = new Handler(); - - boolean isSomePopupMenuVisible = false; - private final int qualityPopupMenuGroupId = 69; private PopupMenu qualityPopupMenu; - - private final int playbackSpeedPopupMenuGroupId = 79; private PopupMenu playbackSpeedPopupMenu; - - private final int captionPopupMenuGroupId = 89; private PopupMenu captionPopupMenu; /////////////////////////////////////////////////////////////////////////// - public VideoPlayer(String debugTag, Context context) { + public VideoPlayer(final String debugTag, final Context context) { super(context); this.TAG = debugTag; this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); } - public void setup(View rootView) { - initViews(rootView); + // workaround to match normalized captions like english to English or deutsch to Deutsch + private static boolean containsCaseInsensitive(final List list, final String toFind) { + for (String i : list) { + if (i.equalsIgnoreCase(toFind)) { + return true; + } + } + return false; + } + + public void setup(final View view) { + initViews(view); setup(); } - public void initViews(View rootView) { - this.rootView = rootView; - this.aspectRatioFrameLayout = rootView.findViewById(R.id.aspectRatioLayout); - this.surfaceView = rootView.findViewById(R.id.surfaceView); - this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground); - this.loadingPanel = rootView.findViewById(R.id.loading_panel); - this.endScreen = rootView.findViewById(R.id.endScreen); - this.controlAnimationView = rootView.findViewById(R.id.controlAnimationView); - this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot); - this.currentDisplaySeek = rootView.findViewById(R.id.currentDisplaySeek); - this.playbackSeekBar = rootView.findViewById(R.id.playbackSeekBar); - this.playbackCurrentTime = rootView.findViewById(R.id.playbackCurrentTime); - this.playbackEndTime = rootView.findViewById(R.id.playbackEndTime); - this.playbackLiveSync = rootView.findViewById(R.id.playbackLiveSync); - this.playbackSpeedTextView = rootView.findViewById(R.id.playbackSpeed); - this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls); - this.topControlsRoot = rootView.findViewById(R.id.topControls); - this.qualityTextView = rootView.findViewById(R.id.qualityTextView); - - this.subtitleView = rootView.findViewById(R.id.subtitleView); + public void initViews(final View view) { + this.rootView = view; + this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout); + this.surfaceView = view.findViewById(R.id.surfaceView); + this.surfaceForeground = view.findViewById(R.id.surfaceForeground); + this.loadingPanel = view.findViewById(R.id.loading_panel); + this.endScreen = view.findViewById(R.id.endScreen); + this.controlAnimationView = view.findViewById(R.id.controlAnimationView); + this.controlsRoot = view.findViewById(R.id.playbackControlRoot); + this.currentDisplaySeek = view.findViewById(R.id.currentDisplaySeek); + this.playbackSeekBar = view.findViewById(R.id.playbackSeekBar); + this.playbackCurrentTime = view.findViewById(R.id.playbackCurrentTime); + this.playbackEndTime = view.findViewById(R.id.playbackEndTime); + this.playbackLiveSync = view.findViewById(R.id.playbackLiveSync); + this.playbackSpeedTextView = view.findViewById(R.id.playbackSpeed); + this.bottomControlsRoot = view.findViewById(R.id.bottomControls); + this.topControlsRoot = view.findViewById(R.id.topControls); + this.qualityTextView = view.findViewById(R.id.qualityTextView); + + this.subtitleView = view.findViewById(R.id.subtitleView); final float captionScale = PlayerHelper.getCaptionScale(context); final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); setupSubtitleView(subtitleView, captionScale, captionStyle); - this.resizeView = rootView.findViewById(R.id.resizeTextView); - resizeView.setText(PlayerHelper.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode())); + this.resizeView = view.findViewById(R.id.resizeTextView); + resizeView.setText(PlayerHelper + .resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode())); - this.captionTextView = rootView.findViewById(R.id.captionTextView); + this.captionTextView = view.findViewById(R.id.captionTextView); //this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); - this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY); + } + this.playbackSeekBar.getProgressDrawable(). + setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY); this.qualityPopupMenu = new PopupMenu(context, qualityTextView); this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView); @@ -209,9 +207,8 @@ public void initViews(View rootView) { .getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY); } - protected abstract void setupSubtitleView(@NonNull SubtitleView view, - final float captionScale, - @NonNull final CaptionStyleCompat captionStyle); + protected abstract void setupSubtitleView(@NonNull SubtitleView view, float captionScale, + @NonNull CaptionStyleCompat captionStyle); @Override public void initListeners() { @@ -241,9 +238,15 @@ public void initPlayer(final boolean playOnReady) { } } + /*////////////////////////////////////////////////////////////////////////// + // UI Builders + //////////////////////////////////////////////////////////////////////////*/ + @Override public void handleIntent(final Intent intent) { - if (intent == null) return; + if (intent == null) { + return; + } if (intent.hasExtra(PLAYBACK_QUALITY)) { setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY)); @@ -252,18 +255,16 @@ public void handleIntent(final Intent intent) { super.handleIntent(intent); } - /*////////////////////////////////////////////////////////////////////////// - // UI Builders - //////////////////////////////////////////////////////////////////////////*/ - public void buildQualityMenu() { - if (qualityPopupMenu == null) return; + if (qualityPopupMenu == null) { + return; + } qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId); for (int i = 0; i < availableStreams.size(); i++) { VideoStream videoStream = availableStreams.get(i); - qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, - MediaFormat.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution); + qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat + .getNameById(videoStream.getFormatId()) + " " + videoStream.resolution); } if (getSelectedVideoStream() != null) { qualityTextView.setText(getSelectedVideoStream().resolution); @@ -273,11 +274,14 @@ public void buildQualityMenu() { } private void buildPlaybackSpeedMenu() { - if (playbackSpeedPopupMenu == null) return; + if (playbackSpeedPopupMenu == null) { + return; + } playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId); for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { - playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i])); + playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, + formatSpeed(PLAYBACK_SPEEDS[i])); } playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed())); playbackSpeedPopupMenu.setOnMenuItemClickListener(this); @@ -285,7 +289,9 @@ private void buildPlaybackSpeedMenu() { } private void buildCaptionMenu(final List availableLanguages) { - if (captionPopupMenu == null) return; + if (captionPopupMenu == null) { + return; + } captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId); String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context) @@ -296,8 +302,8 @@ private void buildCaptionMenu(final List availableLanguages) { * we are only looking for "(" instead of "(auto-generated)" to hopefully get all * internationalized variants such as "(automatisch-erzeugt)" and so on */ - boolean searchForAutogenerated = userPreferredLanguage != null && - !userPreferredLanguage.contains("("); + boolean searchForAutogenerated = userPreferredLanguage != null + && !userPreferredLanguage.contains("("); // Add option for turning off caption MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId, @@ -324,18 +330,19 @@ private void buildCaptionMenu(final List availableLanguages) { trackSelector.setPreferredTextLanguage(captionLanguage); trackSelector.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(textRendererIndex, false)); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); prefs.edit().putString(context.getString(R.string.caption_user_set_key), captionLanguage).commit(); } return true; }); // apply caption language from previous user preference - if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) || - searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage) || - userPreferredLanguage.contains("(") && - captionLanguage.startsWith(userPreferredLanguage.substring(0, - userPreferredLanguage.indexOf('('))))) { + if (userPreferredLanguage != null && (captionLanguage.equals(userPreferredLanguage) + || searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage) + || userPreferredLanguage.contains("(") && captionLanguage.startsWith( + userPreferredLanguage + .substring(0, userPreferredLanguage.indexOf('('))))) { final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); if (textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setPreferredTextLanguage(captionLanguage); @@ -347,9 +354,14 @@ private void buildCaptionMenu(final List availableLanguages) { } captionPopupMenu.setOnDismissListener(this); } + /*////////////////////////////////////////////////////////////////////////// + // Playback Listener + //////////////////////////////////////////////////////////////////////////*/ private void updateStreamRelatedViews() { - if (getCurrentMetadata() == null) return; + if (getCurrentMetadata() == null) { + return; + } final MediaSourceTag tag = getCurrentMetadata(); final StreamInfo metadata = tag.getMetadata(); @@ -380,8 +392,10 @@ private void updateStreamRelatedViews() { break; case VIDEO_STREAM: - if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() == 0) + if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() + == 0) { break; + } availableStreams = tag.getSortedAvailableVideoStreams(); selectedStreamIndex = tag.getSelectedVideoStreamIndex(); @@ -398,9 +412,6 @@ private void updateStreamRelatedViews() { buildPlaybackSpeedMenu(); playbackSpeedTextView.setVisibility(View.VISIBLE); } - /*////////////////////////////////////////////////////////////////////////// - // Playback Listener - //////////////////////////////////////////////////////////////////////////*/ protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver(); @@ -409,16 +420,16 @@ protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { updateStreamRelatedViews(); } + /*////////////////////////////////////////////////////////////////////////// + // States Implementation + //////////////////////////////////////////////////////////////////////////*/ + @Override @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { return resolver.resolve(info); } - /*////////////////////////////////////////////////////////////////////////// - // States Implementation - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onBlocked() { super.onBlocked(); @@ -427,9 +438,11 @@ public void onBlocked() { animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION); playbackSeekBar.setEnabled(false); - // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, + // so sets the color again + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); + } loadingPanel.setBackgroundColor(Color.BLACK); animateView(loadingPanel, true, 0); @@ -445,9 +458,11 @@ public void onPlaying() { showAndAnimateControl(-1, true); playbackSeekBar.setEnabled(true); - // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, + // so sets the color again + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN); + } loadingPanel.setVisibility(View.GONE); @@ -456,23 +471,33 @@ public void onPlaying() { @Override public void onBuffering() { - if (DEBUG) Log.d(TAG, "onBuffering() called"); + if (DEBUG) { + Log.d(TAG, "onBuffering() called"); + } loadingPanel.setBackgroundColor(Color.TRANSPARENT); } @Override public void onPaused() { - if (DEBUG) Log.d(TAG, "onPaused() called"); + if (DEBUG) { + Log.d(TAG, "onPaused() called"); + } showControls(400); loadingPanel.setVisibility(View.GONE); } @Override public void onPausedSeek() { - if (DEBUG) Log.d(TAG, "onPausedSeek() called"); + if (DEBUG) { + Log.d(TAG, "onPausedSeek() called"); + } showAndAnimateControl(-1, true); } + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer Video Listener + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCompleted() { super.onCompleted(); @@ -485,44 +510,50 @@ public void onCompleted() { animateView(surfaceForeground, true, 100); } - /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer Video Listener - //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + public void onTracksChanged(final TrackGroupArray trackGroups, + final TrackSelectionArray trackSelections) { super.onTracksChanged(trackGroups, trackSelections); onTextTrackUpdate(); } @Override - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) { super.onPlaybackParametersChanged(playbackParameters); playbackSpeedTextView.setText(formatSpeed(playbackParameters.speed)); } @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + public void onVideoSizeChanged(final int width, final int height, + final int unappliedRotationDegrees, + final float pixelWidthHeightRatio) { if (DEBUG) { - Log.d(TAG, "onVideoSizeChanged() called with: width / height = [" + width + " / " + height + " = " + (((float) width) / height) + "], unappliedRotationDegrees = [" + unappliedRotationDegrees + "], pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]"); + Log.d(TAG, "onVideoSizeChanged() called with: " + + "width / height = [" + width + " / " + height + + " = " + (((float) width) / height) + "], " + + "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], " + + "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]"); } aspectRatioFrameLayout.setAspectRatio(((float) width) / height); } + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer Track Updates + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onRenderedFirstFrame() { animateView(surfaceForeground, false, 100); } - /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer Track Updates - //////////////////////////////////////////////////////////////////////////*/ - private void onTextTrackUpdate() { final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT); - if (captionTextView == null) return; - if (trackSelector.getCurrentMappedTrackInfo() == null || textRenderer == RENDERER_UNAVAILABLE) { + if (captionTextView == null) { + return; + } + if (trackSelector.getCurrentMappedTrackInfo() == null + || textRenderer == RENDERER_UNAVAILABLE) { captionTextView.setVisibility(View.GONE); return; } @@ -543,8 +574,8 @@ private void onTextTrackUpdate() { final String preferredLanguage = trackSelector.getPreferredTextLanguage(); // Build UI buildCaptionMenu(availableLanguages); - if (trackSelector.getParameters().getRendererDisabled(textRenderer) || - preferredLanguage == null || (!availableLanguages.contains(preferredLanguage) + if (trackSelector.getParameters().getRendererDisabled(textRenderer) + || preferredLanguage == null || (!availableLanguages.contains(preferredLanguage) && !containsCaseInsensitive(availableLanguages, preferredLanguage))) { captionTextView.setText(R.string.caption_none); } else { @@ -553,22 +584,15 @@ private void onTextTrackUpdate() { captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); } - // workaround to match normalized captions like english to English or deutsch to Deutsch - private static boolean containsCaseInsensitive(List list, String toFind) { - for(String i : list){ - if(i.equalsIgnoreCase(toFind)) - return true; - } - return false; - } - /*////////////////////////////////////////////////////////////////////////// // General Player //////////////////////////////////////////////////////////////////////////*/ @Override - public void onPrepared(boolean playWhenReady) { - if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); + public void onPrepared(final boolean playWhenReady) { + if (DEBUG) { + Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); + } playbackSeekBar.setMax((int) simpleExoPlayer.getDuration()); playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration())); @@ -578,41 +602,56 @@ public void onPrepared(boolean playWhenReady) { if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) { controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION); + controlsVisibilityHandler + .postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION); } } @Override public void destroy() { super.destroy(); - if (endScreen != null) endScreen.setImageBitmap(null); + if (endScreen != null) { + endScreen.setImageBitmap(null); + } } @Override - public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { - if (!isPrepared()) return; + public void onUpdateProgress(final int currentProgress, final int duration, + final int bufferPercent) { + if (!isPrepared()) { + return; + } if (duration != playbackSeekBar.getMax()) { playbackEndTime.setText(getTimeString(duration)); playbackSeekBar.setMax(duration); } if (currentState != STATE_PAUSED) { - if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress); + if (currentState != STATE_PAUSED_SEEK) { + playbackSeekBar.setProgress(currentProgress); + } playbackCurrentTime.setText(getTimeString(currentProgress)); } if (simpleExoPlayer.isLoading() || bufferPercent > 90) { - playbackSeekBar.setSecondaryProgress((int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100))); + playbackSeekBar.setSecondaryProgress( + (int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100))); } if (DEBUG && bufferPercent % 20 == 0) { //Limit log - Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); + Log.d(TAG, "updateProgress() called with: " + + "isVisible = " + isControlsVisible() + ", " + + "currentProgress = [" + currentProgress + "], " + + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); } playbackLiveSync.setClickable(!isLiveEdge()); } @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + public void onLoadingComplete(final String imageUri, final View view, + final Bitmap loadedImage) { super.onLoadingComplete(imageUri, view, loadedImage); - if (loadedImage != null) endScreen.setImageBitmap(loadedImage); + if (loadedImage != null) { + endScreen.setImageBitmap(loadedImage); + } } protected void onFullScreenButtonClicked() { @@ -636,8 +675,10 @@ public void onFastForward() { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onClick(View v) { - if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); + public void onClick(final View v) { + if (DEBUG) { + Log.d(TAG, "onClick() called with: v = [" + v + "]"); + } if (v.getId() == qualityTextView.getId()) { onQualitySelectorClicked(); } else if (v.getId() == playbackSpeedTextView.getId()) { @@ -652,17 +693,22 @@ public void onClick(View v) { } /** - * Called when an item of the quality selector or the playback speed selector is selected + * Called when an item of the quality selector or the playback speed selector is selected. */ @Override - public boolean onMenuItemClick(MenuItem menuItem) { - if (DEBUG) - Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]"); + public boolean onMenuItemClick(final MenuItem menuItem) { + if (DEBUG) { + Log.d(TAG, "onMenuItemClick() called with: " + + "menuItem = [" + menuItem + "], " + + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); + } if (qualityPopupMenuGroupId == menuItem.getGroupId()) { final int menuItemIndex = menuItem.getItemId(); - if (selectedStreamIndex == menuItemIndex || - availableStreams == null || availableStreams.size() <= menuItemIndex) return true; + if (selectedStreamIndex == menuItemIndex || availableStreams == null + || availableStreams.size() <= menuItemIndex) { + return true; + } final String newResolution = availableStreams.get(menuItemIndex).resolution; setRecovery(); @@ -683,11 +729,13 @@ public boolean onMenuItemClick(MenuItem menuItem) { } /** - * Called when some popup menu is dismissed + * Called when some popup menu is dismissed. */ @Override - public void onDismiss(PopupMenu menu) { - if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); + public void onDismiss(final PopupMenu menu) { + if (DEBUG) { + Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); + } isSomePopupMenuVisible = false; if (getSelectedVideoStream() != null) { qualityTextView.setText(getSelectedVideoStream().resolution); @@ -695,7 +743,9 @@ public void onDismiss(PopupMenu menu) { } public void onQualitySelectorClicked() { - if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called"); + if (DEBUG) { + Log.d(TAG, "onQualitySelectorClicked() called"); + } qualityPopupMenu.show(); isSomePopupMenuVisible = true; showControls(DEFAULT_CONTROLS_DURATION); @@ -711,14 +761,18 @@ public void onQualitySelectorClicked() { } public void onPlaybackSpeedClicked() { - if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called"); + if (DEBUG) { + Log.d(TAG, "onPlaybackSpeedClicked() called"); + } playbackSpeedPopupMenu.show(); isSomePopupMenuVisible = true; showControls(DEFAULT_CONTROLS_DURATION); } private void onCaptionClicked() { - if (DEBUG) Log.d(TAG, "onCaptionClicked() called"); + if (DEBUG) { + Log.d(TAG, "onCaptionClicked() called"); + } captionPopupMenu.show(); isSomePopupMenuVisible = true; showControls(DEFAULT_CONTROLS_DURATION); @@ -737,26 +791,38 @@ protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resize getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode)); } - protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode); + protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode); /*////////////////////////////////////////////////////////////////////////// // SeekBar Listener //////////////////////////////////////////////////////////////////////////*/ @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (DEBUG && fromUser) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + progress + "]"); + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { + if (DEBUG && fromUser) { + Log.d(TAG, "onProgressChanged() called with: " + + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); + } //if (fromUser) playbackCurrentTime.setText(getTimeString(progress)); - if (fromUser) currentDisplaySeek.setText(getTimeString(progress)); + if (fromUser) { + currentDisplaySeek.setText(getTimeString(progress)); + } } @Override - public void onStartTrackingTouch(SeekBar seekBar) { - if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); - if (getCurrentState() != STATE_PAUSED_SEEK) changeState(STATE_PAUSED_SEEK); + public void onStartTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + if (getCurrentState() != STATE_PAUSED_SEEK) { + changeState(STATE_PAUSED_SEEK); + } wasPlaying = simpleExoPlayer.getPlayWhenReady(); - if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false); + if (isPlaying()) { + simpleExoPlayer.setPlayWhenReady(false); + } showControls(0); animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true, @@ -764,17 +830,25 @@ public void onStartTrackingTouch(SeekBar seekBar) { } @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (DEBUG) Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); + public void onStopTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } seekTo(seekBar.getProgress()); - if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true); + if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) { + simpleExoPlayer.setPlayWhenReady(true); + } playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200); - if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING); - if (!isProgressLoopRunning()) startProgressLoop(); + if (getCurrentState() == STATE_PAUSED_SEEK) { + changeState(STATE_BUFFERING); + } + if (!isProgressLoopRunning()) { + startProgressLoop(); + } } /*////////////////////////////////////////////////////////////////////////// @@ -782,7 +856,9 @@ public void onStopTrackingTouch(SeekBar seekBar) { //////////////////////////////////////////////////////////////////////////*/ public int getRendererIndex(final int trackIndex) { - if (simpleExoPlayer == null) return RENDERER_UNAVAILABLE; + if (simpleExoPlayer == null) { + return RENDERER_UNAVAILABLE; + } for (int t = 0; t < simpleExoPlayer.getRendererCount(); t++) { if (simpleExoPlayer.getRendererType(t) == trackIndex) { @@ -798,15 +874,21 @@ public boolean isControlsVisible() { } /** - * Show a animation, and depending on goneOnEnd, will stay on the screen or be gone + * Show a animation, and depending on goneOnEnd, will stay on the screen or be gone. * - * @param drawableId the drawable that will be used to animate, pass -1 to clear any animation that is visible + * @param drawableId the drawable that will be used to animate, + * pass -1 to clear any animation that is visible * @param goneOnEnd will set the animation view to GONE on the end of the animation */ public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) { - if (DEBUG) Log.d(TAG, "showAndAnimateControl() called with: drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]"); + if (DEBUG) { + Log.d(TAG, "showAndAnimateControl() called with: " + + "drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]"); + } if (controlViewAnimator != null && controlViewAnimator.isRunning()) { - if (DEBUG) Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning"); + if (DEBUG) { + Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning"); + } controlViewAnimator.end(); } @@ -819,7 +901,7 @@ public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) ).setDuration(DEFAULT_CONTROLS_DURATION); controlViewAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { controlAnimationView.setVisibility(View.GONE); } }); @@ -828,8 +910,10 @@ public void onAnimationEnd(Animator animation) { return; } - float scaleFrom = goneOnEnd ? 1f : 1f, scaleTo = goneOnEnd ? 1.8f : 1.4f; - float alphaFrom = goneOnEnd ? 1f : 0f, alphaTo = goneOnEnd ? 0f : 1f; + float scaleFrom = goneOnEnd ? 1f : 1f; + float scaleTo = goneOnEnd ? 1.8f : 1.4f; + float alphaFrom = goneOnEnd ? 1f : 0f; + float alphaTo = goneOnEnd ? 0f : 1f; controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView, @@ -840,9 +924,12 @@ public void onAnimationEnd(Animator animation) { controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500); controlViewAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - if (goneOnEnd) controlAnimationView.setVisibility(View.GONE); - else controlAnimationView.setVisibility(View.VISIBLE); + public void onAnimationEnd(final Animator animation) { + if (goneOnEnd) { + controlAnimationView.setVisibility(View.GONE); + } else { + controlAnimationView.setVisibility(View.VISIBLE); + } } }); @@ -857,50 +944,58 @@ public boolean isSomePopupMenuVisible() { } public void showControlsThenHide() { - if (DEBUG) Log.d(TAG, "showControlsThenHide() called"); - animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0, - () -> hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME)); + if (DEBUG) { + Log.d(TAG, "showControlsThenHide() called"); + } + animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0, () -> + hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME)); } - public void showControls(long duration) { - if (DEBUG) Log.d(TAG, "showControls() called"); + public void showControls(final long duration) { + if (DEBUG) { + Log.d(TAG, "showControls() called"); + } controlsVisibilityHandler.removeCallbacksAndMessages(null); animateView(controlsRoot, true, duration); } - public void hideControls(final long duration, long delay) { - if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + public void hideControls(final long duration, final long delay) { + if (DEBUG) { + Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + } controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed( - () -> animateView(controlsRoot, false, duration), delay); + controlsVisibilityHandler.postDelayed(() -> + animateView(controlsRoot, false, duration), delay); } - public void hideControlsAndButton(final long duration, long delay, View button) { - if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + public void hideControlsAndButton(final long duration, final long delay, final View button) { + if (DEBUG) { + Log.d(TAG, "hideControls() called with: delay = [" + delay + "]"); + } controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed(hideControlsAndButtonHandler(duration, button), delay); + controlsVisibilityHandler + .postDelayed(hideControlsAndButtonHandler(duration, button), delay); } - private Runnable hideControlsAndButtonHandler(long duration, View videoPlayPause) - { + private Runnable hideControlsAndButtonHandler(final long duration, final View videoPlayPause) { return () -> { videoPlayPause.setVisibility(View.INVISIBLE); - animateView(controlsRoot, false,duration); + animateView(controlsRoot, false, duration); }; } /*////////////////////////////////////////////////////////////////////////// // Getters and Setters //////////////////////////////////////////////////////////////////////////*/ - public void setPlaybackQuality(final String quality) { - this.resolver.setPlaybackQuality(quality); - } - @Nullable public String getPlaybackQuality() { return resolver.getPlaybackQuality(); } + public void setPlaybackQuality(final String quality) { + this.resolver.setPlaybackQuality(quality); + } + public AspectRatioFrameLayout getAspectRatioFrameLayout() { return aspectRatioFrameLayout; } @@ -915,9 +1010,9 @@ public boolean wasPlaying() { @Nullable public VideoStream getSelectedVideoStream() { - return (selectedStreamIndex >= 0 && availableStreams != null && - availableStreams.size() > selectedStreamIndex) ? - availableStreams.get(selectedStreamIndex) : null; + return (selectedStreamIndex >= 0 && availableStreams != null + && availableStreams.size() > selectedStreamIndex) + ? availableStreams.get(selectedStreamIndex) : null; } public Handler getControlsVisibilityHandler() { @@ -928,7 +1023,7 @@ public View getRootView() { return rootView; } - public void setRootView(View rootView) { + public void setRootView(final View rootView) { this.rootView = rootView; } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java index 3a7b29954..0809fa0f5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java @@ -6,8 +6,12 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; public interface PlayerEventListener { - void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, PlaybackParameters parameters); + void onPlaybackUpdate(int state, int repeatMode, boolean shuffled, + PlaybackParameters parameters); + void onProgressUpdate(int currentProgress, int duration, int bufferPercent); + void onMetadataUpdate(StreamInfo info); + void onServiceStopped(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 8f344390a..369e3236e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -9,14 +9,14 @@ import android.media.AudioManager; import android.media.audiofx.AudioEffect; import android.os.Build; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.analytics.AnalyticsListener; -public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, - AnalyticsListener { +public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener { private static final String TAG = "AudioFocusReactor"; @@ -82,20 +82,20 @@ public int getVolume() { return audioManager.getStreamVolume(STREAM_TYPE); } - public int getMaxVolume() { - return audioManager.getStreamMaxVolume(STREAM_TYPE); - } - public void setVolume(final int volume) { audioManager.setStreamVolume(STREAM_TYPE, volume, 0); } + public int getMaxVolume() { + return audioManager.getStreamMaxVolume(STREAM_TYPE); + } + /*////////////////////////////////////////////////////////////////////////// // AudioFocus //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAudioFocusChange(int focusChange) { + public void onAudioFocusChange(final int focusChange) { Log.d(TAG, "onAudioFocusChange() called with: focusChange = [" + focusChange + "]"); switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: @@ -138,17 +138,17 @@ private void animateAudio(final float from, final float to) { valueAnimator.setDuration(AudioReactor.DUCK_DURATION); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationStart(Animator animation) { + public void onAnimationStart(final Animator animation) { player.setVolume(from); } @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(final Animator animation) { player.setVolume(to); } @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { player.setVolume(to); } }); @@ -162,8 +162,10 @@ public void onAnimationEnd(Animator animation) { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAudioSessionId(EventTime eventTime, int audioSessionId) { - if (!PlayerHelper.isUsingDSP(context)) return; + public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) { + if (!PlayerHelper.isUsingDSP(context)) { + return; + } final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index 8160640cb..dc3d9d269 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -21,23 +21,22 @@ /* package-private */ class CacheFactory implements DataSource.Factory { private static final String TAG = "CacheFactory"; private static final String CACHE_FOLDER_NAME = "exoplayer"; - private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR; - - private final DefaultDataSourceFactory dataSourceFactory; - private final File cacheDir; - private final long maxFileSize; - + private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE + | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR; // Creating cache on every instance may cause problems with multiple players when // sources are not ExtractorMediaSource // see: https://stackoverflow.com/questions/28700391/using-cache-in-exoplayer // todo: make this a singleton? private static SimpleCache cache; + private final DefaultDataSourceFactory dataSourceFactory; + private final File cacheDir; + private final long maxFileSize; - public CacheFactory(@NonNull final Context context, - @NonNull final String userAgent, - @NonNull final TransferListener transferListener) { - this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(context), - PlayerHelper.getPreferredFileSize(context)); + CacheFactory(@NonNull final Context context, + @NonNull final String userAgent, + @NonNull final TransferListener transferListener) { + this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(), + PlayerHelper.getPreferredFileSize()); } private CacheFactory(@NonNull final Context context, @@ -55,7 +54,8 @@ private CacheFactory(@NonNull final Context context, } if (cache == null) { - final LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxCacheSize); + final LeastRecentlyUsedCacheEvictor evictor + = new LeastRecentlyUsedCacheEvictor(maxCacheSize); cache = new SimpleCache(cacheDir, evictor, new ExoDatabaseProvider(context)); } } @@ -72,7 +72,9 @@ public DataSource createDataSource() { } public void tryDeleteCacheFiles() { - if (!cacheDir.exists() || !cacheDir.isDirectory()) return; + if (!cacheDir.exists() || !cacheDir.isDirectory()) { + return; + } try { for (File file : cacheDir.listFiles()) { @@ -85,4 +87,4 @@ public void tryDeleteCacheFiles() { Log.e(TAG, "Failed to delete file.", ignored); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java index 4239dd62f..92ae009f6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java @@ -1,7 +1,5 @@ package org.schabi.newpipe.player.helper; -import android.content.Context; - import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.Renderer; @@ -20,10 +18,10 @@ public class LoadController implements LoadControl { // Default Load Control //////////////////////////////////////////////////////////////////////////*/ - public LoadController(final Context context) { - this(PlayerHelper.getPlaybackStartBufferMs(context), - PlayerHelper.getPlaybackMinimumBufferMs(context), - PlayerHelper.getPlaybackOptimalBufferMs(context)); + public LoadController() { + this(PlayerHelper.getPlaybackStartBufferMs(), + PlayerHelper.getPlaybackMinimumBufferMs(), + PlayerHelper.getPlaybackOptimalBufferMs()); } private LoadController(final int initialPlaybackBufferMs, @@ -47,8 +45,8 @@ public void onPrepared() { } @Override - public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroupArray, - TrackSelectionArray trackSelectionArray) { + public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroupArray, + final TrackSelectionArray trackSelectionArray) { internalLoadControl.onTracksSelected(renderers, trackGroupArray, trackSelectionArray); } @@ -78,17 +76,18 @@ public boolean retainBackBufferFromKeyframe() { } @Override - public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) { + public boolean shouldContinueLoading(final long bufferedDurationUs, + final float playbackSpeed) { return internalLoadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed); } @Override - public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, - boolean rebuffering) { - final boolean isInitialPlaybackBufferFilled = bufferedDurationUs >= - this.initialPlaybackBufferUs * playbackSpeed; - final boolean isInternalStartingPlayback = internalLoadControl.shouldStartPlayback( - bufferedDurationUs, playbackSpeed, rebuffering); + public boolean shouldStartPlayback(final long bufferedDurationUs, final float playbackSpeed, + final boolean rebuffering) { + final boolean isInitialPlaybackBufferFilled + = bufferedDurationUs >= this.initialPlaybackBufferUs * playbackSpeed; + final boolean isInternalStartingPlayback = internalLoadControl + .shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering); return isInitialPlaybackBufferFilled || isInternalStartingPlayback; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LockManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/LockManager.java index 1f352159c..6d0cf8e85 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LockManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LockManager.java @@ -18,25 +18,37 @@ public class LockManager { private WifiManager.WifiLock wifiLock; public LockManager(final Context context) { - powerManager = ((PowerManager) context.getApplicationContext().getSystemService(POWER_SERVICE)); - wifiManager = ((WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE)); + powerManager = ((PowerManager) context.getApplicationContext() + .getSystemService(POWER_SERVICE)); + wifiManager = ((WifiManager) context.getApplicationContext() + .getSystemService(WIFI_SERVICE)); } public void acquireWifiAndCpu() { Log.d(TAG, "acquireWifiAndCpu() called"); - if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return; + if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) { + return; + } wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - if (wakeLock != null) wakeLock.acquire(); - if (wifiLock != null) wifiLock.acquire(); + if (wakeLock != null) { + wakeLock.acquire(); + } + if (wifiLock != null) { + wifiLock.acquire(); + } } public void releaseWifiAndCpu() { Log.d(TAG, "releaseWifiAndCpu() called"); - if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); - if (wifiLock != null && wifiLock.isHeld()) wifiLock.release(); + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } + if (wifiLock != null && wifiLock.isHeld()) { + wifiLock.release(); + } wakeLock = null; wifiLock = null; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java index 8b9369613..e101e2185 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java @@ -13,8 +13,8 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; -import androidx.media.session.MediaButtonReceiver; import androidx.media.app.NotificationCompat.MediaStyle; +import androidx.media.session.MediaButtonReceiver; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; @@ -50,7 +50,8 @@ public KeyEvent handleMediaButtonIntent(final Intent intent) { } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void setLockScreenArt(NotificationCompat.Builder builder, @Nullable Bitmap thumbnailBitmap) { + public void setLockScreenArt(final NotificationCompat.Builder builder, + @Nullable final Bitmap thumbnailBitmap) { if (thumbnailBitmap == null || !mediaSession.isActive()) { return; } @@ -68,7 +69,7 @@ public void setLockScreenArt(NotificationCompat.Builder builder, @Nullable Bitma } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void clearLockScreenArt(NotificationCompat.Builder builder) { + public void clearLockScreenArt(final NotificationCompat.Builder builder) { mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, null) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 94fb412f7..089ea456e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -3,11 +3,6 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.appcompat.app.AlertDialog; - import android.preference.PreferenceManager; import android.util.Log; import android.view.View; @@ -15,6 +10,11 @@ import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + import org.schabi.newpipe.R; import org.schabi.newpipe.util.SliderStrategy; @@ -22,64 +22,65 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; public class PlaybackParameterDialog extends DialogFragment { - @NonNull private static final String TAG = "PlaybackParameterDialog"; - // Minimum allowable range in ExoPlayer public static final double MINIMUM_PLAYBACK_VALUE = 0.10f; public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - public static final char STEP_UP_SIGN = '+'; public static final char STEP_DOWN_SIGN = '-'; - public static final double STEP_ONE_PERCENT_VALUE = 0.01f; public static final double STEP_FIVE_PERCENT_VALUE = 0.05f; public static final double STEP_TEN_PERCENT_VALUE = 0.10f; public static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; public static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; - public static final double DEFAULT_TEMPO = 1.00f; public static final double DEFAULT_PITCH = 1.00f; public static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; public static final boolean DEFAULT_SKIP_SILENCE = false; + @NonNull + private static final String TAG = "PlaybackParameterDialog"; + @NonNull + private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; + @NonNull + private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; - @NonNull private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; - @NonNull private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; - - @NonNull private static final String TEMPO_KEY = "tempo_key"; - @NonNull private static final String PITCH_KEY = "pitch_key"; - @NonNull private static final String STEP_SIZE_KEY = "step_size_key"; - - public interface Callback { - void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch, - final boolean playbackSkipSilence); - } - - @Nullable private Callback callback; - - @NonNull private final SliderStrategy strategy = new SliderStrategy.Quadratic( + @NonNull + private static final String TEMPO_KEY = "tempo_key"; + @NonNull + private static final String PITCH_KEY = "pitch_key"; + @NonNull + private static final String STEP_SIZE_KEY = "step_size_key"; + @NonNull + private final SliderStrategy strategy = new SliderStrategy.Quadratic( MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, /*centerAt=*/1.00f, /*sliderGranularity=*/10000); - + @Nullable + private Callback callback; private double initialTempo = DEFAULT_TEMPO; private double initialPitch = DEFAULT_PITCH; private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; - private double tempo = DEFAULT_TEMPO; private double pitch = DEFAULT_PITCH; private double stepSize = DEFAULT_STEP; - - @Nullable private SeekBar tempoSlider; - @Nullable private TextView tempoCurrentText; - @Nullable private TextView tempoStepDownText; - @Nullable private TextView tempoStepUpText; - - @Nullable private SeekBar pitchSlider; - @Nullable private TextView pitchCurrentText; - @Nullable private TextView pitchStepDownText; - @Nullable private TextView pitchStepUpText; - - @Nullable private CheckBox unhookingCheckbox; - @Nullable private CheckBox skipSilenceCheckbox; + @Nullable + private SeekBar tempoSlider; + @Nullable + private TextView tempoCurrentText; + @Nullable + private TextView tempoStepDownText; + @Nullable + private TextView tempoStepUpText; + @Nullable + private SeekBar pitchSlider; + @Nullable + private TextView pitchCurrentText; + @Nullable + private TextView pitchStepDownText; + @Nullable + private TextView pitchStepUpText; + @Nullable + private CheckBox unhookingCheckbox; + @Nullable + private CheckBox skipSilenceCheckbox; public static PlaybackParameterDialog newInstance(final double playbackTempo, final double playbackPitch, @@ -95,12 +96,27 @@ public static PlaybackParameterDialog newInstance(final double playbackTempo, return dialog; } + @NonNull + private static String getStepUpPercentString(final double percent) { + return STEP_UP_SIGN + getPercentString(percent); + } + /*////////////////////////////////////////////////////////////////////////// // Lifecycle //////////////////////////////////////////////////////////////////////////*/ + @NonNull + private static String getStepDownPercentString(final double percent) { + return STEP_DOWN_SIGN + getPercentString(percent); + } + + @NonNull + private static String getPercentString(final double percent) { + return PlayerHelper.formatPitch(percent); + } + @Override - public void onAttach(Context context) { + public void onAttach(final Context context) { super.onAttach(context); if (context != null && context instanceof Callback) { callback = (Callback) context; @@ -109,8 +125,12 @@ public void onAttach(Context context) { } } + /*////////////////////////////////////////////////////////////////////////// + // Dialog + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); super.onCreate(savedInstanceState); if (savedInstanceState != null) { @@ -123,8 +143,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } } + /*////////////////////////////////////////////////////////////////////////// + // Control Views + //////////////////////////////////////////////////////////////////////////*/ + @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putDouble(INITIAL_TEMPO_KEY, initialTempo); outState.putDouble(INITIAL_PITCH_KEY, initialPitch); @@ -134,13 +158,9 @@ public void onSaveInstanceState(Bundle outState) { outState.putDouble(STEP_SIZE_KEY, getCurrentStepSize()); } - /*////////////////////////////////////////////////////////////////////////// - // Dialog - //////////////////////////////////////////////////////////////////////////*/ - @NonNull @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); setupControlViews(view); @@ -159,22 +179,18 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { return dialogBuilder.create(); } - /*////////////////////////////////////////////////////////////////////////// - // Control Views - //////////////////////////////////////////////////////////////////////////*/ - - private void setupControlViews(@NonNull View rootView) { + private void setupControlViews(@NonNull final View rootView) { setupHookingControl(rootView); setupSkipSilenceControl(rootView); setupTempoControl(rootView); setupPitchControl(rootView); - changeStepSize(stepSize); + setStepSize(stepSize); setupStepSizeSelector(rootView); } - private void setupTempoControl(@NonNull View rootView) { + private void setupTempoControl(@NonNull final View rootView) { tempoSlider = rootView.findViewById(R.id.tempoSeekbar); TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); @@ -182,12 +198,15 @@ private void setupTempoControl(@NonNull View rootView) { tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); - if (tempoCurrentText != null) + if (tempoCurrentText != null) { tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); - if (tempoMaximumText != null) + } + if (tempoMaximumText != null) { tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); - if (tempoMinimumText != null) + } + if (tempoMinimumText != null) { tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); + } if (tempoSlider != null) { tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); @@ -196,7 +215,7 @@ private void setupTempoControl(@NonNull View rootView) { } } - private void setupPitchControl(@NonNull View rootView) { + private void setupPitchControl(@NonNull final View rootView) { pitchSlider = rootView.findViewById(R.id.pitchSeekbar); TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); @@ -204,12 +223,15 @@ private void setupPitchControl(@NonNull View rootView) { pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); - if (pitchCurrentText != null) + if (pitchCurrentText != null) { pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - if (pitchMaximumText != null) + } + if (pitchMaximumText != null) { pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); - if (pitchMinimumText != null) + } + if (pitchMinimumText != null) { pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); + } if (pitchSlider != null) { pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); @@ -218,7 +240,7 @@ private void setupPitchControl(@NonNull View rootView) { } } - private void setupHookingControl(@NonNull View rootView) { + private void setupHookingControl(@NonNull final View rootView) { unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); if (unhookingCheckbox != null) { // restore whether pitch and tempo are unhooked or not @@ -242,7 +264,7 @@ private void setupHookingControl(@NonNull View rootView) { } } - private void setupSkipSilenceControl(@NonNull View rootView) { + private void setupSkipSilenceControl(@NonNull final View rootView) { skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox); if (skipSilenceCheckbox != null) { skipSilenceCheckbox.setChecked(initialSkipSilence); @@ -251,45 +273,53 @@ private void setupSkipSilenceControl(@NonNull View rootView) { } } + /*////////////////////////////////////////////////////////////////////////// + // Sliders + //////////////////////////////////////////////////////////////////////////*/ + private void setupStepSizeSelector(@NonNull final View rootView) { TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); - TextView stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent); - TextView stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent); + TextView stepSizeTwentyFivePercentText = rootView + .findViewById(R.id.stepSizeTwentyFivePercent); + TextView stepSizeOneHundredPercentText = rootView + .findViewById(R.id.stepSizeOneHundredPercent); if (stepSizeOnePercentText != null) { stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); - stepSizeOnePercentText.setOnClickListener(view -> - changeStepSize(STEP_ONE_PERCENT_VALUE)); + stepSizeOnePercentText + .setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE)); } if (stepSizeFivePercentText != null) { stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); - stepSizeFivePercentText.setOnClickListener(view -> - changeStepSize(STEP_FIVE_PERCENT_VALUE)); + stepSizeFivePercentText + .setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE)); } if (stepSizeTenPercentText != null) { stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); - stepSizeTenPercentText.setOnClickListener(view -> - changeStepSize(STEP_TEN_PERCENT_VALUE)); + stepSizeTenPercentText + .setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE)); } if (stepSizeTwentyFivePercentText != null) { - stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); - stepSizeTwentyFivePercentText.setOnClickListener(view -> - changeStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); + stepSizeTwentyFivePercentText + .setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); + stepSizeTwentyFivePercentText + .setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); } if (stepSizeOneHundredPercentText != null) { - stepSizeOneHundredPercentText.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); - stepSizeOneHundredPercentText.setOnClickListener(view -> - changeStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); + stepSizeOneHundredPercentText + .setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); + stepSizeOneHundredPercentText + .setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); } } - private void changeStepSize(final double stepSize) { + private void setStepSize(final double stepSize) { this.stepSize = stepSize; if (tempoStepUpText != null) { @@ -325,14 +355,11 @@ private void changeStepSize(final double stepSize) { } } - /*////////////////////////////////////////////////////////////////////////// - // Sliders - //////////////////////////////////////////////////////////////////////////*/ - private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() { return new SeekBar.OnSeekBarChangeListener() { @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { final double currentTempo = strategy.valueOf(progress); if (fromUser) { onTempoSliderUpdated(currentTempo); @@ -341,12 +368,12 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override - public void onStartTrackingTouch(SeekBar seekBar) { + public void onStartTrackingTouch(final SeekBar seekBar) { // Do Nothing. } @Override - public void onStopTrackingTouch(SeekBar seekBar) { + public void onStopTrackingTouch(final SeekBar seekBar) { // Do Nothing. } }; @@ -355,7 +382,8 @@ public void onStopTrackingTouch(SeekBar seekBar) { private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() { return new SeekBar.OnSeekBarChangeListener() { @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { final double currentPitch = strategy.valueOf(progress); if (fromUser) { // this change is first in chain onPitchSliderUpdated(currentPitch); @@ -364,19 +392,21 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override - public void onStartTrackingTouch(SeekBar seekBar) { + public void onStartTrackingTouch(final SeekBar seekBar) { // Do Nothing. } @Override - public void onStopTrackingTouch(SeekBar seekBar) { + public void onStopTrackingTouch(final SeekBar seekBar) { // Do Nothing. } }; } private void onTempoSliderUpdated(final double newTempo) { - if (unhookingCheckbox == null) return; + if (unhookingCheckbox == null) { + return; + } if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); } else { @@ -385,7 +415,9 @@ private void onTempoSliderUpdated(final double newTempo) { } private void onPitchSliderUpdated(final double newPitch) { - if (unhookingCheckbox == null) return; + if (unhookingCheckbox == null) { + return; + } if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); } else { @@ -398,45 +430,49 @@ private void setSliders(final double newValue) { setPitchSlider(newValue); } + /*////////////////////////////////////////////////////////////////////////// + // Helper + //////////////////////////////////////////////////////////////////////////*/ + private void setTempoSlider(final double newTempo) { - if (tempoSlider == null) return; + if (tempoSlider == null) { + return; + } tempoSlider.setProgress(strategy.progressOf(newTempo)); } private void setPitchSlider(final double newPitch) { - if (pitchSlider == null) return; + if (pitchSlider == null) { + return; + } pitchSlider.setProgress(strategy.progressOf(newPitch)); } - /*////////////////////////////////////////////////////////////////////////// - // Helper - //////////////////////////////////////////////////////////////////////////*/ - private void setCurrentPlaybackParameters() { setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence()); } - private void setPlaybackParameters(final double tempo, final double pitch, + private void setPlaybackParameters(final double newTempo, final double newPitch, final boolean skipSilence) { if (callback != null && tempoCurrentText != null && pitchCurrentText != null) { - if (DEBUG) Log.d(TAG, "Setting playback parameters to " + - "tempo=[" + tempo + "], " + - "pitch=[" + pitch + "]"); + if (DEBUG) { + Log.d(TAG, "Setting playback parameters to " + + "tempo=[" + newTempo + "], " + + "pitch=[" + newPitch + "]"); + } - tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); - pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); + tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo)); + pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch)); + callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence); } } private double getCurrentTempo() { - return tempoSlider == null ? tempo : strategy.valueOf( - tempoSlider.getProgress()); + return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress()); } private double getCurrentPitch() { - return pitchSlider == null ? pitch : strategy.valueOf( - pitchSlider.getProgress()); + return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); } private double getCurrentStepSize() { @@ -447,18 +483,8 @@ private boolean getCurrentSkipSilence() { return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); } - @NonNull - private static String getStepUpPercentString(final double percent) { - return STEP_UP_SIGN + getPercentString(percent); - } - - @NonNull - private static String getStepDownPercentString(final double percent) { - return STEP_DOWN_SIGN + getPercentString(percent); - } - - @NonNull - private static String getPercentString(final double percent) { - return PlayerHelper.formatPitch(percent); + public interface Callback { + void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, + boolean playbackSkipSilence); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 5aa331dc5..5fea4761b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -24,30 +24,33 @@ public class PlayerDataSource { private final DataSource.Factory cacheDataSourceFactory; private final DataSource.Factory cachelessDataSourceFactory; - public PlayerDataSource(@NonNull final Context context, - @NonNull final String userAgent, + public PlayerDataSource(@NonNull final Context context, @NonNull final String userAgent, @NonNull final TransferListener transferListener) { cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener); - cachelessDataSourceFactory = new DefaultDataSourceFactory(context, userAgent, transferListener); + cachelessDataSourceFactory + = new DefaultDataSourceFactory(context, userAgent, transferListener); } public SsMediaSource.Factory getLiveSsMediaSourceFactory() { return new SsMediaSource.Factory(new DefaultSsChunkSource.Factory( cachelessDataSourceFactory), cachelessDataSourceFactory) - .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)) + .setLoadErrorHandlingPolicy( + new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)) .setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS); } public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() { return new HlsMediaSource.Factory(cachelessDataSourceFactory) .setAllowChunklessPreparation(true) - .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); + .setLoadErrorHandlingPolicy( + new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); } public DashMediaSource.Factory getLiveDashMediaSourceFactory() { return new DashMediaSource.Factory(new DefaultDashChunkSource.Factory( cachelessDataSourceFactory), cachelessDataSourceFactory) - .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)) + .setLoadErrorHandlingPolicy( + new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)) .setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS, true); } @@ -67,10 +70,12 @@ public DashMediaSource.Factory getDashMediaSourceFactory() { public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() { return new ProgressiveMediaSource.Factory(cacheDataSourceFactory) - .setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY)); + .setLoadErrorHandlingPolicy( + new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY)); } - public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory(@NonNull final String key) { + public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory( + @NonNull final String key) { return getExtractorMediaSourceFactory().setCustomCacheKey(key); } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 5ca02980d..bc8955e74 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -4,10 +4,11 @@ import android.content.SharedPreferences; import android.os.Build; import android.preference.PreferenceManager; +import android.view.accessibility.CaptioningManager; + import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.view.accessibility.CaptioningManager; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.text.CaptionStyleCompat; @@ -48,51 +49,49 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -public class PlayerHelper { - private PlayerHelper() {} +public final class PlayerHelper { + private static final StringBuilder STRING_BUILDER = new StringBuilder(); + private static final Formatter STRING_FORMATTER + = new Formatter(STRING_BUILDER, Locale.getDefault()); + private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); + private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); - private static final StringBuilder stringBuilder = new StringBuilder(); - private static final Formatter stringFormatter = new Formatter(stringBuilder, Locale.getDefault()); - private static final NumberFormat speedFormatter = new DecimalFormat("0.##x"); - private static final NumberFormat pitchFormatter = new DecimalFormat("##%"); + private PlayerHelper() { } - @Retention(SOURCE) - @IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND, - MINIMIZE_ON_EXIT_MODE_POPUP}) - public @interface MinimizeMode { - int MINIMIZE_ON_EXIT_MODE_NONE = 0; - int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1; - int MINIMIZE_ON_EXIT_MODE_POPUP = 2; - } - //////////////////////////////////////////////////////////////////////////// - // Exposed helpers - //////////////////////////////////////////////////////////////////////////// - - public static String getTimeString(int milliSeconds) { + public static String getTimeString(final int milliSeconds) { int seconds = (milliSeconds % 60000) / 1000; int minutes = (milliSeconds % 3600000) / 60000; int hours = (milliSeconds % 86400000) / 3600000; int days = (milliSeconds % (86400000 * 7)) / 86400000; - stringBuilder.setLength(0); - return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString() - : hours > 0 ? stringFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() - : stringFormatter.format("%02d:%02d", minutes, seconds).toString(); + STRING_BUILDER.setLength(0); + return days > 0 + ? STRING_FORMATTER.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds) + .toString() + : hours > 0 + ? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds).toString() + : STRING_FORMATTER.format("%02d:%02d", minutes, seconds).toString(); } + //////////////////////////////////////////////////////////////////////////// + // Exposed helpers + //////////////////////////////////////////////////////////////////////////// - public static String formatSpeed(double speed) { - return speedFormatter.format(speed); + public static String formatSpeed(final double speed) { + return SPEED_FORMATTER.format(speed); } - public static String formatPitch(double pitch) { - return pitchFormatter.format(pitch); + public static String formatPitch(final double pitch) { + return PITCH_FORMATTER.format(pitch); } public static String subtitleMimeTypesOf(final MediaFormat format) { switch (format) { - case VTT: return MimeTypes.TEXT_VTT; - case TTML: return MimeTypes.APPLICATION_TTML; - default: throw new IllegalArgumentException("Unrecognized mime type: " + format.name()); + case VTT: + return MimeTypes.TEXT_VTT; + case TTML: + return MimeTypes.APPLICATION_TTML; + default: + throw new IllegalArgumentException("Unrecognized mime type: " + format.name()); } } @@ -100,42 +99,55 @@ public static String subtitleMimeTypesOf(final MediaFormat format) { public static String captionLanguageOf(@NonNull final Context context, @NonNull final SubtitlesStream subtitles) { final String displayName = subtitles.getDisplayLanguageName(); - return displayName + (subtitles.isAutoGenerated() ? " (" + context.getString(R.string.caption_auto_generated)+ ")" : ""); + return displayName + (subtitles.isAutoGenerated() + ? " (" + context.getString(R.string.caption_auto_generated) + ")" : ""); } @NonNull public static String resizeTypeOf(@NonNull final Context context, @AspectRatioFrameLayout.ResizeMode final int resizeMode) { switch (resizeMode) { - case RESIZE_MODE_FIT: return context.getResources().getString(R.string.resize_fit); - case RESIZE_MODE_FILL: return context.getResources().getString(R.string.resize_fill); - case RESIZE_MODE_ZOOM: return context.getResources().getString(R.string.resize_zoom); - default: throw new IllegalArgumentException("Unrecognized resize mode: " + resizeMode); + case RESIZE_MODE_FIT: + return context.getResources().getString(R.string.resize_fit); + case RESIZE_MODE_FILL: + return context.getResources().getString(R.string.resize_fill); + case RESIZE_MODE_ZOOM: + return context.getResources().getString(R.string.resize_zoom); + default: + throw new IllegalArgumentException("Unrecognized resize mode: " + resizeMode); } } @NonNull - public static String cacheKeyOf(@NonNull final StreamInfo info, @NonNull VideoStream video) { + public static String cacheKeyOf(@NonNull final StreamInfo info, + @NonNull final VideoStream video) { return info.getUrl() + video.getResolution() + video.getFormat().getName(); } @NonNull - public static String cacheKeyOf(@NonNull final StreamInfo info, @NonNull AudioStream audio) { + public static String cacheKeyOf(@NonNull final StreamInfo info, + @NonNull final AudioStream audio) { return info.getUrl() + audio.getAverageBitrate() + audio.getFormat().getName(); } /** * Given a {@link StreamInfo} and the existing queue items, provide the * {@link SinglePlayQueue} consisting of the next video for auto queuing. - *

+ *

* This method detects and prevents cycle by naively checking if a * candidate next video's url already exists in the existing items. - *

+ *

+ *

* To select the next video, {@link StreamInfo#getNextVideo()} is first * checked. If it is nonnull and is not part of the existing items, then * it will be used as the next video. Otherwise, an random item with * non-repeating url will be selected from the {@link StreamInfo#getRelatedStreams()}. - * */ + *

+ * + * @param info currently playing stream + * @param existingItems existing items in the queue + * @return {@link SinglePlayQueue} with the next stream to queue + */ @Nullable public static PlayQueue autoQueueOf(@NonNull final StreamInfo info, @NonNull final List existingItems) { @@ -150,7 +162,9 @@ public static PlayQueue autoQueueOf(@NonNull final StreamInfo info, } final List relatedItems = info.getRelatedStreams(); - if (relatedItems == null) return null; + if (relatedItems == null) { + return null; + } List autoQueueItems = new ArrayList<>(); for (final InfoItem item : info.getRelatedStreams()) { @@ -159,17 +173,18 @@ public static PlayQueue autoQueueOf(@NonNull final StreamInfo info, } } Collections.shuffle(autoQueueItems); - return autoQueueItems.isEmpty() ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0)); + return autoQueueItems.isEmpty() + ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0)); } - //////////////////////////////////////////////////////////////////////////// - // Settings Resolution - //////////////////////////////////////////////////////////////////////////// - public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { return isResumeAfterAudioFocusGain(context, false); } + //////////////////////////////////////////////////////////////////////////// + // Settings Resolution + //////////////////////////////////////////////////////////////////////////// + public static boolean isVolumeGestureEnabled(@NonNull final Context context) { return isVolumeGestureEnabled(context, true); } @@ -204,44 +219,43 @@ public static int getMinimizeOnExitAction(@NonNull final Context context) { @NonNull public static SeekParameters getSeekParameters(@NonNull final Context context) { - return isUsingInexactSeek(context) ? - SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT; + return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT; } - public static long getPreferredCacheSize(@NonNull final Context context) { + public static long getPreferredCacheSize() { return 64 * 1024 * 1024L; } - public static long getPreferredFileSize(@NonNull final Context context) { + public static long getPreferredFileSize() { return 512 * 1024L; } /** - * Returns the number of milliseconds the player buffers for before starting playback. - * */ - public static int getPlaybackStartBufferMs(@NonNull final Context context) { + * @return the number of milliseconds the player buffers for before starting playback + */ + public static int getPlaybackStartBufferMs() { return 500; } /** - * Returns the minimum number of milliseconds the player always buffers to after starting - * playback. - * */ - public static int getPlaybackMinimumBufferMs(@NonNull final Context context) { + * @return the minimum number of milliseconds the player always buffers to + * after starting playback. + */ + public static int getPlaybackMinimumBufferMs() { return 25000; } /** - * Returns the maximum/optimal number of milliseconds the player will buffer to once the buffer - * hits the point of {@link #getPlaybackMinimumBufferMs(Context)}. - * */ - public static int getPlaybackOptimalBufferMs(@NonNull final Context context) { + * @return the maximum/optimal number of milliseconds the player will buffer to once the buffer + * hits the point of {@link #getPlaybackMinimumBufferMs()}. + */ + public static int getPlaybackOptimalBufferMs() { return 60000; } public static TrackSelection.Factory getQualitySelector(@NonNull final Context context) { return new AdaptiveTrackSelection.Factory( - /*bufferDurationRequiredForQualityIncrease=*/1000, + 1000, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION); @@ -257,7 +271,9 @@ public static int getTossFlingVelocity(@NonNull final Context context) { @NonNull public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return CaptionStyleCompat.DEFAULT; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return CaptionStyleCompat.DEFAULT; + } final CaptioningManager captioningManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); @@ -269,14 +285,26 @@ public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) } /** - * System font scaling: - * Very small - 0.25f, Small - 0.5f, Normal - 1.0f, Large - 1.5f, Very Large - 2.0f - * */ + * Get scaling for captions based on system font scaling. + *

Options:

+ *
    + *
  • Very small: 0.25f
  • + *
  • Small: 0.5f
  • + *
  • Normal: 1.0f
  • + *
  • Large: 1.5f
  • + *
  • Very large: 2.0f
  • + *
+ * + * @param context Android app context + * @return caption scaling + */ public static float getCaptionScale(@NonNull final Context context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return 1f; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return 1f; + } - final CaptioningManager captioningManager = (CaptioningManager) - context.getSystemService(Context.CAPTIONING_SERVICE); + final CaptioningManager captioningManager + = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); if (captioningManager == null || !captioningManager.isEnabled()) { return 1f; } @@ -289,71 +317,96 @@ public static float getScreenBrightness(@NonNull final Context context) { return getScreenBrightness(context, -1); } - public static void setScreenBrightness(@NonNull final Context context, final float setScreenBrightness) { + public static void setScreenBrightness(@NonNull final Context context, + final float setScreenBrightness) { setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis()); } - //////////////////////////////////////////////////////////////////////////// - // Private helpers - //////////////////////////////////////////////////////////////////////////// - @NonNull private static SharedPreferences getPreferences(@NonNull final Context context) { return PreferenceManager.getDefaultSharedPreferences(context); } - private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context, final boolean b) { - return getPreferences(context).getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b); + //////////////////////////////////////////////////////////////////////////// + // Private helpers + //////////////////////////////////////////////////////////////////////////// + + private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context, + final boolean b) { + return getPreferences(context) + .getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b); } - private static boolean isVolumeGestureEnabled(@NonNull final Context context, final boolean b) { - return getPreferences(context).getBoolean(context.getString(R.string.volume_gesture_control_key), b); + private static boolean isVolumeGestureEnabled(@NonNull final Context context, + final boolean b) { + return getPreferences(context) + .getBoolean(context.getString(R.string.volume_gesture_control_key), b); } - private static boolean isBrightnessGestureEnabled(@NonNull final Context context, final boolean b) { - return getPreferences(context).getBoolean(context.getString(R.string.brightness_gesture_control_key), b); + private static boolean isBrightnessGestureEnabled(@NonNull final Context context, + final boolean b) { + return getPreferences(context) + .getBoolean(context.getString(R.string.brightness_gesture_control_key), b); } - private static boolean isRememberingPopupDimensions(@NonNull final Context context, final boolean b) { - return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b); + private static boolean isRememberingPopupDimensions(@NonNull final Context context, + final boolean b) { + return getPreferences(context) + .getBoolean(context.getString(R.string.popup_remember_size_pos_key), b); } private static boolean isUsingInexactSeek(@NonNull final Context context) { - return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), false); + return getPreferences(context) + .getBoolean(context.getString(R.string.use_inexact_seek_key), false); } private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) { return getPreferences(context).getBoolean(context.getString(R.string.auto_queue_key), b); } - private static void setScreenBrightness(@NonNull final Context context, final float screenBrightness, final long timestamp) { + private static void setScreenBrightness(@NonNull final Context context, + final float screenBrightness, final long timestamp) { SharedPreferences.Editor editor = getPreferences(context).edit(); editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness); editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp); editor.apply(); } - private static float getScreenBrightness(@NonNull final Context context, final float screenBrightness) { + private static float getScreenBrightness(@NonNull final Context context, + final float screenBrightness) { SharedPreferences sp = getPreferences(context); - long timestamp = sp.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0); - // hypothesis: 4h covers a viewing block, eg evening. External lightning conditions will change in the next + long timestamp = sp + .getLong(context.getString(R.string.screen_brightness_timestamp_key), 0); + // Hypothesis: 4h covers a viewing block, e.g. evening. + // External lightning conditions will change in the next // viewing block so we fall back to the default brightness if ((System.currentTimeMillis() - timestamp) > TimeUnit.HOURS.toMillis(4)) { return screenBrightness; } else { - return sp.getFloat(context.getString(R.string.screen_brightness_key), screenBrightness); + return sp + .getFloat(context.getString(R.string.screen_brightness_key), screenBrightness); } } private static String getMinimizeOnExitAction(@NonNull final Context context, final String key) { - return getPreferences(context).getString(context.getString(R.string.minimize_on_exit_key), - key); + return getPreferences(context) + .getString(context.getString(R.string.minimize_on_exit_key), key); } - private static SinglePlayQueue getAutoQueuedSinglePlayQueue(StreamInfoItem streamInfoItem) { + private static SinglePlayQueue getAutoQueuedSinglePlayQueue( + final StreamInfoItem streamInfoItem) { SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem); singlePlayQueue.getItem().setAutoQueued(true); return singlePlayQueue; } + + @Retention(SOURCE) + @IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND, + MINIMIZE_ON_EXIT_MODE_POPUP}) + public @interface MinimizeMode { + int MINIMIZE_ON_EXIT_MODE_NONE = 0; + int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1; + int MINIMIZE_ON_EXIT_MODE_POPUP = 2; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java index 498fb4a88..883d9bb4f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java @@ -4,13 +4,18 @@ public interface MediaSessionCallback { void onSkipToPrevious(); + void onSkipToNext(); - void onSkipToIndex(final int index); + + void onSkipToIndex(int index); int getCurrentPlayingIndex(); + int getQueueSize(); - MediaDescriptionCompat getQueueMetadata(final int index); + + MediaDescriptionCompat getQueueMetadata(int index); void onPlay(); + void onPause(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index ab0de08be..1f1152b62 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -20,7 +20,6 @@ import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; - public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { public static final int DEFAULT_MAX_QUEUE_SIZE = 10; @@ -40,17 +39,17 @@ public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession, } @Override - public long getSupportedQueueNavigatorActions(@Nullable Player player) { + public long getSupportedQueueNavigatorActions(@Nullable final Player player) { return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM; } @Override - public void onTimelineChanged(Player player) { + public void onTimelineChanged(final Player player) { publishFloatingQueueWindow(); } @Override - public void onCurrentWindowIndexChanged(Player player) { + public void onCurrentWindowIndexChanged(final Player player) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { publishFloatingQueueWindow(); @@ -60,22 +59,23 @@ public void onCurrentWindowIndexChanged(Player player) { } @Override - public long getActiveQueueItemId(@Nullable Player player) { + public long getActiveQueueItemId(@Nullable final Player player) { return callback.getCurrentPlayingIndex(); } @Override - public void onSkipToPrevious(Player player, ControlDispatcher controlDispatcher) { + public void onSkipToPrevious(final Player player, final ControlDispatcher controlDispatcher) { callback.onSkipToPrevious(); } @Override - public void onSkipToQueueItem(Player player, ControlDispatcher controlDispatcher, long id) { + public void onSkipToQueueItem(final Player player, final ControlDispatcher controlDispatcher, + final long id) { callback.onSkipToIndex((int) id); } @Override - public void onSkipToNext(Player player, ControlDispatcher controlDispatcher) { + public void onSkipToNext(final Player player, final ControlDispatcher controlDispatcher) { callback.onSkipToNext(); } @@ -102,7 +102,8 @@ private void publishFloatingQueueWindow() { } @Override - public boolean onCommand(Player player, ControlDispatcher controlDispatcher, String command, Bundle extras, ResultReceiver cb) { + public boolean onCommand(final Player player, final ControlDispatcher controlDispatcher, + final String command, final Bundle extras, final ResultReceiver cb) { return false; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java index b7f0638e3..21c99859c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java @@ -12,7 +12,7 @@ public PlayQueuePlaybackController(final MediaSessionCallback callback) { } @Override - public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) { + public boolean dispatchSetPlayWhenReady(final Player player, final boolean playWhenReady) { if (playWhenReady) { callback.onPlay(); } else { diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index b99047417..c09a44c08 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -1,8 +1,9 @@ package org.schabi.newpipe.player.mediasource; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.util.Log; import com.google.android.exoplayer2.source.BaseMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; @@ -15,32 +16,8 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource { private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode()); - - public static class FailedMediaSourceException extends Exception { - FailedMediaSourceException(String message) { - super(message); - } - - FailedMediaSourceException(Throwable cause) { - super(cause); - } - } - - public static final class MediaSourceResolutionException extends FailedMediaSourceException { - public MediaSourceResolutionException(String message) { - super(message); - } - } - - public static final class StreamInfoLoadException extends FailedMediaSourceException { - public StreamInfoLoadException(Throwable cause) { - super(cause); - } - } - private final PlayQueueItem playQueueItem; private final FailedMediaSourceException error; - private final long retryTimestamp; public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem, @@ -54,7 +31,10 @@ public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem, /** * Permanently fail the play queue item associated with this source, with no hope of retrying. * The error will always be propagated to ExoPlayer. - * */ + * + * @param playQueueItem play queue item + * @param error exception that was the reason to fail + */ public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem, @NonNull final FailedMediaSourceException error) { this.playQueueItem = playQueueItem; @@ -80,21 +60,21 @@ public void maybeThrowSourceInfoRefreshError() throws IOException { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { + public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator, + final long startPositionUs) { return null; } @Override - public void releasePeriod(MediaPeriod mediaPeriod) {} - + public void releasePeriod(final MediaPeriod mediaPeriod) { } @Override - protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { + protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) { Log.e(TAG, "Loading failed source: ", error); } @Override - protected void releaseSourceInternal() {} + protected void releaseSourceInternal() { } @Override public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, @@ -103,7 +83,29 @@ public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, } @Override - public boolean isStreamEqual(@NonNull PlayQueueItem stream) { + public boolean isStreamEqual(@NonNull final PlayQueueItem stream) { return playQueueItem == stream; } + + public static class FailedMediaSourceException extends Exception { + FailedMediaSourceException(final String message) { + super(message); + } + + FailedMediaSourceException(final Throwable cause) { + super(cause); + } + } + + public static final class MediaSourceResolutionException extends FailedMediaSourceException { + public MediaSourceResolutionException(final String message) { + super(message); + } + } + + public static final class StreamInfoLoadException extends FailedMediaSourceException { + public StreamInfoLoadException(final Throwable cause) { + super(cause); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java index 1519103c2..a4a6eb2ce 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.player.mediasource; import android.os.Handler; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -15,13 +16,11 @@ import java.io.IOException; public class LoadedMediaSource implements ManagedMediaSource { - private final MediaSource source; private final PlayQueueItem stream; private final long expireTimestamp; - public LoadedMediaSource(@NonNull final MediaSource source, - @NonNull final PlayQueueItem stream, + public LoadedMediaSource(@NonNull final MediaSource source, @NonNull final PlayQueueItem stream, final long expireTimestamp) { this.source = source; this.stream = stream; @@ -37,7 +36,8 @@ private boolean isExpired() { } @Override - public void prepareSource(SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener) { + public void prepareSource(final SourceInfoRefreshListener listener, + @Nullable final TransferListener mediaTransferListener) { source.prepareSource(listener, mediaTransferListener); } @@ -47,38 +47,40 @@ public void maybeThrowSourceInfoRefreshError() throws IOException { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { + public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator, + final long startPositionUs) { return source.createPeriod(id, allocator, startPositionUs); } @Override - public void releasePeriod(MediaPeriod mediaPeriod) { + public void releasePeriod(final MediaPeriod mediaPeriod) { source.releasePeriod(mediaPeriod); } @Override - public void releaseSource(SourceInfoRefreshListener listener) { + public void releaseSource(final SourceInfoRefreshListener listener) { source.releaseSource(listener); } @Override - public void addEventListener(Handler handler, MediaSourceEventListener eventListener) { + public void addEventListener(final Handler handler, + final MediaSourceEventListener eventListener) { source.addEventListener(handler, eventListener); } @Override - public void removeEventListener(MediaSourceEventListener eventListener) { + public void removeEventListener(final MediaSourceEventListener eventListener) { source.removeEventListener(eventListener); } @Override - public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity, + public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, final boolean isInterruptable) { return newIdentity != stream || (isInterruptable && isExpired()); } @Override - public boolean isStreamEqual(@NonNull PlayQueueItem stream) { - return this.stream == stream; + public boolean isStreamEqual(@NonNull final PlayQueueItem otherStream) { + return this.stream == otherStream; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java index b180ca9f2..9d6b94893 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java @@ -10,18 +10,21 @@ public interface ManagedMediaSource extends MediaSource { /** * Determines whether or not this {@link ManagedMediaSource} can be replaced. * - * @param newIdentity a stream the {@link ManagedMediaSource} should encapsulate over, if - * it is different from the existing stream in the - * {@link ManagedMediaSource}, then it should be replaced. + * @param newIdentity a stream the {@link ManagedMediaSource} should encapsulate over, if + * it is different from the existing stream in the + * {@link ManagedMediaSource}, then it should be replaced. * @param isInterruptable specifies if this {@link ManagedMediaSource} potentially * being played. - * */ - boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, - final boolean isInterruptable); + * @return whether this could be replaces + */ + boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity, boolean isInterruptable); /** * Determines if the {@link PlayQueueItem} is the one the * {@link ManagedMediaSource} encapsulates over. - * */ - boolean isStreamEqual(@NonNull final PlayQueueItem stream); + * + * @param stream play queue item to check + * @return whether this source is for the specified stream + */ + boolean isStreamEqual(@NonNull PlayQueueItem stream); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java index 76f097665..582eb31ca 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSourcePlaylist.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.mediasource; + import android.os.Handler; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -7,7 +9,8 @@ import com.google.android.exoplayer2.source.ShuffleOrder; public class ManagedMediaSourcePlaylist { - @NonNull private final ConcatenatingMediaSource internalSource; + @NonNull + private final ConcatenatingMediaSource internalSource; public ManagedMediaSourcePlaylist() { internalSource = new ConcatenatingMediaSource(/*isPlaylistAtomic=*/false, @@ -25,11 +28,14 @@ public int size() { /** * Returns the {@link ManagedMediaSource} at the given index of the playlist. * If the index is invalid, then null is returned. - * */ + * + * @param index index of {@link ManagedMediaSource} to get from the playlist + * @return the {@link ManagedMediaSource} at the given index of the playlist + */ @Nullable public ManagedMediaSource get(final int index) { - return (index < 0 || index >= size()) ? - null : (ManagedMediaSource) internalSource.getMediaSource(index); + return (index < 0 || index >= size()) + ? null : (ManagedMediaSource) internalSource.getMediaSource(index); } @NonNull @@ -46,15 +52,17 @@ public ConcatenatingMediaSource getParentMediaSource() { * {@link PlaceholderMediaSource}. * * @see #append(ManagedMediaSource) - * */ + */ public synchronized void expand() { append(new PlaceholderMediaSource()); } /** * Appends a {@link ManagedMediaSource} to the end of {@link ConcatenatingMediaSource}. + * * @see ConcatenatingMediaSource#addMediaSource - * */ + * @param source {@link ManagedMediaSource} to append + */ public synchronized void append(@NonNull final ManagedMediaSource source) { internalSource.addMediaSource(source); } @@ -62,10 +70,14 @@ public synchronized void append(@NonNull final ManagedMediaSource source) { /** * Removes a {@link ManagedMediaSource} from {@link ConcatenatingMediaSource} * at the given index. If this index is out of bound, then the removal is ignored. + * * @see ConcatenatingMediaSource#removeMediaSource(int) - * */ + * @param index of {@link ManagedMediaSource} to be removed + */ public synchronized void remove(final int index) { - if (index < 0 || index > internalSource.getSize()) return; + if (index < 0 || index > internalSource.getSize()) { + return; + } internalSource.removeMediaSource(index); } @@ -74,11 +86,18 @@ public synchronized void remove(final int index) { * Moves a {@link ManagedMediaSource} in {@link ConcatenatingMediaSource} * from the given source index to the target index. If either index is out of bound, * then the call is ignored. + * * @see ConcatenatingMediaSource#moveMediaSource(int, int) - * */ + * @param source original index of {@link ManagedMediaSource} + * @param target new index of {@link ManagedMediaSource} + */ public synchronized void move(final int source, final int target) { - if (source < 0 || target < 0) return; - if (source >= internalSource.getSize() || target >= internalSource.getSize()) return; + if (source < 0 || target < 0) { + return; + } + if (source >= internalSource.getSize() || target >= internalSource.getSize()) { + return; + } internalSource.moveMediaSource(source, target); } @@ -86,20 +105,30 @@ public synchronized void move(final int source, final int target) { /** * Invalidates the {@link ManagedMediaSource} at the given index by replacing it * with a {@link PlaceholderMediaSource}. + * * @see #update(int, ManagedMediaSource, Handler, Runnable) - * */ + * @param index index of {@link ManagedMediaSource} to invalidate + * @param handler the {@link Handler} to run {@code finalizingAction} + * @param finalizingAction a {@link Runnable} which is executed immediately + * after the media source has been removed from the playlist + */ public synchronized void invalidate(final int index, @Nullable final Handler handler, @Nullable final Runnable finalizingAction) { - if (get(index) instanceof PlaceholderMediaSource) return; + if (get(index) instanceof PlaceholderMediaSource) { + return; + } update(index, new PlaceholderMediaSource(), handler, finalizingAction); } /** * Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource} * at the given index with a given {@link ManagedMediaSource}. + * * @see #update(int, ManagedMediaSource, Handler, Runnable) - * */ + * @param index index of {@link ManagedMediaSource} to update + * @param source new {@link ManagedMediaSource} to use + */ public synchronized void update(final int index, @NonNull final ManagedMediaSource source) { update(index, source, null, /*doNothing=*/null); } @@ -108,13 +137,21 @@ public synchronized void update(final int index, @NonNull final ManagedMediaSour * Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource} * at the given index with a given {@link ManagedMediaSource}. If the index is out of bound, * then the replacement is ignored. + * * @see ConcatenatingMediaSource#addMediaSource * @see ConcatenatingMediaSource#removeMediaSource(int, Handler, Runnable) - * */ + * @param index index of {@link ManagedMediaSource} to update + * @param source new {@link ManagedMediaSource} to use + * @param handler the {@link Handler} to run {@code finalizingAction} + * @param finalizingAction a {@link Runnable} which is executed immediately + * after the media source has been removed from the playlist + */ public synchronized void update(final int index, @NonNull final ManagedMediaSource source, @Nullable final Handler handler, @Nullable final Runnable finalizingAction) { - if (index < 0 || index >= internalSource.getSize()) return; + if (index < 0 || index >= internalSource.getSize()) { + return; + } // Add and remove are sequential on the same thread, therefore here, the exoplayer // message queue must receive and process add before remove, effectively treating them diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java index 48179aed5..f73a219d7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java @@ -12,20 +12,32 @@ public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource { // Do nothing, so this will stall the playback - @Override public void maybeThrowSourceInfoRefreshError() {} - @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return null; } - @Override public void releasePeriod(MediaPeriod mediaPeriod) {} - @Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {} - @Override protected void releaseSourceInternal() {} + @Override + public void maybeThrowSourceInfoRefreshError() { } + + @Override + public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator, + final long startPositionUs) { + return null; + } + + @Override + public void releasePeriod(final MediaPeriod mediaPeriod) { } + + @Override + protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) { } + + @Override + protected void releaseSourceInternal() { } @Override - public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity, + public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, final boolean isInterruptable) { return true; } @Override - public boolean isStreamEqual(@NonNull PlayQueueItem stream) { + public boolean isStreamEqual(@NonNull final PlayQueueItem stream) { return false; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java index 7b55629b8..0154716e0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java @@ -27,25 +27,31 @@ public void onSkipToNext() { } @Override - public void onSkipToIndex(int index) { - if (player.getPlayQueue() == null) return; + public void onSkipToIndex(final int index) { + if (player.getPlayQueue() == null) { + return; + } player.onSelected(player.getPlayQueue().getItem(index)); } @Override public int getCurrentPlayingIndex() { - if (player.getPlayQueue() == null) return -1; + if (player.getPlayQueue() == null) { + return -1; + } return player.getPlayQueue().getIndex(); } @Override public int getQueueSize() { - if (player.getPlayQueue() == null) return -1; + if (player.getPlayQueue() == null) { + return -1; + } return player.getPlayQueue().size(); } @Override - public MediaDescriptionCompat getQueueMetadata(int index) { + public MediaDescriptionCompat getQueueMetadata(final int index) { if (player.getPlayQueue() == null || player.getPlayQueue().getItem(index) == null) { return null; } @@ -60,13 +66,17 @@ public MediaDescriptionCompat getQueueMetadata(int index) { Bundle additionalMetadata = new Bundle(); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); descriptionBuilder.setExtras(additionalMetadata); final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); - if (thumbnailUri != null) descriptionBuilder.setIconUri(thumbnailUri); + if (thumbnailUri != null) { + descriptionBuilder.setIconUri(thumbnailUri); + } return descriptionBuilder.build(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java index d51cf630d..0c4e7b2d0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java @@ -7,7 +7,6 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -18,18 +17,22 @@ /** * This class allows irregular text language labels for use when selecting text captions and * is mostly a copy-paste from {@link DefaultTrackSelector}. - * + *

* This is a hack and should be removed once ExoPlayer fixes language normalization to accept * a broader set of languages. - * */ + *

+ */ public class CustomTrackSelector extends DefaultTrackSelector { - private String preferredTextLanguage; - public CustomTrackSelector(TrackSelection.Factory adaptiveTrackSelectionFactory) { + public CustomTrackSelector(final TrackSelection.Factory adaptiveTrackSelectionFactory) { super(adaptiveTrackSelectionFactory); } + private static boolean formatHasLanguage(final Format format, final String language) { + return language != null && TextUtils.equals(language, format.language); + } + public String getPreferredTextLanguage() { return preferredTextLanguage; } @@ -42,18 +45,11 @@ public void setPreferredTextLanguage(@NonNull final String label) { } } - private static boolean formatHasLanguage(Format format, String language) { - return language != null && TextUtils.equals(language, format.language); - } - @Override @Nullable protected Pair selectTextTrack( - TrackGroupArray groups, - int[][] formatSupport, - Parameters params, - @Nullable String selectedAudioLanguage) - throws ExoPlaybackException { + final TrackGroupArray groups, final int[][] formatSupport, final Parameters params, + @Nullable final String selectedAudioLanguage) { TrackGroup selectedGroup = null; int selectedTrackIndex = C.INDEX_UNSET; int newPipeTrackScore = 0; @@ -65,17 +61,16 @@ protected Pair selectTextTrack( if (isSupported(trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); - TextTrackScore trackScore = - new TextTrackScore( - format, params, trackFormatSupport[trackIndex], selectedAudioLanguage); + TextTrackScore trackScore = new TextTrackScore(format, params, + trackFormatSupport[trackIndex], selectedAudioLanguage); if (formatHasLanguage(format, preferredTextLanguage)) { selectedGroup = trackGroup; selectedTrackIndex = trackIndex; selectedTrackScore = trackScore; // found user selected match (perfect!) break; - } else if (trackScore.isWithinConstraints - && (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0)) { + } else if (trackScore.isWithinConstraints && (selectedTrackScore == null + || trackScore.compareTo(selectedTrackScore) > 0)) { selectedGroup = trackGroup; selectedTrackIndex = trackIndex; selectedTrackScore = trackScore; @@ -83,10 +78,8 @@ protected Pair selectTextTrack( } } } - return selectedGroup == null - ? null - : Pair.create( - new TrackSelection.Definition(selectedGroup, selectedTrackIndex), - Assertions.checkNotNull(selectedTrackScore)); + return selectedGroup == null ? null + : Pair.create(new TrackSelection.Definition(selectedGroup, selectedTrackIndex), + Assertions.checkNotNull(selectedTrackScore)); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index e4cef8c5c..7bc9c34cc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -1,9 +1,11 @@ package org.schabi.newpipe.player.playback; + import android.os.Handler; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.collection.ArraySet; -import android.util.Log; import com.google.android.exoplayer2.source.MediaSource; @@ -42,8 +44,6 @@ import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG; public class MediaSourceManager { - @NonNull private final String TAG = "MediaSourceManager@" + hashCode(); - /** * Determines how many streams before and after the current stream should be loaded. * The default value (1) ensures seamless playback under typical network settings. @@ -52,26 +52,37 @@ public class MediaSourceManager { * streams before will only be cached for future usage. * * @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource) - * */ - private final static int WINDOW_SIZE = 1; - - @NonNull private final PlaybackListener playbackListener; - @NonNull private final PlayQueue playQueue; - + */ + private static final int WINDOW_SIZE = 1; + /** + * Determines the maximum number of disposables allowed in the {@link #loaderReactor}. + * Once exceeded, new calls to {@link #loadImmediate()} will evict all disposables in the + * {@link #loaderReactor} in order to load a new set of items. + * + * @see #loadImmediate() + * @see #maybeLoadItem(PlayQueueItem) + */ + private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1; + @NonNull + private final String TAG = "MediaSourceManager@" + hashCode(); + @NonNull + private final PlaybackListener playbackListener; + @NonNull + private final PlayQueue playQueue; /** * Determines the gap time between the playback position and the playback duration which * the {@link #getEdgeIntervalSignal()} begins to request loading. * * @see #progressUpdateIntervalMillis - * */ + */ private final long playbackNearEndGapMillis; /** * Determines the interval which the {@link #getEdgeIntervalSignal()} waits for between * each request for loading, once {@link #playbackNearEndGapMillis} has reached. - * */ + */ private final long progressUpdateIntervalMillis; - @NonNull private final Observable nearEndIntervalSignal; - + @NonNull + private final Observable nearEndIntervalSignal; /** * Process only the last load order when receiving a stream of load orders (lessens I/O). *

@@ -80,34 +91,28 @@ public class MediaSourceManager { * Not recommended to go below 100ms. * * @see #loadDebounced() - * */ + */ private final long loadDebounceMillis; - @NonNull private final Disposable debouncedLoader; - @NonNull private final PublishSubject debouncedSignal; - - @NonNull private Subscription playQueueReactor; - - /** - * Determines the maximum number of disposables allowed in the {@link #loaderReactor}. - * Once exceeded, new calls to {@link #loadImmediate()} will evict all disposables in the - * {@link #loaderReactor} in order to load a new set of items. - * - * @see #loadImmediate() - * @see #maybeLoadItem(PlayQueueItem) - * */ - private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1; - @NonNull private final CompositeDisposable loaderReactor; - @NonNull private final Set loadingItems; - - @NonNull private final AtomicBoolean isBlocked; - - @NonNull private ManagedMediaSourcePlaylist playlist; + @NonNull + private final Disposable debouncedLoader; + @NonNull + private final PublishSubject debouncedSignal; + @NonNull + private final CompositeDisposable loaderReactor; + @NonNull + private final Set loadingItems; + @NonNull + private final AtomicBoolean isBlocked; + @NonNull + private Subscription playQueueReactor; + @NonNull + private ManagedMediaSourcePlaylist playlist; private Handler removeMediaSourceHandler = new Handler(); public MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { - this(listener, playQueue, /*loadDebounceMillis=*/400L, + this(listener, playQueue, 400L, /*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS), /*progressUpdateIntervalMillis*/TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS)); } @@ -121,9 +126,9 @@ private MediaSourceManager(@NonNull final PlaybackListener listener, throw new IllegalArgumentException("Play Queue has not been initialized."); } if (playbackNearEndGapMillis < progressUpdateIntervalMillis) { - throw new IllegalArgumentException("Playback end gap=[" + playbackNearEndGapMillis + - " ms] must be longer than update interval=[ " + progressUpdateIntervalMillis + - " ms] for them to be useful."); + throw new IllegalArgumentException("Playback end gap=[" + playbackNearEndGapMillis + + " ms] must be longer than update interval=[ " + progressUpdateIntervalMillis + + " ms] for them to be useful."); } this.playbackListener = listener; @@ -154,11 +159,50 @@ private MediaSourceManager(@NonNull final PlaybackListener listener, /*////////////////////////////////////////////////////////////////////////// // Exposed Methods //////////////////////////////////////////////////////////////////////////*/ + + /*////////////////////////////////////////////////////////////////////////// + // Manager Helpers + //////////////////////////////////////////////////////////////////////////*/ + @Nullable + private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) { + // The current item has higher priority + final int currentIndex = playQueue.getIndex(); + final PlayQueueItem currentItem = playQueue.getItem(currentIndex); + if (currentItem == null) { + return null; + } + + // The rest are just for seamless playback + // Although timeline is not updated prior to the current index, these sources are still + // loaded into the cache for faster retrieval at a potentially later time. + final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE); + final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1; + final int rightBound = Math.min(playQueue.size(), rightLimit); + final Set neighbors = new ArraySet<>( + playQueue.getStreams().subList(leftBound, rightBound)); + + // Do a round robin + final int excess = rightLimit - playQueue.size(); + if (excess >= 0) { + neighbors.addAll(playQueue.getStreams() + .subList(0, Math.min(playQueue.size(), excess))); + } + neighbors.remove(currentItem); + + return new ItemsToLoad(currentItem, neighbors); + } + + /*////////////////////////////////////////////////////////////////////////// + // Event Reactor + //////////////////////////////////////////////////////////////////////////*/ + /** * Dispose the manager and releases all message buses and loaders. - * */ + */ public void dispose() { - if (DEBUG) Log.d(TAG, "close() called."); + if (DEBUG) { + Log.d(TAG, "close() called."); + } debouncedSignal.onComplete(); debouncedLoader.dispose(); @@ -167,32 +211,32 @@ public void dispose() { loaderReactor.dispose(); } - /*////////////////////////////////////////////////////////////////////////// - // Event Reactor - //////////////////////////////////////////////////////////////////////////*/ - private Subscriber getReactor() { return new Subscriber() { @Override - public void onSubscribe(@NonNull Subscription d) { + public void onSubscribe(@NonNull final Subscription d) { playQueueReactor.cancel(); playQueueReactor = d; playQueueReactor.request(1); } @Override - public void onNext(@NonNull PlayQueueEvent playQueueMessage) { + public void onNext(@NonNull final PlayQueueEvent playQueueMessage) { onPlayQueueChanged(playQueueMessage); } @Override - public void onError(@NonNull Throwable e) {} + public void onError(@NonNull final Throwable e) { } @Override - public void onComplete() {} + public void onComplete() { } }; } + /*////////////////////////////////////////////////////////////////////////// + // Playback Locking + //////////////////////////////////////////////////////////////////////////*/ + private void onPlayQueueChanged(final PlayQueueEvent event) { if (playQueue.isEmpty() && playQueue.isComplete()) { playbackListener.onPlaybackShutdown(); @@ -254,29 +298,33 @@ private void onPlayQueueChanged(final PlayQueueEvent event) { playQueueReactor.request(1); } - /*////////////////////////////////////////////////////////////////////////// - // Playback Locking - //////////////////////////////////////////////////////////////////////////*/ - private boolean isPlayQueueReady() { final boolean isWindowLoaded = playQueue.size() - playQueue.getIndex() > WINDOW_SIZE; return playQueue.isComplete() || isWindowLoaded; } private boolean isPlaybackReady() { - if (playlist.size() != playQueue.size()) return false; + if (playlist.size() != playQueue.size()) { + return false; + } final ManagedMediaSource mediaSource = playlist.get(playQueue.getIndex()); - if (mediaSource == null) return false; + if (mediaSource == null) { + return false; + } final PlayQueueItem playQueueItem = playQueue.getItem(); return mediaSource.isStreamEqual(playQueueItem); } private void maybeBlock() { - if (DEBUG) Log.d(TAG, "maybeBlock() called."); + if (DEBUG) { + Log.d(TAG, "maybeBlock() called."); + } - if (isBlocked.get()) return; + if (isBlocked.get()) { + return; + } playbackListener.onPlaybackBlock(); resetSources(); @@ -284,8 +332,14 @@ private void maybeBlock() { isBlocked.set(true); } + /*////////////////////////////////////////////////////////////////////////// + // Metadata Synchronization + //////////////////////////////////////////////////////////////////////////*/ + private void maybeUnblock() { - if (DEBUG) Log.d(TAG, "maybeUnblock() called."); + if (DEBUG) { + Log.d(TAG, "maybeUnblock() called."); + } if (isBlocked.get()) { isBlocked.set(false); @@ -293,19 +347,23 @@ private void maybeUnblock() { } } - /*////////////////////////////////////////////////////////////////////////// - // Metadata Synchronization - //////////////////////////////////////////////////////////////////////////*/ - private void maybeSync() { - if (DEBUG) Log.d(TAG, "maybeSync() called."); + if (DEBUG) { + Log.d(TAG, "maybeSync() called."); + } final PlayQueueItem currentItem = playQueue.getItem(); - if (isBlocked.get() || currentItem == null) return; + if (isBlocked.get() || currentItem == null) { + return; + } playbackListener.onPlaybackSynchronize(currentItem); } + /*////////////////////////////////////////////////////////////////////////// + // MediaSource Loading + //////////////////////////////////////////////////////////////////////////*/ + private synchronized void maybeSynchronizePlayer() { if (isPlayQueueReady() && isPlaybackReady()) { maybeUnblock(); @@ -313,10 +371,6 @@ private synchronized void maybeSynchronizePlayer() { } } - /*////////////////////////////////////////////////////////////////////////// - // MediaSource Loading - //////////////////////////////////////////////////////////////////////////*/ - private Observable getEdgeIntervalSignal() { return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) @@ -337,9 +391,13 @@ private void loadDebounced() { } private void loadImmediate() { - if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called"); + if (DEBUG) { + Log.d(TAG, "MediaSource - loadImmediate() called"); + } final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue); - if (itemsToLoad == null) return; + if (itemsToLoad == null) { + return; + } // Evict the previous items being loaded to free up memory, before start loading new ones maybeClearLoaders(); @@ -351,12 +409,18 @@ private void loadImmediate() { } private void maybeLoadItem(@NonNull final PlayQueueItem item) { - if (DEBUG) Log.d(TAG, "maybeLoadItem() called."); - if (playQueue.indexOf(item) >= playlist.size()) return; + if (DEBUG) { + Log.d(TAG, "maybeLoadItem() called."); + } + if (playQueue.indexOf(item) >= playlist.size()) { + return; + } if (!loadingItems.contains(item) && isCorrectionNeeded(item)) { - if (DEBUG) Log.d(TAG, "MediaSource - Loading=[" + item.getTitle() + - "] with url=[" + item.getUrl() + "]"); + if (DEBUG) { + Log.d(TAG, "MediaSource - Loading=[" + item.getTitle() + "] " + + "with url=[" + item.getUrl() + "]"); + } loadingItems.add(item); final Disposable loader = getLoadedMediaSource(item) @@ -371,16 +435,16 @@ private Single getLoadedMediaSource(@NonNull final PlayQueue return stream.getStream().map(streamInfo -> { final MediaSource source = playbackListener.sourceOf(stream, streamInfo); if (source == null) { - final String message = "Unable to resolve source from stream info." + - " URL: " + stream.getUrl() + - ", audio count: " + streamInfo.getAudioStreams().size() + - ", video count: " + streamInfo.getVideoOnlyStreams().size() + - streamInfo.getVideoStreams().size(); + final String message = "Unable to resolve source from stream info. " + + "URL: " + stream.getUrl() + ", " + + "audio count: " + streamInfo.getAudioStreams().size() + ", " + + "video count: " + streamInfo.getVideoOnlyStreams().size() + ", " + + streamInfo.getVideoStreams().size(); return new FailedMediaSource(stream, new MediaSourceResolutionException(message)); } - final long expiration = System.currentTimeMillis() + - ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId()); + final long expiration = System.currentTimeMillis() + + ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId()); return new LoadedMediaSource(source, stream, expiration); }).onErrorReturn(throwable -> new FailedMediaSource(stream, new StreamInfoLoadException(throwable))); @@ -388,17 +452,22 @@ private Single getLoadedMediaSource(@NonNull final PlayQueue private void onMediaSourceReceived(@NonNull final PlayQueueItem item, @NonNull final ManagedMediaSource mediaSource) { - if (DEBUG) Log.d(TAG, "MediaSource - Loaded=[" + item.getTitle() + - "] with url=[" + item.getUrl() + "]"); + if (DEBUG) { + Log.d(TAG, "MediaSource - Loaded=[" + item.getTitle() + + "] with url=[" + item.getUrl() + "]"); + } loadingItems.remove(item); final int itemIndex = playQueue.indexOf(item); // Only update the playlist timeline for items at the current index or after. if (isCorrectionNeeded(item)) { - if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " + - "title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]"); - playlist.update(itemIndex, mediaSource, removeMediaSourceHandler, this::maybeSynchronizePlayer); + if (DEBUG) { + Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " + + "title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]"); + } + playlist.update(itemIndex, mediaSource, removeMediaSourceHandler, + this::maybeSynchronizePlayer); } } @@ -407,17 +476,21 @@ private void onMediaSourceReceived(@NonNull final PlayQueueItem item, * {@link com.google.android.exoplayer2.source.ConcatenatingMediaSource} * for a given {@link PlayQueueItem} needs replacement, either due to gapless playback * readiness or playlist desynchronization. - *

+ *

* If the given {@link PlayQueueItem} is currently being played and is already loaded, * then correction is not only needed if the playlist is desynchronized. Otherwise, the * check depends on the status (e.g. expiration or placeholder) of the * {@link ManagedMediaSource}. - * */ + *

+ * + * @param item {@link PlayQueueItem} to check + * @return whether a correction is needed + */ private boolean isCorrectionNeeded(@NonNull final PlayQueueItem item) { final int index = playQueue.indexOf(item); final ManagedMediaSource mediaSource = playlist.get(index); return mediaSource != null && mediaSource.shouldBeReplacedWith(item, - /*mightBeInProgress=*/index != playQueue.getIndex()); + index != playQueue.getIndex()); } /** @@ -430,79 +503,62 @@ private boolean isCorrectionNeeded(@NonNull final PlayQueueItem item) { *

* Under both cases, {@link #maybeSync()} will be called to ensure the listener * is up-to-date. - * */ + */ private void maybeRenewCurrentIndex() { final int currentIndex = playQueue.getIndex(); final ManagedMediaSource currentSource = playlist.get(currentIndex); - if (currentSource == null) return; + if (currentSource == null) { + return; + } final PlayQueueItem currentItem = playQueue.getItem(); - if (!currentSource.shouldBeReplacedWith(currentItem, /*canInterruptOnRenew=*/true)) { + if (!currentSource.shouldBeReplacedWith(currentItem, true)) { maybeSynchronizePlayer(); return; } - if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " + - "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]"); + if (DEBUG) { + Log.d(TAG, "MediaSource - Reloading currently playing, " + + "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]"); + } playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate); } + /*////////////////////////////////////////////////////////////////////////// + // MediaSource Playlist Helpers + //////////////////////////////////////////////////////////////////////////*/ private void maybeClearLoaders() { - if (DEBUG) Log.d(TAG, "MediaSource - maybeClearLoaders() called."); - if (!loadingItems.contains(playQueue.getItem()) && - loaderReactor.size() > MAXIMUM_LOADER_SIZE) { + if (DEBUG) { + Log.d(TAG, "MediaSource - maybeClearLoaders() called."); + } + if (!loadingItems.contains(playQueue.getItem()) + && loaderReactor.size() > MAXIMUM_LOADER_SIZE) { loaderReactor.clear(); loadingItems.clear(); } } - /*////////////////////////////////////////////////////////////////////////// - // MediaSource Playlist Helpers - //////////////////////////////////////////////////////////////////////////*/ private void resetSources() { - if (DEBUG) Log.d(TAG, "resetSources() called."); + if (DEBUG) { + Log.d(TAG, "resetSources() called."); + } playlist = new ManagedMediaSourcePlaylist(); } private void populateSources() { - if (DEBUG) Log.d(TAG, "populateSources() called."); + if (DEBUG) { + Log.d(TAG, "populateSources() called."); + } while (playlist.size() < playQueue.size()) { playlist.expand(); } } - /*////////////////////////////////////////////////////////////////////////// - // Manager Helpers - //////////////////////////////////////////////////////////////////////////*/ - @Nullable - private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) { - // The current item has higher priority - final int currentIndex = playQueue.getIndex(); - final PlayQueueItem currentItem = playQueue.getItem(currentIndex); - if (currentItem == null) return null; - - // The rest are just for seamless playback - // Although timeline is not updated prior to the current index, these sources are still - // loaded into the cache for faster retrieval at a potentially later time. - final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE); - final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1; - final int rightBound = Math.min(playQueue.size(), rightLimit); - final Set neighbors = new ArraySet<>( - playQueue.getStreams().subList(leftBound,rightBound)); - - // Do a round robin - final int excess = rightLimit - playQueue.size(); - if (excess >= 0) { - neighbors.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess))); - } - neighbors.remove(currentItem); - - return new ItemsToLoad(currentItem, neighbors); - } - private static class ItemsToLoad { - @NonNull final private PlayQueueItem center; - @NonNull final private Collection neighbors; + @NonNull + private final PlayQueueItem center; + @NonNull + private final Collection neighbors; ItemsToLoad(@NonNull final PlayQueueItem center, @NonNull final Collection neighbors) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index 9682ea15e..0755bdd7a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -9,57 +9,72 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; public interface PlaybackListener { - /** * Called to check if the currently playing stream is approaching the end of its playback. * Implementation should return true when the current playback position is progressing within * timeToEndMillis or less to its playback during. - * + *

* May be called at any time. - * */ - boolean isApproachingPlaybackEdge(final long timeToEndMillis); + *

+ * + * @param timeToEndMillis + * @return whether the stream is approaching end of playback + */ + boolean isApproachingPlaybackEdge(long timeToEndMillis); /** * Called when the stream at the current queue index is not ready yet. * Signals to the listener to block the player from playing anything and notify the source * is now invalid. - * + *

* May be called at any time. - * */ + *

+ */ void onPlaybackBlock(); /** * Called when the stream at the current queue index is ready. * Signals to the listener to resume the player by preparing a new source. - * + *

* May be called only when the player is blocked. - * */ - void onPlaybackUnblock(final MediaSource mediaSource); + *

+ * + * @param mediaSource + */ + void onPlaybackUnblock(MediaSource mediaSource); /** * Called when the queue index is refreshed. * Signals to the listener to synchronize the player's window to the manager's * window. - * + *

* May be called anytime at any amount once unblock is called. - * */ - void onPlaybackSynchronize(@NonNull final PlayQueueItem item); + *

+ * + * @param item + */ + void onPlaybackSynchronize(@NonNull PlayQueueItem item); /** * Requests the listener to resolve a stream info into a media source * according to the listener's implementation (background, popup or main video player). - * + *

* May be called at any time. - * */ + *

+ * @param item + * @param info + * @return the corresponding {@link MediaSource} + */ @Nullable - MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info); + MediaSource sourceOf(PlayQueueItem item, StreamInfo info); /** * Called when the play queue can no longer to played or used. * Currently, this means the play queue is empty and complete. * Signals to the listener that it should shutdown. - * + *

* May be called at any time. - * */ + *

+ */ void onPlaybackShutdown(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 676c0ca72..9c77a6ef5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -16,24 +16,20 @@ import io.reactivex.disposables.Disposable; abstract class AbstractInfoPlayQueue extends PlayQueue { - boolean isInitial; - boolean isComplete; - final int serviceId; final String baseUrl; + boolean isInitial; + private boolean isComplete; String nextUrl; - transient Disposable fetchReactor; + private transient Disposable fetchReactor; AbstractInfoPlayQueue(final U item) { this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0); } - AbstractInfoPlayQueue(final int serviceId, - final String url, - final String nextPageUrl, - final List streams, - final int index) { + AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl, + final List streams, final int index) { super(index, extractListItems(streams)); this.baseUrl = url; @@ -44,7 +40,17 @@ abstract class AbstractInfoPlayQueue ext this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); } - abstract protected String getTag(); + private static List extractListItems(final List infos) { + List result = new ArrayList<>(); + for (final InfoItem stream : infos) { + if (stream instanceof StreamInfoItem) { + result.add(new PlayQueueItem((StreamInfoItem) stream)); + } + } + return result; + } + + protected abstract String getTag(); @Override public boolean isComplete() { @@ -54,8 +60,9 @@ public boolean isComplete() { SingleObserver getHeadListObserver() { return new SingleObserver() { @Override - public void onSubscribe(@NonNull Disposable d) { - if (isComplete || !isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) { + public void onSubscribe(@NonNull final Disposable d) { + if (isComplete || !isInitial || (fetchReactor != null + && !fetchReactor.isDisposed())) { d.dispose(); } else { fetchReactor = d; @@ -63,9 +70,11 @@ public void onSubscribe(@NonNull Disposable d) { } @Override - public void onSuccess(@NonNull T result) { + public void onSuccess(@NonNull final T result) { isInitial = false; - if (!result.hasNextPage()) isComplete = true; + if (!result.hasNextPage()) { + isComplete = true; + } nextUrl = result.getNextPageUrl(); append(extractListItems(result.getRelatedItems())); @@ -75,7 +84,7 @@ public void onSuccess(@NonNull T result) { } @Override - public void onError(@NonNull Throwable e) { + public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; append(); // Notify change @@ -86,8 +95,9 @@ public void onError(@NonNull Throwable e) { SingleObserver getNextPageObserver() { return new SingleObserver() { @Override - public void onSubscribe(@NonNull Disposable d) { - if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) { + public void onSubscribe(@NonNull final Disposable d) { + if (isComplete || isInitial || (fetchReactor != null + && !fetchReactor.isDisposed())) { d.dispose(); } else { fetchReactor = d; @@ -95,8 +105,10 @@ public void onSubscribe(@NonNull Disposable d) { } @Override - public void onSuccess(@NonNull ListExtractor.InfoItemsPage result) { - if (!result.hasNextPage()) isComplete = true; + public void onSuccess(@NonNull final ListExtractor.InfoItemsPage result) { + if (!result.hasNextPage()) { + isComplete = true; + } nextUrl = result.getNextPageUrl(); append(extractListItems(result.getItems())); @@ -106,7 +118,7 @@ public void onSuccess(@NonNull ListExtractor.InfoItemsPage result) { } @Override - public void onError(@NonNull Throwable e) { + public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; append(); // Notify change @@ -117,17 +129,9 @@ public void onError(@NonNull Throwable e) { @Override public void dispose() { super.dispose(); - if (fetchReactor != null) fetchReactor.dispose(); - fetchReactor = null; - } - - private static List extractListItems(final List infos) { - List result = new ArrayList<>(); - for (final InfoItem stream : infos) { - if (stream instanceof StreamInfoItem) { - result.add(new PlayQueueItem((StreamInfoItem) stream)); - } + if (fetchReactor != null) { + fetchReactor.dispose(); } - return result; + fetchReactor = null; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index fcb1e2819..84ef45242 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -1,8 +1,9 @@ package org.schabi.newpipe.player.playqueue; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.util.Log; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -32,22 +33,20 @@ /** * PlayQueue is responsible for keeping track of a list of streams and the index of * the stream that should be currently playing. - * + *

* This class contains basic manipulation of a playlist while also functions as a * message bus, providing all listeners with new updates to the play queue. - * + *

* This class can be serialized for passing intents, but in order to start the * message bus, it must be initialized. - * */ + */ public abstract class PlayQueue implements Serializable { - private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); - public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); - + private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); + @NonNull + private final AtomicInteger queueIndex; private ArrayList backup; private ArrayList streams; - @NonNull private final AtomicInteger queueIndex; - private transient BehaviorSubject eventBroadcast; private transient Flowable broadcastReceiver; private transient Subscription reportingReactor; @@ -65,9 +64,10 @@ public abstract class PlayQueue implements Serializable { /** * Initializes the play queue message buses. - * + *

* Also starts a self reporter for logging if debug mode is enabled. - * */ + *

+ */ public void init() { eventBroadcast = BehaviorSubject.create(); @@ -75,15 +75,21 @@ public void init() { .observeOn(AndroidSchedulers.mainThread()) .startWith(new InitEvent()); - if (DEBUG) broadcastReceiver.subscribe(getSelfReporter()); + if (DEBUG) { + broadcastReceiver.subscribe(getSelfReporter()); + } } /** * Dispose the play queue by stopping all message buses. - * */ + */ public void dispose() { - if (eventBroadcast != null) eventBroadcast.onComplete(); - if (reportingReactor != null) reportingReactor.cancel(); + if (eventBroadcast != null) { + eventBroadcast.onComplete(); + } + if (reportingReactor != null) { + reportingReactor.cancel(); + } eventBroadcast = null; broadcastReceiver = null; @@ -92,15 +98,18 @@ public void dispose() { /** * Checks if the queue is complete. - * + *

* A queue is complete if it has loaded all items in an external playlist * single stream or local queues are always complete. - * */ + *

+ * + * @return whether the queue is complete + */ public abstract boolean isComplete(); /** * Load partial queue in the background, does nothing if the queue is complete. - * */ + */ public abstract void fetch(); /*////////////////////////////////////////////////////////////////////////// @@ -108,32 +117,64 @@ public void dispose() { //////////////////////////////////////////////////////////////////////////*/ /** - * Returns the current index that should be played. - * */ + * @return the current index that should be played + */ public int getIndex() { return queueIndex.get(); } /** - * Returns the current item that should be played. - * */ + * Changes the current playing index to a new index. + *

+ * This method is guarded using in a circular manner for index exceeding the play queue size. + *

+ *

+ * Will emit a {@link SelectEvent} if the index is not the current playing index. + *

+ * + * @param index the index to be set + */ + public synchronized void setIndex(final int index) { + final int oldIndex = getIndex(); + + int newIndex = index; + if (index < 0) { + newIndex = 0; + } + if (index >= streams.size()) { + newIndex = isComplete() ? index % streams.size() : streams.size() - 1; + } + + queueIndex.set(newIndex); + broadcast(new SelectEvent(oldIndex, newIndex)); + } + + /** + * @return the current item that should be played + */ public PlayQueueItem getItem() { return getItem(getIndex()); } /** - * Returns the item at the given index. - * May throw {@link IndexOutOfBoundsException}. - * */ - public PlayQueueItem getItem(int index) { - if (index < 0 || index >= streams.size() || streams.get(index) == null) return null; + * @param index the index of the item to return + * @return the item at the given index + * @throws IndexOutOfBoundsException + */ + public PlayQueueItem getItem(final int index) { + if (index < 0 || index >= streams.size() || streams.get(index) == null) { + return null; + } return streams.get(index); } /** * Returns the index of the given item using referential equality. * May be null despite play queue contains identical item. - * */ + * + * @param item the item to find the index of + * @return the index of the given item + */ public int indexOf(@NonNull final PlayQueueItem item) { // referential equality, can't think of a better way to do this // todo: better than this @@ -141,70 +182,61 @@ public int indexOf(@NonNull final PlayQueueItem item) { } /** - * Returns the current size of play queue. - * */ + * @return the current size of play queue. + */ public int size() { return streams.size(); } /** * Checks if the play queue is empty. - * */ + * + * @return whether the play queue is empty + */ public boolean isEmpty() { return streams.isEmpty(); } /** * Determines if the current play queue is shuffled. - * */ + * + * @return whether the play queue is shuffled + */ public boolean isShuffled() { return backup != null; } /** - * Returns an immutable view of the play queue. - * */ + * @return an immutable view of the play queue + */ @NonNull public List getStreams() { return Collections.unmodifiableList(streams); } - /** - * Returns the play queue's update broadcast. - * May be null if the play queue message bus is not initialized. - * */ - @Nullable - public Flowable getBroadcastReceiver() { - return broadcastReceiver; - } - /*////////////////////////////////////////////////////////////////////////// // Write ops //////////////////////////////////////////////////////////////////////////*/ /** - * Changes the current playing index to a new index. - * - * This method is guarded using in a circular manner for index exceeding the play queue size. + * Returns the play queue's update broadcast. + * May be null if the play queue message bus is not initialized. * - * Will emit a {@link SelectEvent} if the index is not the current playing index. - * */ - public synchronized void setIndex(final int index) { - final int oldIndex = getIndex(); - - int newIndex = index; - if (index < 0) newIndex = 0; - if (index >= streams.size()) newIndex = isComplete() ? index % streams.size() : streams.size() - 1; - - queueIndex.set(newIndex); - broadcast(new SelectEvent(oldIndex, newIndex)); + * @return the play queue's update broadcast + */ + @Nullable + public Flowable getBroadcastReceiver() { + return broadcastReceiver; } /** * Changes the current playing index by an offset amount. - * + *

* Will emit a {@link SelectEvent} if offset is non-zero. - * */ + *

+ * + * @param offset the offset relative to the current index + */ public synchronized void offsetIndex(final int offset) { setIndex(getIndex() + offset); } @@ -213,19 +245,24 @@ public synchronized void offsetIndex(final int offset) { * Appends the given {@link PlayQueueItem}s to the current play queue. * * @see #append(List items) - * */ + * @param items {@link PlayQueueItem}s to append + */ public synchronized void append(@NonNull final PlayQueueItem... items) { append(Arrays.asList(items)); } /** * Appends the given {@link PlayQueueItem}s to the current play queue. - * + *

* If the play queue is shuffled, then append the items to the backup queue as is and * append the shuffle items to the play queue. - * + *

+ *

* Will emit a {@link AppendEvent} on any given context. - * */ + *

+ * + * @param items {@link PlayQueueItem}s to append + */ public synchronized void append(@NonNull final List items) { List itemList = new ArrayList<>(items); @@ -233,7 +270,8 @@ public synchronized void append(@NonNull final List items) { backup.addAll(itemList); Collections.shuffle(itemList); } - if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued() && !itemList.get(0).isAutoQueued()) { + if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued() + && !itemList.get(0).isAutoQueued()) { streams.remove(streams.size() - 1); } streams.addAll(itemList); @@ -243,14 +281,20 @@ public synchronized void append(@NonNull final List items) { /** * Removes the item at the given index from the play queue. - * + *

* The current playing index will decrement if it is greater than the index being removed. * On cases where the current playing index exceeds the playlist range, it is set to 0. - * + *

+ *

* Will emit a {@link RemoveEvent} if the index is within the play queue index range. - * */ + *

+ * + * @param index the index of the item to remove + */ public synchronized void remove(final int index) { - if (index >= streams.size() || index < 0) return; + if (index >= streams.size() || index < 0) { + return; + } removeInternal(index); broadcast(new RemoveEvent(index, getIndex())); } @@ -258,10 +302,13 @@ public synchronized void remove(final int index) { /** * Report an exception for the item at the current index in order and the course of action: * if the error can be skipped or the current item should be removed. - * + *

* This is done as a separate event as the underlying manager may have * different implementation regarding exceptions. - * */ + *

+ * + * @param skippable whether the error could be skipped + */ public synchronized void error(final boolean skippable) { final int index = getIndex(); @@ -284,29 +331,36 @@ private synchronized void removeInternal(final int removeIndex) { } else if (currentIndex >= size) { queueIndex.set(currentIndex % (size - 1)); - } else if (currentIndex == removeIndex && currentIndex == size - 1){ + } else if (currentIndex == removeIndex && currentIndex == size - 1) { queueIndex.set(0); } if (backup != null) { - final int backupIndex = backup.indexOf(getItem(removeIndex)); - backup.remove(backupIndex); + backup.remove(getItem(removeIndex)); } streams.remove(removeIndex); } /** * Moves a queue item at the source index to the target index. - * + *

* If the item being moved is the currently playing, then the current playing index is set * to that of the target. * If the moved item is not the currently playing and moves to an index AFTER the * current playing index, then the current playing index is decremented. * Vice versa if the an item after the currently playing is moved BEFORE. - * */ + *

+ * + * @param source the original index of the item + * @param target the new index of the item + */ public synchronized void move(final int source, final int target) { - if (source < 0 || target < 0) return; - if (source >= streams.size() || target >= streams.size()) return; + if (source < 0 || target < 0) { + return; + } + if (source >= streams.size() || target >= streams.size()) { + return; + } final int current = getIndex(); if (source == current) { @@ -325,11 +379,17 @@ public synchronized void move(final int source, final int target) { /** * Sets the recovery record of the item at the index. - * + *

* Broadcasts a recovery event. - * */ + *

+ * + * @param index index of the item + * @param position the recovery position + */ public synchronized void setRecovery(final int index, final long position) { - if (index < 0 || index >= streams.size()) return; + if (index < 0 || index >= streams.size()) { + return; + } streams.get(index).setRecoveryPosition(position); broadcast(new RecoveryEvent(index, position)); @@ -337,22 +397,27 @@ public synchronized void setRecovery(final int index, final long position) { /** * Revoke the recovery record of the item at the index. - * + *

* Broadcasts a recovery event. - * */ + *

+ * + * @param index index of the item + */ public synchronized void unsetRecovery(final int index) { setRecovery(index, PlayQueueItem.RECOVERY_UNSET); } /** * Shuffles the current play queue. - * + *

* This method first backs up the existing play queue and item being played. * Then a newly shuffled play queue will be generated along with currently * playing item placed at the beginning of the queue. - * + *

+ *

* Will emit a {@link ReorderEvent} in any context. - * */ + *

+ */ public synchronized void shuffle() { if (backup == null) { backup = new ArrayList<>(streams); @@ -372,14 +437,18 @@ public synchronized void shuffle() { /** * Unshuffles the current play queue if a backup play queue exists. - * + *

* This method undoes shuffling and index will be set to the previously playing item if found, * otherwise, the index will reset to 0. - * + *

+ *

* Will emit a {@link ReorderEvent} if a backup exists. - * */ + *

+ */ public synchronized void unshuffle() { - if (backup == null) return; + if (backup == null) { + return; + } final int originIndex = getIndex(); final PlayQueueItem current = getItem(); @@ -410,20 +479,23 @@ private void broadcast(@NonNull final PlayQueueEvent event) { private Subscriber getSelfReporter() { return new Subscriber() { @Override - public void onSubscribe(Subscription s) { - if (reportingReactor != null) reportingReactor.cancel(); + public void onSubscribe(final Subscription s) { + if (reportingReactor != null) { + reportingReactor.cancel(); + } reportingReactor = s; reportingReactor.request(1); } @Override - public void onNext(PlayQueueEvent event) { - Log.d(TAG, "Received broadcast: " + event.type().name() + ". Current index: " + getIndex() + ", play queue length: " + size() + "."); + public void onNext(final PlayQueueEvent event) { + Log.d(TAG, "Received broadcast: " + event.type().name() + ". " + + "Current index: " + getIndex() + ", play queue length: " + size() + "."); reportingReactor.request(1); } @Override - public void onError(Throwable t) { + public void onError(final Throwable t) { Log.e(TAG, "Received broadcast error", t); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java index b74736c49..8028a5a9d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java @@ -1,12 +1,13 @@ package org.schabi.newpipe.player.playqueue; import android.content.Context; -import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.R; import org.schabi.newpipe.player.playqueue.events.AppendEvent; import org.schabi.newpipe.player.playqueue.events.ErrorEvent; @@ -24,20 +25,20 @@ /** * Created by Christian Schabesberger on 01.08.16. - * + *

* Copyright (C) Christian Schabesberger 2016 * InfoListAdapter.java is part of NewPipe. - * + *

* 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. - * + *

* 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. - * + *

* You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . */ @@ -55,14 +56,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter getReactor() { return new Observer() { @Override - public void onSubscribe(@NonNull Disposable d) { - if (playQueueReactor != null) playQueueReactor.dispose(); + public void onSubscribe(@NonNull final Disposable d) { + if (playQueueReactor != null) { + playQueueReactor.dispose(); + } playQueueReactor = d; } @Override - public void onNext(@NonNull PlayQueueEvent playQueueMessage) { - if (playQueueReactor != null) onPlayQueueChanged(playQueueMessage); + public void onNext(@NonNull final PlayQueueEvent playQueueMessage) { + if (playQueueReactor != null) { + onPlayQueueChanged(playQueueMessage); + } } @Override - public void onError(@NonNull Throwable e) {} + public void onError(@NonNull final Throwable e) { } @Override public void onComplete() { @@ -138,7 +135,9 @@ private void onPlayQueueChanged(final PlayQueueEvent message) { } public void dispose() { - if (playQueueReactor != null) playQueueReactor.dispose(); + if (playQueueReactor != null) { + playQueueReactor.dispose(); + } playQueueReactor = null; } @@ -150,7 +149,7 @@ public void unsetSelectedListener() { playQueueItemBuilder.setOnSelectedListener(null); } - public void setFooter(View footer) { + public void setFooter(final View footer) { this.footer = footer; notifyItemChanged(playQueue.size()); } @@ -167,13 +166,15 @@ public List getItems() { @Override public int getItemCount() { int count = playQueue.getStreams().size(); - if(footer != null && showFooter) count++; + if (footer != null && showFooter) { + count++; + } return count; } @Override - public int getItemViewType(int position) { - if(footer != null && position == playQueue.getStreams().size() && showFooter) { + public int getItemViewType(final int position) { + if (footer != null && position == playQueue.getStreams().size() && showFooter) { return FOOTER_VIEW_TYPE_ID; } @@ -181,12 +182,13 @@ public int getItemViewType(int position) { } @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { - switch(type) { + public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int type) { + switch (type) { case FOOTER_VIEW_TYPE_ID: return new HFHolder(footer); case ITEM_VIEW_TYPE_ID: - return new PlayQueueItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.play_queue_item, parent, false)); + return new PlayQueueItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.play_queue_item, parent, false)); default: Log.e(TAG, "Attempting to create view holder with undefined type: " + type); return new FallbackViewHolder(new View(parent.getContext())); @@ -194,19 +196,30 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { } @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if(holder instanceof PlayQueueItemHolder) { + public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { + if (holder instanceof PlayQueueItemHolder) { final PlayQueueItemHolder itemHolder = (PlayQueueItemHolder) holder; // Build the list item - playQueueItemBuilder.buildStreamInfoItem(itemHolder, playQueue.getStreams().get(position)); + playQueueItemBuilder + .buildStreamInfoItem(itemHolder, playQueue.getStreams().get(position)); // Check if the current item should be selected/highlighted final boolean isSelected = playQueue.getIndex() == position; itemHolder.itemSelected.setVisibility(isSelected ? View.VISIBLE : View.INVISIBLE); itemHolder.itemView.setSelected(isSelected); - } else if(holder instanceof HFHolder && position == playQueue.getStreams().size() && footer != null && showFooter) { + } else if (holder instanceof HFHolder && position == playQueue.getStreams().size() + && footer != null && showFooter) { ((HFHolder) holder).view = footer; } } + + public class HFHolder extends RecyclerView.ViewHolder { + public View view; + + public HFHolder(final View v) { + super(v); + view = v; + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java index 309f22ad5..74aef07fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java @@ -14,27 +14,34 @@ import io.reactivex.schedulers.Schedulers; public class PlayQueueItem implements Serializable { - public final static long RECOVERY_UNSET = Long.MIN_VALUE; - private final static String EMPTY_STRING = ""; + public static final long RECOVERY_UNSET = Long.MIN_VALUE; + private static final String EMPTY_STRING = ""; - @NonNull final private String title; - @NonNull final private String url; - final private int serviceId; - final private long duration; - @NonNull final private String thumbnailUrl; - @NonNull final private String uploader; - @NonNull final private StreamType streamType; + @NonNull + private final String title; + @NonNull + private final String url; + private final int serviceId; + private final long duration; + @NonNull + private final String thumbnailUrl; + @NonNull + private final String uploader; + @NonNull + private final StreamType streamType; private boolean isAutoQueued; private long recoveryPosition; private Throwable error; + PlayQueueItem(@NonNull final StreamInfo info) { this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(), info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType()); - if (info.getStartPosition() > 0) + if (info.getStartPosition() > 0) { setRecoveryPosition(info.getStartPosition() * 1000); + } } PlayQueueItem(@NonNull final StreamInfoItem item) { @@ -94,6 +101,10 @@ public long getRecoveryPosition() { return recoveryPosition; } + /*package-private*/ void setRecoveryPosition(final long recoveryPosition) { + this.recoveryPosition = recoveryPosition; + } + @Nullable public Throwable getError() { return error; @@ -110,15 +121,11 @@ public boolean isAutoQueued() { return isAutoQueued; } - public void setAutoQueued(boolean autoQueued) { - isAutoQueued = autoQueued; - } - //////////////////////////////////////////////////////////////////////////// // Item States, keep external access out //////////////////////////////////////////////////////////////////////////// - /*package-private*/ void setRecoveryPosition(final long recoveryPosition) { - this.recoveryPosition = recoveryPosition; + public void setAutoQueued(final boolean autoQueued) { + isAutoQueued = autoQueued; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java index c24eff81a..1c50dc6b4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java @@ -12,25 +12,20 @@ import org.schabi.newpipe.util.Localization; public class PlayQueueItemBuilder { - private static final String TAG = PlayQueueItemBuilder.class.toString(); - - public interface OnSelectedListener { - void selected(PlayQueueItem item, View view); - void held(PlayQueueItem item, View view); - void onStartDrag(PlayQueueItemHolder viewHolder); - } - private OnSelectedListener onItemClickListener; - public PlayQueueItemBuilder(final Context context) {} + public PlayQueueItemBuilder(final Context context) { + } - public void setOnSelectedListener(OnSelectedListener listener) { + public void setOnSelectedListener(final OnSelectedListener listener) { this.onItemClickListener = listener; } public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueueItem item) { - if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle()); + if (!TextUtils.isEmpty(item.getTitle())) { + holder.itemVideoTitleView.setText(item.getTitle()); + } holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(), NewPipe.getNameOfService(item.getServiceId()))); @@ -71,4 +66,12 @@ private View.OnTouchListener getOnTouchListener(final PlayQueueItemHolder holder return false; }; } + + public interface OnSelectedListener { + void selected(PlayQueueItem item, View view); + + void held(PlayQueueItem item, View view); + + void onStartDrag(PlayQueueItemHolder viewHolder); + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java index 7ad34b91e..c46410343 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java @@ -1,10 +1,11 @@ package org.schabi.newpipe.player.playqueue; -import androidx.recyclerview.widget.RecyclerView; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.R; /** @@ -12,29 +13,37 @@ *

* Copyright (C) Christian Schabesberger 2016 * StreamInfoItemHolder.java is part of NewPipe. + *

*

* 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. + *

*

* 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. + *

*

* You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . + * along with NewPipe. If not, see . + *

*/ public class PlayQueueItemHolder extends RecyclerView.ViewHolder { + public final TextView itemVideoTitleView; + public final TextView itemDurationView; + final TextView itemAdditionalDetailsView; - public final TextView itemVideoTitleView, itemDurationView, itemAdditionalDetailsView; - public final ImageView itemSelected, itemThumbnailView, itemHandle; + final ImageView itemSelected; + public final ImageView itemThumbnailView; + final ImageView itemHandle; public final View itemRoot; - public PlayQueueItemHolder(View v) { + PlayQueueItemHolder(final View v) { super(v); itemRoot = v.findViewById(R.id.itemRoot); itemVideoTitleView = v.findViewById(R.id.itemVideoTitleView); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index 38e8e092a..5fee43659 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -1,7 +1,7 @@ package org.schabi.newpipe.player.playqueue; -import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleCallback { private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10; @@ -11,14 +11,14 @@ public PlayQueueItemTouchCallback() { super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT); } - public abstract void onMove(final int sourceIndex, final int targetIndex); + public abstract void onMove(int sourceIndex, int targetIndex); public abstract void onSwiped(int index); @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, - int viewSizeOutOfBounds, int totalSize, - long msSinceStartScroll) { + public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, final int viewSize, + final int viewSizeOutOfBounds, final int totalSize, + final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, @@ -27,8 +27,8 @@ public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, } @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, - RecyclerView.ViewHolder target) { + public boolean onMove(final RecyclerView recyclerView, final RecyclerView.ViewHolder source, + final RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType()) { return false; } @@ -50,7 +50,7 @@ public boolean isItemViewSwipeEnabled() { } @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { onSwiped(viewHolder.getAdapterPosition()); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java index 40d1a11e7..79cf0601c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java @@ -25,7 +25,7 @@ public SinglePlayQueue(final List items, final int index) { super(index, playQueueItemsOf(items)); } - private static List playQueueItemsOf(List items) { + private static List playQueueItemsOf(final List items) { List playQueueItems = new ArrayList<>(items.size()); for (final StreamInfoItem item : items) { playQueueItems.add(new PlayQueueItem(item)); @@ -39,5 +39,6 @@ public boolean isComplete() { } @Override - public void fetch() {} + public void fetch() { + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/AppendEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/AppendEvent.java index 6ccd85f82..cc922dbb1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/AppendEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/AppendEvent.java @@ -1,18 +1,17 @@ package org.schabi.newpipe.player.playqueue.events; - public class AppendEvent implements PlayQueueEvent { - final private int amount; + private final int amount; + + public AppendEvent(final int amount) { + this.amount = amount; + } @Override public PlayQueueEventType type() { return PlayQueueEventType.APPEND; } - public AppendEvent(final int amount) { - this.amount = amount; - } - public int getAmount() { return amount; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ErrorEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ErrorEvent.java index 570a8e337..16fb339b8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ErrorEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ErrorEvent.java @@ -1,15 +1,9 @@ package org.schabi.newpipe.player.playqueue.events; - public class ErrorEvent implements PlayQueueEvent { - final private int errorIndex; - final private int queueIndex; - final private boolean skippable; - - @Override - public PlayQueueEventType type() { - return PlayQueueEventType.ERROR; - } + private final int errorIndex; + private final int queueIndex; + private final boolean skippable; public ErrorEvent(final int errorIndex, final int queueIndex, final boolean skippable) { this.errorIndex = errorIndex; @@ -17,6 +11,11 @@ public ErrorEvent(final int errorIndex, final int queueIndex, final boolean skip this.skippable = skippable; } + @Override + public PlayQueueEventType type() { + return PlayQueueEventType.ERROR; + } + public int getErrorIndex() { return errorIndex; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/MoveEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/MoveEvent.java index 69468be31..55d198923 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/MoveEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/MoveEvent.java @@ -1,19 +1,19 @@ package org.schabi.newpipe.player.playqueue.events; public class MoveEvent implements PlayQueueEvent { - final private int fromIndex; - final private int toIndex; - - @Override - public PlayQueueEventType type() { - return PlayQueueEventType.MOVE; - } + private final int fromIndex; + private final int toIndex; public MoveEvent(final int oldIndex, final int newIndex) { this.fromIndex = oldIndex; this.toIndex = newIndex; } + @Override + public PlayQueueEventType type() { + return PlayQueueEventType.MOVE; + } + public int getFromIndex() { return fromIndex; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RecoveryEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RecoveryEvent.java index 58d3fadfc..6f21b36cd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RecoveryEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RecoveryEvent.java @@ -1,20 +1,19 @@ package org.schabi.newpipe.player.playqueue.events; - public class RecoveryEvent implements PlayQueueEvent { - final private int index; - final private long position; - - @Override - public PlayQueueEventType type() { - return PlayQueueEventType.RECOVERY; - } + private final int index; + private final long position; public RecoveryEvent(final int index, final long position) { this.index = index; this.position = position; } + @Override + public PlayQueueEventType type() { + return PlayQueueEventType.RECOVERY; + } + public int getIndex() { return index; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RemoveEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RemoveEvent.java index bb42ef109..a5872906d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RemoveEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/RemoveEvent.java @@ -1,20 +1,19 @@ package org.schabi.newpipe.player.playqueue.events; - public class RemoveEvent implements PlayQueueEvent { - final private int removeIndex; - final private int queueIndex; - - @Override - public PlayQueueEventType type() { - return PlayQueueEventType.REMOVE; - } + private final int removeIndex; + private final int queueIndex; public RemoveEvent(final int removeIndex, final int queueIndex) { this.removeIndex = removeIndex; this.queueIndex = queueIndex; } + @Override + public PlayQueueEventType type() { + return PlayQueueEventType.REMOVE; + } + public int getQueueIndex() { return queueIndex; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ReorderEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ReorderEvent.java index 738a89fcf..4f4f14756 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ReorderEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/ReorderEvent.java @@ -4,16 +4,16 @@ public class ReorderEvent implements PlayQueueEvent { private final int fromSelectedIndex; private final int toSelectedIndex; - @Override - public PlayQueueEventType type() { - return PlayQueueEventType.REORDER; - } - public ReorderEvent(final int fromSelectedIndex, final int toSelectedIndex) { this.fromSelectedIndex = fromSelectedIndex; this.toSelectedIndex = toSelectedIndex; } + @Override + public PlayQueueEventType type() { + return PlayQueueEventType.REORDER; + } + public int getFromSelectedIndex() { return fromSelectedIndex; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/SelectEvent.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/SelectEvent.java index 7dcc88794..95e344211 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/events/SelectEvent.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/events/SelectEvent.java @@ -1,20 +1,19 @@ package org.schabi.newpipe.player.playqueue.events; - public class SelectEvent implements PlayQueueEvent { - final private int oldIndex; - final private int newIndex; - - @Override - public PlayQueueEventType type() { - return PlayQueueEventType.SELECT; - } + private final int oldIndex; + private final int newIndex; public SelectEvent(final int oldIndex, final int newIndex) { this.oldIndex = oldIndex; this.newIndex = newIndex; } + @Override + public PlayQueueEventType type() { + return PlayQueueEventType.SELECT; + } + public int getOldIndex() { return oldIndex; } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java index 7e9199040..29be402c5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.player.resolver; import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,9 +15,10 @@ import org.schabi.newpipe.util.ListHelper; public class AudioPlaybackResolver implements PlaybackResolver { - - @NonNull private final Context context; - @NonNull private final PlayerDataSource dataSource; + @NonNull + private final Context context; + @NonNull + private final PlayerDataSource dataSource; public AudioPlaybackResolver(@NonNull final Context context, @NonNull final PlayerDataSource dataSource) { @@ -26,12 +28,16 @@ public AudioPlaybackResolver(@NonNull final Context context, @Override @Nullable - public MediaSource resolve(@NonNull StreamInfo info) { + public MediaSource resolve(@NonNull final StreamInfo info) { final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info); - if (liveSource != null) return liveSource; + if (liveSource != null) { + return liveSource; + } final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); - if (index < 0 || index >= info.getAudioStreams().size()) return null; + if (index < 0 || index >= info.getAudioStreams().size()) { + return null; + } final AudioStream audio = info.getAudioStreams().get(index); final MediaSourceTag tag = new MediaSourceTag(info); diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java b/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java index d8c0c89b7..360e92e7f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/MediaSourceTag.java @@ -11,9 +11,11 @@ import java.util.List; public class MediaSourceTag implements Serializable { - @NonNull private final StreamInfo metadata; + @NonNull + private final StreamInfo metadata; - @NonNull private final List sortedAvailableVideoStreams; + @NonNull + private final List sortedAvailableVideoStreams; private final int selectedVideoStreamIndex; public MediaSourceTag(@NonNull final StreamInfo metadata, @@ -44,8 +46,8 @@ public int getSelectedVideoStreamIndex() { @Nullable public VideoStream getSelectedVideoStream() { - return selectedVideoStreamIndex < 0 || - selectedVideoStreamIndex >= sortedAvailableVideoStreams.size() ? null : - sortedAvailableVideoStreams.get(selectedVideoStreamIndex); + return selectedVideoStreamIndex < 0 + || selectedVideoStreamIndex >= sortedAvailableVideoStreams.size() + ? null : sortedAvailableVideoStreams.get(selectedVideoStreamIndex); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java index ef28f71ee..e06c0ff82 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java @@ -1,9 +1,10 @@ package org.schabi.newpipe.player.resolver; import android.net.Uri; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.MediaSource; @@ -61,8 +62,8 @@ default MediaSource buildMediaSource(@NonNull final PlayerDataSource dataSource, @NonNull final String overrideExtension, @NonNull final MediaSourceTag metadata) { final Uri uri = Uri.parse(sourceUrl); - @C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ? - Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension); + @C.ContentType final int type = TextUtils.isEmpty(overrideExtension) + ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension); switch (type) { case C.TYPE_SS: diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java index d6af20ae2..a3e1db5b4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/Resolver.java @@ -4,5 +4,6 @@ import androidx.annotation.Nullable; public interface Resolver { - @Nullable Product resolve(@NonNull Source source); + @Nullable + Product resolve(@NonNull Source source); } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index c503fe596..3c131454d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -2,6 +2,7 @@ import android.content.Context; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -10,9 +11,9 @@ import com.google.android.exoplayer2.source.MergingMediaSource; import org.schabi.newpipe.extractor.MediaFormat; -import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -25,18 +26,14 @@ import static com.google.android.exoplayer2.C.TIME_UNSET; public class VideoPlaybackResolver implements PlaybackResolver { - - public interface QualityResolver { - int getDefaultResolutionIndex(final List sortedVideos); - int getOverrideResolutionIndex(final List sortedVideos, - final String playbackQuality); - } - - @NonNull private final Context context; - @NonNull private final PlayerDataSource dataSource; - @NonNull private final QualityResolver qualityResolver; - - @Nullable private String playbackQuality; + @NonNull + private final Context context; + @NonNull + private final PlayerDataSource dataSource; + @NonNull + private final QualityResolver qualityResolver; + @Nullable + private String playbackQuality; public VideoPlaybackResolver(@NonNull final Context context, @NonNull final PlayerDataSource dataSource, @@ -48,9 +45,11 @@ public VideoPlaybackResolver(@NonNull final Context context, @Override @Nullable - public MediaSource resolve(@NonNull StreamInfo info) { + public MediaSource resolve(@NonNull final StreamInfo info) { final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info); - if (liveSource != null) return liveSource; + if (liveSource != null) { + return liveSource; + } List mediaSources = new ArrayList<>(); @@ -81,7 +80,7 @@ public MediaSource resolve(@NonNull StreamInfo info) { ListHelper.getDefaultAudioFormat(context, audioStreams)); // Use the audio stream if there is no video stream, or // Merge with audio stream in case if video does not contain audio - if (audio != null && ((video != null && video.isVideoOnly) || video == null)) { + if (audio != null && (video == null || video.isVideoOnly)) { final MediaSource audioSource = buildMediaSource(dataSource, audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio), MediaFormat.getSuffixById(audio.getFormatId()), tag); @@ -89,17 +88,22 @@ public MediaSource resolve(@NonNull StreamInfo info) { } // If there is no audio or video sources, then this media source cannot be played back - if (mediaSources.isEmpty()) return null; + if (mediaSources.isEmpty()) { + return null; + } // Below are auxiliary media sources // Create subtitle sources - if(info.getSubtitles() != null) { + if (info.getSubtitles() != null) { for (final SubtitlesStream subtitle : info.getSubtitles()) { final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat()); - if (mimeType == null) continue; + if (mimeType == null) { + continue; + } final Format textFormat = Format.createTextSampleFormat(null, mimeType, - SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle)); + SELECTION_FLAG_AUTOSELECT, + PlayerHelper.captionLanguageOf(context, subtitle)); final MediaSource textSource = dataSource.getSampleMediaSourceFactory() .createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET); mediaSources.add(textSource); @@ -119,7 +123,13 @@ public String getPlaybackQuality() { return playbackQuality; } - public void setPlaybackQuality(@Nullable String playbackQuality) { + public void setPlaybackQuality(@Nullable final String playbackQuality) { this.playbackQuality = playbackQuality; } + + public interface QualityResolver { + int getDefaultResolutionIndex(List sortedVideos); + + int getOverrideResolutionIndex(List sortedVideos, String playbackQuality); + } } diff --git a/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java index d8506fe6e..a6559d54d 100644 --- a/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.report; import android.content.Context; + import androidx.annotation.NonNull; import org.acra.collector.CrashReportData; @@ -30,9 +31,9 @@ public class AcraReportSender implements ReportSender { @Override - public void send(@NonNull Context context, @NonNull CrashReportData report) { + public void send(@NonNull final Context context, @NonNull final CrashReportData report) { ErrorActivity.reportError(context, report, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,"none", + ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "App crash, UI failure", R.string.app_ui_crash)); } } diff --git a/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java b/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java index 94b2e84a5..9428df0cb 100644 --- a/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java +++ b/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.report; import android.content.Context; + import androidx.annotation.NonNull; import org.acra.config.ACRAConfiguration; @@ -8,7 +9,7 @@ import org.acra.sender.ReportSenderFactory; /* - * Created by Christian Schabesberger on 13.09.16. + * Created by Christian Schabesberger on 13.09.16. * * Copyright (C) Christian Schabesberger 2015 * AcraReportSenderFactory.java is part of NewPipe. @@ -29,7 +30,8 @@ public class AcraReportSenderFactory implements ReportSenderFactory { @NonNull - public ReportSender create(@NonNull Context context, @NonNull ACRAConfiguration config) { + public ReportSender create(@NonNull final Context context, + @NonNull final ACRAConfiguration config) { return new AcraReportSender(); } } diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index b78751496..19bf9d14d 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -12,13 +12,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.preference.PreferenceManager; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import com.google.android.material.snackbar.Snackbar; -import androidx.core.app.NavUtils; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -28,6 +21,15 @@ import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.NavUtils; + +import com.google.android.material.snackbar.Snackbar; + import org.acra.ReportField; import org.acra.collector.CrashReportData; import org.json.JSONArray; @@ -77,7 +79,8 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_LIST = "error_list"; public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; - public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME; + public static final String ERROR_EMAIL_SUBJECT + = "Exception in NewPipe " + BuildConfig.VERSION_NAME; private String[] errorList; private ErrorInfo errorInfo; private Class returnActivity; @@ -85,12 +88,13 @@ public class ErrorActivity extends AppCompatActivity { private EditText userCommentBox; public static void reportUiError(final AppCompatActivity activity, final Throwable el) { - reportError(activity, el, activity.getClass(), null, - ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR, + "none", "", R.string.app_ui_crash)); } public static void reportError(final Context context, final List el, - final Class returnActivity, View rootView, final ErrorInfo errorInfo) { + final Class returnActivity, final View rootView, + final ErrorInfo errorInfo) { if (rootView != null) { Snackbar.make(rootView, R.string.error_snackbar_message, 3 * 1000) .setActionTextColor(Color.YELLOW) @@ -101,9 +105,10 @@ public static void reportError(final Context context, final List el, } } - private static void startErrorActivity(Class returnActivity, Context context, ErrorInfo errorInfo, List el) { + private static void startErrorActivity(final Class returnActivity, final Context context, + final ErrorInfo errorInfo, final List el) { ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); - ac.returnActivity = returnActivity; + ac.setReturnActivity(returnActivity); Intent intent = new Intent(context, ErrorActivity.class); intent.putExtra(ERROR_INFO, errorInfo); intent.putExtra(ERROR_LIST, elToSl(el)); @@ -112,7 +117,8 @@ private static void startErrorActivity(Class returnActivity, Context context, Er } public static void reportError(final Context context, final Throwable e, - final Class returnActivity, View rootView, final ErrorInfo errorInfo) { + final Class returnActivity, final View rootView, + final ErrorInfo errorInfo) { List el = null; if (e != null) { el = new Vector<>(); @@ -122,8 +128,9 @@ public static void reportError(final Context context, final Throwable e, } // async call - public static void reportError(Handler handler, final Context context, final Throwable e, - final Class returnActivity, final View rootView, final ErrorInfo errorInfo) { + public static void reportError(final Handler handler, final Context context, + final Throwable e, final Class returnActivity, + final View rootView, final ErrorInfo errorInfo) { List el = null; if (e != null) { @@ -134,12 +141,14 @@ public static void reportError(Handler handler, final Context context, final Thr } // async call - public static void reportError(Handler handler, final Context context, final List el, - final Class returnActivity, final View rootView, final ErrorInfo errorInfo) { + public static void reportError(final Handler handler, final Context context, + final List el, final Class returnActivity, + final View rootView, final ErrorInfo errorInfo) { handler.post(() -> reportError(context, el, returnActivity, rootView, errorInfo)); } - public static void reportError(final Context context, final CrashReportData report, final ErrorInfo errorInfo) { + public static void reportError(final Context context, final CrashReportData report, + final ErrorInfo errorInfo) { // get key first (don't ask about this solution) ReportField key = null; for (ReportField k : report.keySet()) { @@ -164,7 +173,7 @@ private static String getStackTrace(final Throwable throwable) { } // errorList to StringList - private static String[] elToSl(List stackTraces) { + private static String[] elToSl(final List stackTraces) { String[] out = new String[stackTraces.size()]; for (int i = 0; i < stackTraces.size(); i++) { out[i] = getStackTrace(stackTraces.get(i)); @@ -172,8 +181,27 @@ private static String[] elToSl(List stackTraces) { return out; } + /** + * Get the checked activity. + * + * @param returnActivity the activity to return to + * @return the casted return activity or null + */ + @Nullable + static Class getReturnActivity(final Class returnActivity) { + Class checkedReturnActivity = null; + if (returnActivity != null) { + if (Activity.class.isAssignableFrom(returnActivity)) { + checkedReturnActivity = returnActivity.asSubclass(Activity.class); + } else { + checkedReturnActivity = MainActivity.class; + } + } + return checkedReturnActivity; + } + @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { assureCorrectAppLanguage(this); super.onCreate(savedInstanceState); ThemeHelper.setTheme(this); @@ -198,7 +226,7 @@ protected void onCreate(Bundle savedInstanceState) { TextView errorMessageView = findViewById(R.id.errorMessageView); ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); - returnActivity = ac.returnActivity; + returnActivity = ac.getReturnActivity(); errorInfo = intent.getParcelableExtra(ERROR_INFO); errorList = intent.getStringArrayExtra(ERROR_LIST); @@ -252,32 +280,31 @@ protected void onCreate(Bundle savedInstanceState) { } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.error_menu, menu); return true; } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { int id = item.getItemId(); switch (id) { case android.R.id.home: goToReturnActivity(); break; - case R.id.menu_item_share_error: { + case R.id.menu_item_share_error: Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TEXT, buildJson()); intent.setType("text/plain"); startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); - } - break; + break; } return false; } - private String formErrorText(String[] el) { + private String formErrorText(final String[] el) { StringBuilder text = new StringBuilder(); if (el != null) { for (String e : el) { @@ -288,25 +315,6 @@ private String formErrorText(String[] el) { return text.toString(); } - /** - * Get the checked activity. - * - * @param returnActivity the activity to return to - * @return the casted return activity or null - */ - @Nullable - static Class getReturnActivity(Class returnActivity) { - Class checkedReturnActivity = null; - if (returnActivity != null) { - if (Activity.class.isAssignableFrom(returnActivity)) { - checkedReturnActivity = returnActivity.asSubclass(Activity.class); - } else { - checkedReturnActivity = MainActivity.class; - } - } - return checkedReturnActivity; - } - private void goToReturnActivity() { Class checkedReturnActivity = getReturnActivity(returnActivity); if (checkedReturnActivity == null) { @@ -318,21 +326,21 @@ private void goToReturnActivity() { } } - private void buildInfo(ErrorInfo info) { + private void buildInfo(final ErrorInfo info) { TextView infoLabelView = findViewById(R.id.errorInfoLabelsView); TextView infoView = findViewById(R.id.errorInfosView); String text = ""; infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n")); - text += getUserActionString(info.userAction) - + "\n" + info.request - + "\n" + getContentLangString() - + "\n" + info.serviceName - + "\n" + currentTimeStamp - + "\n" + getPackageName() - + "\n" + BuildConfig.VERSION_NAME - + "\n" + getOsString(); + text += getUserActionString(info.userAction) + "\n" + + info.request + "\n" + + getContentLangString() + "\n" + + info.serviceName + "\n" + + currentTimeStamp + "\n" + + getPackageName() + "\n" + + BuildConfig.VERSION_NAME + "\n" + + getOsString(); infoView.setText(text); } @@ -369,7 +377,7 @@ private String buildJson() { return ""; } - private String getUserActionString(UserAction userAction) { + private String getUserActionString(final UserAction userAction) { if (userAction == null) { return "Your description is in another castle."; } else { @@ -391,7 +399,7 @@ private String getOsString() { return System.getProperty("os.name") + " " + (osBase.isEmpty() ? "Android" : osBase) + " " + Build.VERSION.RELEASE - + " - " + Integer.toString(Build.VERSION.SDK_INT); + + " - " + Build.VERSION.SDK_INT; } private void addGuruMeditaion() { @@ -415,38 +423,42 @@ public String getCurrentTimeStamp() { } public static class ErrorInfo implements Parcelable { - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { @Override - public ErrorInfo createFromParcel(Parcel source) { + public ErrorInfo createFromParcel(final Parcel source) { return new ErrorInfo(source); } @Override - public ErrorInfo[] newArray(int size) { + public ErrorInfo[] newArray(final int size) { return new ErrorInfo[size]; } }; - final public UserAction userAction; - final public String request; - final public String serviceName; + + final UserAction userAction; + public final String request; + final String serviceName; @StringRes - final public int message; + public final int message; - private ErrorInfo(UserAction userAction, String serviceName, String request, @StringRes int message) { + private ErrorInfo(final UserAction userAction, final String serviceName, + final String request, @StringRes final int message) { this.userAction = userAction; this.serviceName = serviceName; this.request = request; this.message = message; } - protected ErrorInfo(Parcel in) { + protected ErrorInfo(final Parcel in) { this.userAction = UserAction.valueOf(in.readString()); this.request = in.readString(); this.serviceName = in.readString(); this.message = in.readInt(); } - public static ErrorInfo make(UserAction userAction, String serviceName, String request, @StringRes int message) { + public static ErrorInfo make(final UserAction userAction, final String serviceName, + final String request, @StringRes final int message) { return new ErrorInfo(userAction, serviceName, request, message); } @@ -456,7 +468,7 @@ public int describeContents() { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(final Parcel dest, final int flags) { dest.writeString(this.userAction.name()); dest.writeString(this.request); dest.writeString(this.serviceName); diff --git a/app/src/main/java/org/schabi/newpipe/report/UserAction.java b/app/src/main/java/org/schabi/newpipe/report/UserAction.java index f4f3e31b6..faa5e7a44 100644 --- a/app/src/main/java/org/schabi/newpipe/report/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/report/UserAction.java @@ -25,7 +25,7 @@ public enum UserAction { private final String message; - UserAction(String message) { + UserAction(final String message) { this.message = message; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java index ce22b84e9..ffee3e693 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java @@ -4,6 +4,7 @@ import android.os.Build; import android.os.Bundle; import android.provider.Settings; + import androidx.annotation.Nullable; import androidx.preference.Preference; @@ -11,55 +12,57 @@ import org.schabi.newpipe.util.Constants; public class AppearanceSettingsFragment extends BasePreferenceFragment { - private final static boolean CAPTIONING_SETTINGS_ACCESSIBLE = + private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; /** - * Theme that was applied when the settings was opened (or recreated after a theme change) + * Theme that was applied when the settings was opened (or recreated after a theme change). */ private String startThemeKey; + private final Preference.OnPreferenceChangeListener themePreferenceChange + = new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply(); + defaultPreferences.edit() + .putString(getString(R.string.theme_key), newValue.toString()).apply(); + + if (!newValue.equals(startThemeKey) && getActivity() != null) { + // If it's not the current theme + getActivity().recreate(); + } + + return false; + } + }; private String captionSettingsKey; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); String themeKey = getString(R.string.theme_key); - startThemeKey = defaultPreferences.getString(themeKey, getString(R.string.default_theme_value)); + startThemeKey = defaultPreferences + .getString(themeKey, getString(R.string.default_theme_value)); findPreference(themeKey).setOnPreferenceChangeListener(themePreferenceChange); captionSettingsKey = getString(R.string.caption_settings_key); - if (!CAPTIONING_SETTINGS_ACCESSIBLE) { + if (!CAPTIONING_SETTINGS_ACCESSIBLE) { final Preference captionSettings = findPreference(captionSettingsKey); getPreferenceScreen().removePreference(captionSettings); } } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.appearance_settings); } @Override - public boolean onPreferenceTreeClick(Preference preference) { + public boolean onPreferenceTreeClick(final Preference preference) { if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) { startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); } return super.onPreferenceTreeClick(preference); } - - private final Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply(); - defaultPreferences.edit().putString(getString(R.string.theme_key), newValue.toString()).apply(); - - if (!newValue.equals(startThemeKey) && getActivity() != null) { - // If it's not the current theme - getActivity().recreate(); - } - - return false; - } - }; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java index 056e9942a..125931ee1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java @@ -3,11 +3,12 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; +import android.view.View; + import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.preference.PreferenceFragmentCompat; -import android.view.View; import org.schabi.newpipe.MainActivity; @@ -15,16 +16,16 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); protected final boolean DEBUG = MainActivity.DEBUG; - protected SharedPreferences defaultPreferences; + SharedPreferences defaultPreferences; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { defaultPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); super.onCreate(savedInstanceState); } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setDivider(null); updateTitle(); @@ -39,7 +40,9 @@ public void onResume() { private void updateTitle() { if (getActivity() instanceof AppCompatActivity) { ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - if (actionBar != null) actionBar.setTitle(getPreferenceScreen().getTitle()); + if (actionBar != null) { + actionBar.setTitle(getPreferenceScreen().getTitle()); + } } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 0be72d0eb..bc2765387 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -45,16 +45,15 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; public class ContentSettingsFragment extends BasePreferenceFragment { - private static final int REQUEST_IMPORT_PATH = 8945; private static final int REQUEST_EXPORT_PATH = 30945; private File databasesDir; - private File newpipe_db; - private File newpipe_db_journal; - private File newpipe_db_shm; - private File newpipe_db_wal; - private File newpipe_settings; + private File newpipeDb; + private File newpipeDbJournal; + private File newpipeDbShm; + private File newpipeDbWal; + private File newpipeSettings; private String thumbnailLoadToggleKey; @@ -63,17 +62,20 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private String initialLanguage; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); - initialSelectedLocalization = org.schabi.newpipe.util.Localization.getPreferredLocalization(requireContext()); - initialSelectedContentCountry = org.schabi.newpipe.util.Localization.getPreferredContentCountry(requireContext()); - initialLanguage = PreferenceManager.getDefaultSharedPreferences(getContext()).getString("app_language_key", "en"); + initialSelectedLocalization = org.schabi.newpipe.util.Localization + .getPreferredLocalization(requireContext()); + initialSelectedContentCountry = org.schabi.newpipe.util.Localization + .getPreferredContentCountry(requireContext()); + initialLanguage = PreferenceManager + .getDefaultSharedPreferences(getContext()).getString("app_language_key", "en"); } @Override - public boolean onPreferenceTreeClick(Preference preference) { + public boolean onPreferenceTreeClick(final Preference preference) { if (preference.getKey().equals(thumbnailLoadToggleKey)) { final ImageLoader imageLoader = ImageLoader.getInstance(); imageLoader.stop(); @@ -88,17 +90,17 @@ public boolean onPreferenceTreeClick(Preference preference) { } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { String homeDir = getActivity().getApplicationInfo().dataDir; databasesDir = new File(homeDir + "/databases"); - newpipe_db = new File(homeDir + "/databases/newpipe.db"); - newpipe_db_journal = new File(homeDir + "/databases/newpipe.db-journal"); - newpipe_db_shm = new File(homeDir + "/databases/newpipe.db-shm"); - newpipe_db_wal = new File(homeDir + "/databases/newpipe.db-wal"); + newpipeDb = new File(homeDir + "/databases/newpipe.db"); + newpipeDbJournal = new File(homeDir + "/databases/newpipe.db-journal"); + newpipeDbShm = new File(homeDir + "/databases/newpipe.db-shm"); + newpipeDbWal = new File(homeDir + "/databases/newpipe.db-wal"); - newpipe_settings = new File(homeDir + "/databases/newpipe.settings"); - newpipe_settings.delete(); + newpipeSettings = new File(homeDir + "/databases/newpipe.settings"); + newpipeSettings.delete(); addPreferencesFromResource(R.xml.content_settings); @@ -107,7 +109,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { Intent i = new Intent(getActivity(), FilePickerActivityHelper.class) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false) - .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE); + .putExtra(FilePickerActivityHelper.EXTRA_MODE, + FilePickerActivityHelper.MODE_FILE); startActivityForResult(i, REQUEST_IMPORT_PATH); return true; }); @@ -117,7 +120,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { Intent i = new Intent(getActivity(), FilePickerActivityHelper.class) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) - .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR); + .putExtra(FilePickerActivityHelper.EXTRA_MODE, + FilePickerActivityHelper.MODE_DIR); startActivityForResult(i, REQUEST_EXPORT_PATH); return true; }); @@ -131,22 +135,29 @@ public void onDestroy() { .getPreferredLocalization(requireContext()); final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization .getPreferredContentCountry(requireContext()); - final String selectedLanguage = PreferenceManager.getDefaultSharedPreferences(getContext()).getString("app_language_key", "en"); + final String selectedLanguage = PreferenceManager + .getDefaultSharedPreferences(getContext()).getString("app_language_key", "en"); if (!selectedLocalization.equals(initialSelectedLocalization) - || !selectedContentCountry.equals(initialSelectedContentCountry) || !selectedLanguage.equals(initialLanguage)) { - Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart, Toast.LENGTH_LONG).show(); + || !selectedContentCountry.equals(initialSelectedContentCountry) + || !selectedLanguage.equals(initialLanguage)) { + Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart, + Toast.LENGTH_LONG).show(); NewPipe.setupLocalization(selectedLocalization, selectedContentCountry); } } @Override - public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data) { + public void onActivityResult(final int requestCode, final int resultCode, + @NonNull final Intent data) { assureCorrectAppLanguage(getContext()); super.onActivityResult(requestCode, resultCode, data); if (DEBUG) { - Log.d(TAG, "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]"); + Log.d(TAG, "onActivityResult() called with: " + + "requestCode = [" + requestCode + "], " + + "resultCode = [" + resultCode + "], " + + "data = [" + data + "]"); } if ((requestCode == REQUEST_IMPORT_PATH || requestCode == REQUEST_EXPORT_PATH) @@ -167,7 +178,7 @@ public void onActivityResult(int requestCode, int resultCode, @NonNull Intent da } } - private void exportDatabase(String path) { + private void exportDatabase(final String path) { try { //checkpoint before export NewPipeDatabase.checkpoint(); @@ -175,10 +186,10 @@ private void exportDatabase(String path) { ZipOutputStream outZip = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream(path))); - ZipHelper.addFileToZip(outZip, newpipe_db.getPath(), "newpipe.db"); + ZipHelper.addFileToZip(outZip, newpipeDb.getPath(), "newpipe.db"); - saveSharedPreferencesToFile(newpipe_settings); - ZipHelper.addFileToZip(outZip, newpipe_settings.getPath(), "newpipe.settings"); + saveSharedPreferencesToFile(newpipeSettings); + ZipHelper.addFileToZip(outZip, newpipeSettings.getPath(), "newpipe.settings"); outZip.close(); @@ -189,7 +200,7 @@ private void exportDatabase(String path) { } } - private void saveSharedPreferencesToFile(File dst) { + private void saveSharedPreferencesToFile(final File dst) { ObjectOutputStream output = null; try { output = new ObjectOutputStream(new FileOutputStream(dst)); @@ -212,7 +223,7 @@ private void saveSharedPreferencesToFile(File dst) { } } - private void importDatabase(String filePath) { + private void importDatabase(final String filePath) { // check if file is supported ZipFile zipFile = null; try { @@ -224,7 +235,8 @@ private void importDatabase(String filePath) { } finally { try { zipFile.close(); - } catch (Exception ignored){} + } catch (Exception ignored) { + } } try { @@ -233,21 +245,20 @@ private void importDatabase(String filePath) { } final boolean isDbFileExtracted = ZipHelper.extractFileFromZip(filePath, - newpipe_db.getPath(), "newpipe.db"); + newpipeDb.getPath(), "newpipe.db"); if (isDbFileExtracted) { - newpipe_db_journal.delete(); - newpipe_db_wal.delete(); - newpipe_db_shm.delete(); - + newpipeDbJournal.delete(); + newpipeDbWal.delete(); + newpipeDbShm.delete(); } else { - Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG) .show(); } //If settings file exist, ask if it should be imported. - if (ZipHelper.extractFileFromZip(filePath, newpipe_settings.getPath(), "newpipe.settings")) { + if (ZipHelper.extractFileFromZip(filePath, newpipeSettings.getPath(), + "newpipe.settings")) { AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); alert.setTitle(R.string.import_settings); @@ -258,7 +269,7 @@ private void importDatabase(String filePath) { }); alert.setPositiveButton(getString(R.string.finish), (dialog, which) -> { dialog.dismiss(); - loadSharedPreferences(newpipe_settings); + loadSharedPreferences(newpipeSettings); // restart app to properly load db System.exit(0); }); @@ -267,33 +278,34 @@ private void importDatabase(String filePath) { // restart app to properly load db System.exit(0); } - } catch (Exception e) { onError(e); } } - private void loadSharedPreferences(File src) { + private void loadSharedPreferences(final File src) { ObjectInputStream input = null; try { input = new ObjectInputStream(new FileInputStream(src)); - SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); + SharedPreferences.Editor prefEdit = PreferenceManager + .getDefaultSharedPreferences(getContext()).edit(); prefEdit.clear(); Map entries = (Map) input.readObject(); for (Map.Entry entry : entries.entrySet()) { Object v = entry.getValue(); String key = entry.getKey(); - if (v instanceof Boolean) + if (v instanceof Boolean) { prefEdit.putBoolean(key, (Boolean) v); - else if (v instanceof Float) + } else if (v instanceof Float) { prefEdit.putFloat(key, (Float) v); - else if (v instanceof Integer) + } else if (v instanceof Integer) { prefEdit.putInt(key, (Integer) v); - else if (v instanceof Long) + } else if (v instanceof Long) { prefEdit.putLong(key, (Long) v); - else if (v instanceof String) - prefEdit.putString(key, ((String) v)); + } else if (v instanceof String) { + prefEdit.putString(key, (String) v); + } } prefEdit.commit(); } catch (FileNotFoundException e) { @@ -317,7 +329,7 @@ else if (v instanceof String) // Error //////////////////////////////////////////////////////////////////////////*/ - protected void onError(Throwable e) { + protected void onError(final Throwable e) { final Activity activity = getActivity(); ErrorActivity.reportError(activity, e, activity.getClass(), diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index 0956f47d6..af3e3f5a9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -6,7 +6,7 @@ public class DebugSettingsFragment extends BasePreferenceFragment { @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.debug_settings); } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index b8ce0ec18..aaa572eab 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -32,13 +32,12 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; public class DownloadSettingsFragment extends BasePreferenceFragment { + public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; private static final int REQUEST_DOWNLOAD_VIDEO_PATH = 0x1235; private static final int REQUEST_DOWNLOAD_AUDIO_PATH = 0x1236; - public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; - - private String DOWNLOAD_PATH_VIDEO_PREFERENCE; - private String DOWNLOAD_PATH_AUDIO_PREFERENCE; - private String STORAGE_USE_SAF_PREFERENCE; + private String downloadPathVideoPreference; + private String downloadPathAudioPreference; + private String storageUseSafPreference; private Preference prefPathVideo; private Preference prefPathAudio; @@ -47,16 +46,16 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { private Context ctx; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - DOWNLOAD_PATH_VIDEO_PREFERENCE = getString(R.string.download_path_video_key); - DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key); - STORAGE_USE_SAF_PREFERENCE = getString(R.string.storage_use_saf); + downloadPathVideoPreference = getString(R.string.download_path_video_key); + downloadPathAudioPreference = getString(R.string.download_path_audio_key); + storageUseSafPreference = getString(R.string.storage_use_saf); final String downloadStorageAsk = getString(R.string.downloads_storage_ask); - prefPathVideo = findPreference(DOWNLOAD_PATH_VIDEO_PREFERENCE); - prefPathAudio = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE); + prefPathVideo = findPreference(downloadPathVideoPreference); + prefPathAudio = findPreference(downloadPathAudioPreference); prefStorageAsk = findPreference(downloadStorageAsk); updatePreferencesSummary(); @@ -66,7 +65,8 @@ public void onCreate(@Nullable Bundle savedInstanceState) { prefStorageAsk.setSummary(R.string.downloads_storage_ask_summary); } - if (hasInvalidPath(DOWNLOAD_PATH_VIDEO_PREFERENCE) || hasInvalidPath(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { + if (hasInvalidPath(downloadPathVideoPreference) + || hasInvalidPath(downloadPathAudioPreference)) { updatePreferencesSummary(); } @@ -77,12 +77,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.download_settings); } @Override - public void onAttach(Context context) { + public void onAttach(final Context context) { super.onAttach(context); ctx = context; } @@ -95,11 +95,14 @@ public void onDetach() { } private void updatePreferencesSummary() { - showPathInSummary(DOWNLOAD_PATH_VIDEO_PREFERENCE, R.string.download_path_summary, prefPathVideo); - showPathInSummary(DOWNLOAD_PATH_AUDIO_PREFERENCE, R.string.download_path_audio_summary, prefPathAudio); + showPathInSummary(downloadPathVideoPreference, R.string.download_path_summary, + prefPathVideo); + showPathInSummary(downloadPathAudioPreference, R.string.download_path_audio_summary, + prefPathAudio); } - private void showPathInSummary(String prefKey, @StringRes int defaultString, Preference target) { + private void showPathInSummary(final String prefKey, @StringRes final int defaultString, + final Preference target) { String rawUri = defaultPreferences.getString(prefKey, null); if (rawUri == null || rawUri.isEmpty()) { target.setSummary(getString(defaultString)); @@ -124,33 +127,36 @@ private void showPathInSummary(String prefKey, @StringRes int defaultString, Pre target.setSummary(rawUri); } - private boolean isFileUri(String path) { + private boolean isFileUri(final String path) { return path.charAt(0) == File.separatorChar || path.startsWith(ContentResolver.SCHEME_FILE); } - private boolean hasInvalidPath(String prefKey) { + private boolean hasInvalidPath(final String prefKey) { String value = defaultPreferences.getString(prefKey, null); return value == null || value.isEmpty(); } - private void updatePathPickers(boolean enabled) { + private void updatePathPickers(final boolean enabled) { prefPathVideo.setEnabled(enabled); prefPathAudio.setEnabled(enabled); } // FIXME: after releasing the old path, all downloads created on the folder becomes inaccessible - private void forgetSAFTree(Context ctx, String oldPath) { + private void forgetSAFTree(final Context context, final String oldPath) { if (IGNORE_RELEASE_ON_OLD_PATH) { return; } - if (oldPath == null || oldPath.isEmpty() || isFileUri(oldPath)) return; + if (oldPath == null || oldPath.isEmpty() || isFileUri(oldPath)) { + return; + } try { Uri uri = Uri.parse(oldPath); - ctx.getContentResolver().releasePersistableUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS); - ctx.revokeUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS); + context.getContentResolver() + .releasePersistableUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS); + context.revokeUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS); Log.i(TAG, "Revoke old path permissions success on " + oldPath); } catch (Exception err) { @@ -158,7 +164,7 @@ private void forgetSAFTree(Context ctx, String oldPath) { } } - private void showMessageDialog(@StringRes int title, @StringRes int message) { + private void showMessageDialog(@StringRes final int title, @StringRes final int message) { AlertDialog.Builder msg = new AlertDialog.Builder(ctx); msg.setTitle(title); msg.setMessage(message); @@ -167,35 +173,40 @@ private void showMessageDialog(@StringRes int title, @StringRes int message) { } @Override - public boolean onPreferenceTreeClick(Preference preference) { + public boolean onPreferenceTreeClick(final Preference preference) { if (DEBUG) { - Log.d(TAG, "onPreferenceTreeClick() called with: preference = [" + preference + "]"); + Log.d(TAG, "onPreferenceTreeClick() called with: " + + "preference = [" + preference + "]"); } String key = preference.getKey(); int request; - if (key.equals(STORAGE_USE_SAF_PREFERENCE)) { - Toast.makeText(getContext(), R.string.download_choose_new_path, Toast.LENGTH_LONG).show(); + if (key.equals(storageUseSafPreference)) { + Toast.makeText(getContext(), R.string.download_choose_new_path, + Toast.LENGTH_LONG).show(); return true; - } else if (key.equals(DOWNLOAD_PATH_VIDEO_PREFERENCE)) { + } else if (key.equals(downloadPathVideoPreference)) { request = REQUEST_DOWNLOAD_VIDEO_PATH; - } else if (key.equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { + } else if (key.equals(downloadPathAudioPreference)) { request = REQUEST_DOWNLOAD_AUDIO_PATH; } else { return super.onPreferenceTreeClick(preference); } Intent i; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && NewPipeSettings.useStorageAccessFramework(ctx)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && NewPipeSettings.useStorageAccessFramework(ctx)) { i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) .putExtra("android.content.extra.SHOW_ADVANCED", true) - .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | StoredDirectoryHelper.PERMISSION_FLAGS); + .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | StoredDirectoryHelper.PERMISSION_FLAGS); } else { i = new Intent(getActivity(), FilePickerActivityHelper.class) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) - .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR); + .putExtra(FilePickerActivityHelper.EXTRA_MODE, + FilePickerActivityHelper.MODE_DIR); } startActivityForResult(i, request); @@ -204,24 +215,28 @@ public boolean onPreferenceTreeClick(Preference preference) { } @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { assureCorrectAppLanguage(getContext()); super.onActivityResult(requestCode, resultCode, data); if (DEBUG) { - Log.d(TAG, "onActivityResult() called with: requestCode = [" + requestCode + "], " + - "resultCode = [" + resultCode + "], data = [" + data + "]" + Log.d(TAG, "onActivityResult() called with: " + + "requestCode = [" + requestCode + "], " + + "resultCode = [" + resultCode + "], data = [" + data + "]" ); } - if (resultCode != Activity.RESULT_OK) return; + if (resultCode != Activity.RESULT_OK) { + return; + } String key; - if (requestCode == REQUEST_DOWNLOAD_VIDEO_PATH) - key = DOWNLOAD_PATH_VIDEO_PREFERENCE; - else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) - key = DOWNLOAD_PATH_AUDIO_PREFERENCE; - else + if (requestCode == REQUEST_DOWNLOAD_VIDEO_PATH) { + key = downloadPathVideoPreference; + } else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) { + key = downloadPathAudioPreference; + } else { return; + } Uri uri = data.getData(); if (uri == null) { @@ -231,23 +246,28 @@ else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) // revoke permissions on the old save path (required for SAF only) - final Context ctx = getContext(); - if (ctx == null) throw new NullPointerException("getContext()"); + final Context context = getContext(); + if (context == null) { + throw new NullPointerException("getContext()"); + } - forgetSAFTree(ctx, defaultPreferences.getString(key, "")); + forgetSAFTree(context, defaultPreferences.getString(key, "")); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !FilePickerActivityHelper.isOwnFileUri(ctx, uri)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && !FilePickerActivityHelper.isOwnFileUri(context, uri)) { // steps to acquire the selected path: // 1. acquire permissions on the new save path // 2. save the new path, if step(2) was successful try { - ctx.grantUriPermission(ctx.getPackageName(), uri, StoredDirectoryHelper.PERMISSION_FLAGS); + context.grantUriPermission(context.getPackageName(), uri, + StoredDirectoryHelper.PERMISSION_FLAGS); - StoredDirectoryHelper mainStorage = new StoredDirectoryHelper(ctx, uri, null); + StoredDirectoryHelper mainStorage = new StoredDirectoryHelper(context, uri, null); Log.i(TAG, "Acquiring tree success from " + uri.toString()); - if (!mainStorage.canWrite()) + if (!mainStorage.canWrite()) { throw new IOException("No write permissions on " + uri.toString()); + } } catch (IOException err) { Log.e(TAG, "Error acquiring tree from " + uri.toString(), err); showMessageDialog(R.string.general_error, R.string.no_available_dir); @@ -256,7 +276,8 @@ else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) } else { File target = Utils.getFileForUri(uri); if (!target.canWrite()) { - showMessageDialog(R.string.download_to_sdcard_error_title, R.string.download_to_sdcard_error_message); + showMessageDialog(R.string.download_to_sdcard_error_title, + R.string.download_to_sdcard_error_message); return; } uri = Uri.fromFile(target); diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java index cdfbf54a7..d9b404204 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -1,10 +1,11 @@ package org.schabi.newpipe.settings; import android.os.Bundle; +import android.widget.Toast; + import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; -import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -25,7 +26,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment { private CompositeDisposable disposables; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); cacheWipeKey = getString(R.string.metadata_cache_wipe_key); viewsHistoryClearKey = getString(R.string.clear_views_history_key); @@ -36,12 +37,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.history_settings); } @Override - public boolean onPreferenceTreeClick(Preference preference) { + public boolean onPreferenceTreeClick(final Preference preference) { if (preference.getKey().equals(cacheWipeKey)) { InfoCache.getInstance().clearCache(); Toast.makeText(preference.getContext(), R.string.metadata_cache_wipe_complete_notice, @@ -53,7 +54,8 @@ public boolean onPreferenceTreeClick(Preference preference) { .setTitle(R.string.delete_view_history_alert) .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss())) .setPositiveButton(R.string.delete, ((dialog, which) -> { - final Disposable onDeletePlaybackStates = recordManager.deleteCompelteStreamStateHistory() + final Disposable onDeletePlaybackStates + = recordManager.deleteCompelteStreamStateHistory() .observeOn(AndroidSchedulers.mainThread()) .subscribe( howManyDeleted -> Toast.makeText(getActivity(), @@ -86,7 +88,8 @@ public boolean onPreferenceTreeClick(Preference preference) { final Disposable onClearOrphans = recordManager.removeOrphanedRecords() .observeOn(AndroidSchedulers.mainThread()) .subscribe( - howManyDeleted -> {}, + howManyDeleted -> { + }, throwable -> ErrorActivity.reportError(getContext(), throwable, SettingsActivity.class, null, @@ -109,7 +112,8 @@ public boolean onPreferenceTreeClick(Preference preference) { .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss())) .setPositiveButton(R.string.delete, ((dialog, which) -> { - final Disposable onDeletePlaybackStates = recordManager.deleteCompelteStreamStateHistory() + final Disposable onDeletePlaybackStates + = recordManager.deleteCompelteStreamStateHistory() .observeOn(AndroidSchedulers.mainThread()) .subscribe( howManyDeleted -> Toast.makeText(getActivity(), diff --git a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java index 70460509d..159625c92 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.settings; import android.os.Bundle; + import androidx.preference.Preference; import org.schabi.newpipe.BuildConfig; @@ -11,7 +12,7 @@ public class MainSettingsFragment extends BasePreferenceFragment { public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.main_settings); if (!CheckForNewAppVersionTask.isGithubApk()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index 6c765dc3d..47a16f6f3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -1,41 +1,20 @@ -/* - * Created by k3b on 07.01.2016. - * - * Copyright (C) Christian Schabesberger 2015 - * NewPipeSettings.java is part of NewPipe. - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - package org.schabi.newpipe.settings; import android.content.Context; import android.content.SharedPreferences; import android.os.Environment; -import androidx.preference.PreferenceManager; + import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import java.io.File; -/** - * Helper for global settings - */ - /* - * Copyright (C) Christian Schabesberger 2016 + * Created by k3b on 07.01.2016. + * + * Copyright (C) Christian Schabesberger 2015 * NewPipeSettings.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify @@ -52,12 +31,13 @@ * along with NewPipe. If not, see . */ -public class NewPipeSettings { - - private NewPipeSettings() { - } +/** + * Helper class for global settings. + */ +public final class NewPipeSettings { + private NewPipeSettings() { } - public static void initSettings(Context context) { + public static void initSettings(final Context context) { PreferenceManager.setDefaultValues(context, R.xml.appearance_settings, true); PreferenceManager.setDefaultValues(context, R.xml.content_settings, true); PreferenceManager.setDefaultValues(context, R.xml.download_settings, true); @@ -70,19 +50,22 @@ public static void initSettings(Context context) { getAudioDownloadFolder(context); } - private static void getVideoDownloadFolder(Context context) { + private static void getVideoDownloadFolder(final Context context) { getDir(context, R.string.download_path_video_key, Environment.DIRECTORY_MOVIES); } - private static void getAudioDownloadFolder(Context context) { + private static void getAudioDownloadFolder(final Context context) { getDir(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC); } - private static void getDir(Context context, int keyID, String defaultDirectoryName) { + private static void getDir(final Context context, final int keyID, + final String defaultDirectoryName) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String key = context.getString(keyID); String downloadPath = prefs.getString(key, null); - if ((downloadPath != null) && (!downloadPath.isEmpty())) return; + if ((downloadPath != null) && (!downloadPath.isEmpty())) { + return; + } SharedPreferences.Editor spEditor = prefs.edit(); spEditor.putString(key, getNewPipeChildFolderPathForDir(getDir(defaultDirectoryName))); @@ -90,15 +73,15 @@ private static void getDir(Context context, int keyID, String defaultDirectoryNa } @NonNull - public static File getDir(String defaultDirectoryName) { + public static File getDir(final String defaultDirectoryName) { return new File(Environment.getExternalStorageDirectory(), defaultDirectoryName); } - private static String getNewPipeChildFolderPathForDir(File dir) { + private static String getNewPipeChildFolderPathForDir(final File dir) { return new File(dir, "NewPipe").toURI().toString(); } - public static boolean useStorageAccessFramework(Context context) { + public static boolean useStorageAccessFramework(final Context context) { final String key = context.getString(R.string.storage_use_saf); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index a0c16af75..1242c39e8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -53,23 +53,21 @@ import io.reactivex.schedulers.Schedulers; public class PeertubeInstanceListFragment extends Fragment { - + private static final int MENU_ITEM_RESTORE_ID = 123456; + public InstanceListAdapter instanceListAdapter; private List instanceList = new ArrayList<>(); private PeertubeInstance selectedInstance; private String savedInstanceListKey; - public InstanceListAdapter instanceListAdapter; - private ProgressBar progressBar; private SharedPreferences sharedPreferences; - private CompositeDisposable disposables = new CompositeDisposable(); - /*////////////////////////////////////////////////////////////////////////// // Lifecycle //////////////////////////////////////////////////////////////////////////*/ + private CompositeDisposable disposables = new CompositeDisposable(); @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); @@ -81,20 +79,23 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } @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_instance_list, container, false); } @Override - public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View rootView, + @Nullable final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); initViews(rootView); } - private void initViews(@NonNull View rootView) { + private void initViews(@NonNull final View rootView) { TextView instanceHelpTV = rootView.findViewById(R.id.instanceHelpTV); - instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, getString(R.string.peertube_instance_list_url))); + instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, + getString(R.string.peertube_instance_list_url))); initButton(rootView); @@ -121,32 +122,34 @@ public void onPause() { super.onPause(); saveChanges(); } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ @Override public void onDestroy() { super.onDestroy(); - if (disposables != null) disposables.clear(); + if (disposables != null) { + disposables.clear(); + } disposables = null; } - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - - private final int MENU_ITEM_RESTORE_ID = 123456; @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); + final MenuItem restoreItem = menu + .add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults); + final int restoreIcon = ThemeHelper + .resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults); restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon)); } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == MENU_ITEM_RESTORE_ID) { restoreDefaults(); return true; @@ -164,7 +167,7 @@ private void updateInstanceList() { instanceList.addAll(PeertubeHelper.getInstanceList(requireContext())); } - private void selectInstance(PeertubeInstance instance) { + private void selectInstance(final PeertubeInstance instance) { selectedInstance = PeertubeHelper.selectInstance(instance, requireContext()); sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); } @@ -172,7 +175,9 @@ private void selectInstance(PeertubeInstance instance) { private void updateTitle() { if (getActivity() instanceof AppCompatActivity) { ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - if (actionBar != null) actionBar.setTitle(R.string.peertube_instance_url_title); + if (actionBar != null) { + actionBar.setTitle(R.string.peertube_instance_url_title); + } } } @@ -202,14 +207,14 @@ private void restoreDefaults() { .show(); } - private void initButton(View rootView) { + private void initButton(final View rootView) { final FloatingActionButton fab = rootView.findViewById(R.id.addInstanceButton); fab.setOnClickListener(v -> { showAddItemDialog(requireContext()); }); } - private void showAddItemDialog(Context c) { + private void showAddItemDialog(final Context c) { final EditText urlET = new EditText(c); urlET.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); urlET.setHint(R.string.peertube_instance_add_help); @@ -226,46 +231,52 @@ private void showAddItemDialog(Context c) { dialog.show(); } - private void addInstance(String url) { + private void addInstance(final String url) { String cleanUrl = cleanUrl(url); - if(null == cleanUrl) return; + if (cleanUrl == null) { + return; + } progressBar.setVisibility(View.VISIBLE); Disposable disposable = Single.fromCallable(() -> { PeertubeInstance instance = new PeertubeInstance(cleanUrl); instance.fetchInstanceMetaData(); return instance; - }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe((instance) -> { - progressBar.setVisibility(View.GONE); - add(instance); - }, e -> { - progressBar.setVisibility(View.GONE); - Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, Toast.LENGTH_SHORT).show(); - }); + }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) + .subscribe((instance) -> { + progressBar.setVisibility(View.GONE); + add(instance); + }, e -> { + progressBar.setVisibility(View.GONE); + Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, + Toast.LENGTH_SHORT).show(); + }); disposables.add(disposable); } @Nullable - private String cleanUrl(String url){ - url = url.trim(); + private String cleanUrl(final String url) { + String cleanUrl = url.trim(); // if protocol not present, add https - if(!url.startsWith("http")){ - url = "https://" + url; + if (!cleanUrl.startsWith("http")) { + cleanUrl = "https://" + cleanUrl; } // remove trailing slash - url = url.replaceAll("/$", ""); + cleanUrl = cleanUrl.replaceAll("/$", ""); // only allow https - if (!url.startsWith("https://")) { - Toast.makeText(getActivity(), R.string.peertube_instance_add_https_only, Toast.LENGTH_SHORT).show(); + if (!cleanUrl.startsWith("https://")) { + Toast.makeText(getActivity(), R.string.peertube_instance_add_https_only, + Toast.LENGTH_SHORT).show(); return null; } // only allow if not already exists for (PeertubeInstance instance : instanceList) { - if (instance.getUrl().equals(url)) { - Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, Toast.LENGTH_SHORT).show(); + if (instance.getUrl().equals(cleanUrl)) { + Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, + Toast.LENGTH_SHORT).show(); return null; } } - return url; + return cleanUrl; } private void add(final PeertubeInstance instance) { @@ -277,30 +288,93 @@ private void add(final PeertubeInstance instance) { // List Handling //////////////////////////////////////////////////////////////////////////*/ - private class InstanceListAdapter extends RecyclerView.Adapter { - private ItemTouchHelper itemTouchHelper; + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, + final int viewSize, + final int viewSizeOutOfBounds, + final int totalSize, + final long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int minimumAbsVelocity = Math.max(12, + Math.abs(standardSpeed)); + return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(final RecyclerView recyclerView, + final RecyclerView.ViewHolder source, + final RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() + || instanceListAdapter == null) { + return false; + } + + final int sourceIndex = source.getAdapterPosition(); + final int targetIndex = target.getAdapterPosition(); + instanceListAdapter.swapItems(sourceIndex, targetIndex); + return true; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return true; + } + + @Override + public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { + int position = viewHolder.getAdapterPosition(); + // do not allow swiping the selected instance + if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { + instanceListAdapter.notifyItemChanged(position); + return; + } + instanceList.remove(position); + instanceListAdapter.notifyItemRemoved(position); + + if (instanceList.isEmpty()) { + instanceList.add(selectedInstance); + instanceListAdapter.notifyItemInserted(0); + } + } + }; + } + + private class InstanceListAdapter + extends RecyclerView.Adapter { private final LayoutInflater inflater; + private ItemTouchHelper itemTouchHelper; private RadioButton lastChecked; - InstanceListAdapter(Context context, ItemTouchHelper itemTouchHelper) { + InstanceListAdapter(final Context context, final ItemTouchHelper itemTouchHelper) { this.itemTouchHelper = itemTouchHelper; this.inflater = LayoutInflater.from(context); } - public void swapItems(int fromPosition, int toPosition) { + public void swapItems(final int fromPosition, final int toPosition) { Collections.swap(instanceList, fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition); } @NonNull @Override - public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int viewType) { View view = inflater.inflate(R.layout.item_instance, parent, false); return new InstanceListAdapter.TabViewHolder(view); } @Override - public void onBindViewHolder(@NonNull InstanceListAdapter.TabViewHolder holder, int position) { + public void onBindViewHolder(@NonNull final InstanceListAdapter.TabViewHolder holder, + final int position) { holder.bind(position, holder); } @@ -316,7 +390,7 @@ class TabViewHolder extends RecyclerView.ViewHolder { private RadioButton instanceRB; private ImageView handle; - TabViewHolder(View itemView) { + TabViewHolder(final View itemView) { super(itemView); instanceIconView = itemView.findViewById(R.id.instanceIcon); @@ -327,7 +401,7 @@ class TabViewHolder extends RecyclerView.ViewHolder { } @SuppressLint("ClickableViewAccessibility") - void bind(int position, TabViewHolder holder) { + void bind(final int position, final TabViewHolder holder) { handle.setOnTouchListener(getOnTouchListener(holder)); final PeertubeInstance instance = instanceList.get(position); @@ -367,61 +441,4 @@ private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder it } } } - - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, - ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, - int viewSizeOutOfBounds, int totalSize, - long msSinceStartScroll) { - final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, - viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int minimumAbsVelocity = Math.max(12, - Math.abs(standardSpeed)); - return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, - RecyclerView.ViewHolder target) { - if (source.getItemViewType() != target.getItemViewType() || - instanceListAdapter == null) { - return false; - } - - final int sourceIndex = source.getAdapterPosition(); - final int targetIndex = target.getAdapterPosition(); - instanceListAdapter.swapItems(sourceIndex, targetIndex); - return true; - } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - - @Override - public boolean isItemViewSwipeEnabled() { - return true; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - int position = viewHolder.getAdapterPosition(); - // do not allow swiping the selected instance - if(instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { - instanceListAdapter.notifyItemChanged(position); - return; - } - instanceList.remove(position); - instanceListAdapter.notifyItemRemoved(position); - - if (instanceList.isEmpty()) { - instanceList.add(selectedInstance); - instanceListAdapter.notifyItemInserted(0); - } - } - }; - } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index 9ee12facc..2621a38e5 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -3,16 +3,17 @@ import android.app.Activity; import android.content.DialogInterface; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; @@ -31,61 +32,56 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; - /** * Created by Christian Schabesberger on 26.09.17. * SelectChannelFragment.java is part of NewPipe. - * + *

* 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. - * + *

+ *

* 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. - * + *

+ *

* You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . + *

*/ public class SelectChannelFragment extends DialogFragment { + /** + * This contains the base display options for images. + */ + public static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS + = new DisplayImageOptions.Builder().cacheInMemory(true).build(); private final ImageLoader imageLoader = ImageLoader.getInstance(); - + OnSelectedLisener onSelectedLisener = null; + OnCancelListener onCancelListener = null; private ProgressBar progressBar; - private TextView emptyView; - private RecyclerView recyclerView; - - private List subscriptions = new Vector<>(); /*////////////////////////////////////////////////////////////////////////// // Interfaces //////////////////////////////////////////////////////////////////////////*/ + private TextView emptyView; + private RecyclerView recyclerView; + private List subscriptions = new Vector<>(); - public interface OnSelectedLisener { - void onChannelSelected(int serviceId, String url, String name); - } - OnSelectedLisener onSelectedLisener = null; - public void setOnSelectedLisener(OnSelectedLisener listener) { + public void setOnSelectedLisener(final OnSelectedLisener listener) { onSelectedLisener = listener; } - public interface OnCancelListener { - void onCancel(); - } - OnCancelListener onCancelListener = null; - public void setOnCancelListener(OnCancelListener listener) { + public void setOnCancelListener(final OnCancelListener listener) { onCancelListener = listener; } - /*////////////////////////////////////////////////////////////////////////// - // Init - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { View v = inflater.inflate(R.layout.select_channel_fragment, container, false); recyclerView = v.findViewById(R.id.items_list); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); @@ -108,35 +104,36 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, return v; } - /*////////////////////////////////////////////////////////////////////////// - // Handle actions + // Init //////////////////////////////////////////////////////////////////////////*/ @Override public void onCancel(final DialogInterface dialogInterface) { super.onCancel(dialogInterface); - if(onCancelListener != null) { + if (onCancelListener != null) { onCancelListener.onCancel(); } } - private void clickedItem(int position) { - if(onSelectedLisener != null) { + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + private void clickedItem(final int position) { + if (onSelectedLisener != null) { SubscriptionEntity entry = subscriptions.get(position); - onSelectedLisener.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName()); + onSelectedLisener + .onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName()); } dismiss(); } - /*////////////////////////////////////////////////////////////////////////// - // Item handling - //////////////////////////////////////////////////////////////////////////*/ - - private void displayChannels(List subscriptions) { - this.subscriptions = subscriptions; + private void displayChannels(final List newSubscriptions) { + this.subscriptions = newSubscriptions; progressBar.setVisibility(View.GONE); - if(subscriptions.isEmpty()) { + if (newSubscriptions.isEmpty()) { emptyView.setVisibility(View.VISIBLE); return; } @@ -144,49 +141,75 @@ private void displayChannels(List subscriptions) { } + /*////////////////////////////////////////////////////////////////////////// + // Item handling + //////////////////////////////////////////////////////////////////////////*/ + private Observer> getSubscriptionObserver() { return new Observer>() { @Override - public void onSubscribe(Disposable d) { - } + public void onSubscribe(final Disposable d) { } @Override - public void onNext(List subscriptions) { - displayChannels(subscriptions); + public void onNext(final List newSubscriptions) { + displayChannels(newSubscriptions); } @Override - public void onError(Throwable exception) { + public void onError(final Throwable exception) { SelectChannelFragment.this.onError(exception); } @Override - public void onComplete() { - } + public void onComplete() { } }; } - private class SelectChannelAdapter extends - RecyclerView.Adapter { + protected void onError(final Throwable e) { + final Activity activity = getActivity(); + ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo + .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + } + + public interface OnSelectedLisener { + void onChannelSelected(int serviceId, String url, String name); + } + + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + + public interface OnCancelListener { + void onCancel(); + } + + + /*////////////////////////////////////////////////////////////////////////// + // ImageLoaderOptions + //////////////////////////////////////////////////////////////////////////*/ + private class SelectChannelAdapter + extends RecyclerView.Adapter { @Override - public SelectChannelItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent, + final int viewType) { View item = LayoutInflater.from(parent.getContext()) .inflate(R.layout.select_channel_item, parent, false); return new SelectChannelItemHolder(item); } @Override - public void onBindViewHolder(SelectChannelItemHolder holder, final int position) { + public void onBindViewHolder(final SelectChannelItemHolder holder, final int position) { SubscriptionEntity entry = subscriptions.get(position); holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { clickedItem(position); } }); - imageLoader.displayImage(entry.getAvatarUrl(), holder.thumbnailView, DISPLAY_IMAGE_OPTIONS); + imageLoader.displayImage(entry.getAvatarUrl(), holder.thumbnailView, + DISPLAY_IMAGE_OPTIONS); } @Override @@ -195,41 +218,15 @@ public int getItemCount() { } public class SelectChannelItemHolder extends RecyclerView.ViewHolder { - public SelectChannelItemHolder(View v) { + public final View view; + public final CircleImageView thumbnailView; + public final TextView titleView; + SelectChannelItemHolder(final View v) { super(v); this.view = v; thumbnailView = v.findViewById(R.id.itemThumbnailView); titleView = v.findViewById(R.id.itemTitleView); } - public final View view; - public final CircleImageView thumbnailView; - public final TextView titleView; } } - - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - - protected void onError(Throwable e) { - final Activity activity = getActivity(); - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); - } - - - /*////////////////////////////////////////////////////////////////////////// - // ImageLoaderOptions - //////////////////////////////////////////////////////////////////////////*/ - - /** - * Base display options - */ - public static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS = - new DisplayImageOptions.Builder() - .cacheInMemory(true) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index d97e4f1b7..014e108f9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -3,17 +3,17 @@ import android.app.Activity; import android.content.DialogInterface; import android.os.Bundle; -import androidx.fragment.app.DialogFragment; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.LinearLayoutManager; -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 org.schabi.newpipe.MainActivity; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; @@ -28,51 +28,45 @@ /** * Created by Christian Schabesberger on 09.10.17. * SelectKioskFragment.java is part of NewPipe. - * + *

* 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. - * + *

+ *

* 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. - * + *

+ *

* You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . + * along with NewPipe. If not, see . + *

*/ public class SelectKioskFragment extends DialogFragment { + private RecyclerView recyclerView = null; + private SelectKioskAdapter selectKioskAdapter = null; - private static final boolean DEBUG = MainActivity.DEBUG; - - RecyclerView recyclerView = null; - SelectKioskAdapter selectKioskAdapter = null; - - /*////////////////////////////////////////////////////////////////////////// + /*////////////////////////////////////////////////////////////////////////// // Interfaces //////////////////////////////////////////////////////////////////////////*/ + private OnSelectedLisener onSelectedLisener = null; + private OnCancelListener onCancelListener = null; - public interface OnSelectedLisener { - void onKioskSelected(int serviceId, String kioskId, String kioskName); - } - - OnSelectedLisener onSelectedLisener = null; - public void setOnSelectedLisener(OnSelectedLisener listener) { + public void setOnSelectedLisener(final OnSelectedLisener listener) { onSelectedLisener = listener; } - public interface OnCancelListener { - void onCancel(); - } - OnCancelListener onCancelListener = null; - public void setOnCancelListener(OnCancelListener listener) { + public void setOnCancelListener(final OnCancelListener listener) { onCancelListener = listener; } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false); recyclerView = v.findViewById(R.id.items_list); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); @@ -86,52 +80,55 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return v; } - /*////////////////////////////////////////////////////////////////////////// - // Handle actions - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onCancel(final DialogInterface dialogInterface) { super.onCancel(dialogInterface); - if(onCancelListener != null) { + if (onCancelListener != null) { onCancelListener.onCancel(); } } - private void clickedItem(SelectKioskAdapter.Entry entry) { - if(onSelectedLisener != null) { + private void clickedItem(final SelectKioskAdapter.Entry entry) { + if (onSelectedLisener != null) { onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName); } dismiss(); } + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + protected void onError(final Throwable e) { + final Activity activity = getActivity(); + ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo + .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + } + + public interface OnSelectedLisener { + void onKioskSelected(int serviceId, String kioskId, String kioskName); + } + + public interface OnCancelListener { + void onCancel(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + private class SelectKioskAdapter extends RecyclerView.Adapter { - public class Entry { - public Entry (int i, int si, String ki, String kn){ - icon = i; serviceId=si; kioskId=ki; kioskName = kn; - } - final int icon; - final int serviceId; - final String kioskId; - final String kioskName; - } - private final List kioskList = new Vector<>(); - public SelectKioskAdapter() - throws Exception { - - for(StreamingService service : NewPipe.getServices()) { - for(String kioskId : service.getKioskList().getAvailableKiosks()) { + SelectKioskAdapter() throws Exception { + for (StreamingService service : NewPipe.getServices()) { + for (String kioskId : service.getKioskList().getAvailableKiosks()) { String name = String.format(getString(R.string.service_kiosk_string), service.getServiceInfo().getName(), KioskTranslator.getTranslatedKioskName(kioskId, getContext())); - kioskList.add(new Entry( - ServiceHelper.getIcon(service.getServiceId()), - service.getServiceId(), - kioskId, - name)); + kioskList.add(new Entry(ServiceHelper.getIcon(service.getServiceId()), + service.getServiceId(), kioskId, name)); } } } @@ -140,47 +137,50 @@ public int getItemCount() { return kioskList.size(); } - public SelectKioskItemHolder onCreateViewHolder(ViewGroup parent, int type) { + public SelectKioskItemHolder onCreateViewHolder(final ViewGroup parent, final int type) { View item = LayoutInflater.from(parent.getContext()) .inflate(R.layout.select_kiosk_item, parent, false); return new SelectKioskItemHolder(item); } - public class SelectKioskItemHolder extends RecyclerView.ViewHolder { - public SelectKioskItemHolder(View v) { - super(v); - this.view = v; - thumbnailView = v.findViewById(R.id.itemThumbnailView); - titleView = v.findViewById(R.id.itemTitleView); - } - public final View view; - public final ImageView thumbnailView; - public final TextView titleView; - } - - public void onBindViewHolder(SelectKioskItemHolder holder, final int position) { + public void onBindViewHolder(final SelectKioskItemHolder holder, final int position) { final Entry entry = kioskList.get(position); holder.titleView.setText(entry.kioskName); - holder.thumbnailView.setImageDrawable(ContextCompat.getDrawable(getContext(), entry.icon)); + holder.thumbnailView + .setImageDrawable(ContextCompat.getDrawable(getContext(), entry.icon)); holder.view.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { clickedItem(entry); } }); } - } - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ + class Entry { + final int icon; + final int serviceId; + final String kioskId; + final String kioskName; - protected void onError(Throwable e) { - final Activity activity = getActivity(); - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); + Entry(final int i, final int si, final String ki, final String kn) { + icon = i; + serviceId = si; + kioskId = ki; + kioskName = kn; + } + } + + public class SelectKioskItemHolder extends RecyclerView.ViewHolder { + public final View view; + final ImageView thumbnailView; + final TextView titleView; + + SelectKioskItemHolder(final View v) { + super(v); + this.view = v; + thumbnailView = v.findViewById(R.id.itemThumbnailView); + titleView = v.findViewById(R.id.itemTitleView); + } + } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index 53d60f86c..ce5d026db 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -2,14 +2,15 @@ import android.content.Context; import android.os.Bundle; -import androidx.fragment.app.Fragment; +import android.view.Menu; +import android.view.MenuItem; + import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; -import androidx.appcompat.widget.Toolbar; -import android.view.Menu; -import android.view.MenuItem; import org.schabi.newpipe.R; import org.schabi.newpipe.util.ThemeHelper; @@ -36,14 +37,15 @@ * along with NewPipe. If not, see . */ -public class SettingsActivity extends AppCompatActivity implements BasePreferenceFragment.OnPreferenceStartFragmentCallback { +public class SettingsActivity extends AppCompatActivity + implements BasePreferenceFragment.OnPreferenceStartFragmentCallback { - public static void initSettings(Context context) { + public static void initSettings(final Context context) { NewPipeSettings.initSettings(context); } @Override - protected void onCreate(Bundle savedInstanceBundle) { + protected void onCreate(final Bundle savedInstanceBundle) { setTheme(ThemeHelper.getSettingsThemeStyle(this)); assureCorrectAppLanguage(this); super.onCreate(savedInstanceBundle); @@ -60,7 +62,7 @@ protected void onCreate(Bundle savedInstanceBundle) { } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); @@ -71,22 +73,27 @@ public boolean onCreateOptionsMenu(Menu menu) { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { finish(); - } else getSupportFragmentManager().popBackStack(); + } else { + getSupportFragmentManager().popBackStack(); + } } return super.onOptionsItemSelected(item); } @Override - public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference preference) { - Fragment fragment = Fragment.instantiate(this, preference.getFragment(), preference.getExtras()); + public boolean onPreferenceStartFragment(final PreferenceFragmentCompat caller, + final Preference preference) { + Fragment fragment = Fragment + .instantiate(this, preference.getFragment(), preference.getExtras()); getSupportFragmentManager().beginTransaction() - .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out) + .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, + R.animator.custom_fade_in, R.animator.custom_fade_out) .replace(R.id.fragment_holder, fragment) .addToBackStack(null) .commit(); diff --git a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java index 9a4d59549..2b103e794 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java @@ -1,15 +1,22 @@ package org.schabi.newpipe.settings; import android.os.Bundle; + import androidx.annotation.Nullable; import androidx.preference.Preference; import org.schabi.newpipe.R; public class UpdateSettingsFragment extends BasePreferenceFragment { + private Preference.OnPreferenceChangeListener updatePreferenceChange + = (preference, newValue) -> { + defaultPreferences.edit() + .putBoolean(getString(R.string.update_app_key), (boolean) newValue).apply(); + return true; + }; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); String updateToggleKey = getString(R.string.update_app_key); @@ -17,16 +24,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.update_settings); } - - private Preference.OnPreferenceChangeListener updatePreferenceChange - = (preference, newValue) -> { - - defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), - (boolean) newValue).apply(); - - return true; - }; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java index 383cf7f74..bef9a7b56 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java @@ -5,44 +5,45 @@ import android.os.Build; import android.os.Bundle; import android.provider.Settings; - import android.text.format.DateUtils; import android.widget.Toast; + import androidx.annotation.Nullable; import androidx.preference.ListPreference; import com.google.android.material.snackbar.Snackbar; -import java.util.LinkedList; -import java.util.List; import org.schabi.newpipe.R; import org.schabi.newpipe.util.PermissionHelper; -public class VideoAudioSettingsFragment extends BasePreferenceFragment { +import java.util.LinkedList; +import java.util.List; +public class VideoAudioSettingsFragment extends BasePreferenceFragment { private SharedPreferences.OnSharedPreferenceChangeListener listener; @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); updateSeekOptions(); listener = (sharedPreferences, s) -> { - // on M and above, if user chooses to minimise to popup player on exit and the app doesn't have - // display over other apps permission, show a snackbar to let the user give permission - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - s.equals(getString(R.string.minimize_on_exit_key))) { - + // on M and above, if user chooses to minimise to popup player on exit + // and the app doesn't have display over other apps permission, + // show a snackbar to let the user give permission + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && s.equals(getString(R.string.minimize_on_exit_key))) { String newSetting = sharedPreferences.getString(s, null); if (newSetting != null && newSetting.equals(getString(R.string.minimize_on_exit_popup_key)) && !Settings.canDrawOverlays(getContext())) { - Snackbar.make(getListView(), R.string.permission_display_over_apps, Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.settings, - view -> PermissionHelper.checkSystemAlertWindowPermission(getContext())) + Snackbar.make(getListView(), R.string.permission_display_over_apps, + Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.settings, view -> + PermissionHelper.checkSystemAlertWindowPermission(getContext())) .show(); } @@ -53,22 +54,23 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } /** - * Update fast-forward/-rewind seek duration options according to language and inexact seek setting. + * Update fast-forward/-rewind seek duration options + * according to language and inexact seek setting. * Exoplayer can't seek 5 seconds in audio when using inexact seek. */ private void updateSeekOptions() { - //initializing R.array.seek_duration_description to display the translation of seconds + // initializing R.array.seek_duration_description to display the translation of seconds final Resources res = getResources(); final String[] durationsValues = res.getStringArray(R.array.seek_duration_value); final List displayedDurationValues = new LinkedList<>(); final List displayedDescriptionValues = new LinkedList<>(); int currentDurationValue; final boolean inexactSeek = getPreferenceManager().getSharedPreferences() - .getBoolean(res.getString(R.string.use_inexact_seek_key), false); + .getBoolean(res.getString(R.string.use_inexact_seek_key), false); for (String durationsValue : durationsValues) { currentDurationValue = - Integer.parseInt(durationsValue) / (int) DateUtils.SECOND_IN_MILLIS; + Integer.parseInt(durationsValue) / (int) DateUtils.SECOND_IN_MILLIS; if (inexactSeek && currentDurationValue % 10 == 5) { continue; } @@ -76,15 +78,17 @@ private void updateSeekOptions() { displayedDurationValues.add(durationsValue); try { displayedDescriptionValues.add(String.format( - res.getQuantityString(R.plurals.seconds, - currentDurationValue), - currentDurationValue)); + res.getQuantityString(R.plurals.seconds, + currentDurationValue), + currentDurationValue)); } catch (Resources.NotFoundException ignored) { - //if this happens, the translation is missing, and the english string will be displayed instead + // if this happens, the translation is missing, + // and the english string will be displayed instead } } - final ListPreference durations = (ListPreference) findPreference(getString(R.string.seek_duration_key)); + final ListPreference durations = (ListPreference) findPreference( + getString(R.string.seek_duration_key)); durations.setEntryValues(displayedDurationValues.toArray(new CharSequence[0])); durations.setEntries(displayedDescriptionValues.toArray(new CharSequence[0])); final int selectedDuration = Integer.parseInt(durations.getValue()); @@ -93,28 +97,30 @@ private void updateSeekOptions() { durations.setValue(Integer.toString(newDuration * (int) DateUtils.SECOND_IN_MILLIS)); Toast toast = Toast - .makeText(getContext(), - getString(R.string.new_seek_duration_toast, newDuration), - Toast.LENGTH_LONG); + .makeText(getContext(), + getString(R.string.new_seek_duration_toast, newDuration), + Toast.LENGTH_LONG); toast.show(); } } @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResource(R.xml.video_audio_settings); } @Override public void onResume() { super.onResume(); - getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener); + getPreferenceManager().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(listener); } @Override public void onPause() { super.onPause(); - getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener); + getPreferenceManager().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(listener); } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java index b93ec91d0..f03348890 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java @@ -3,23 +3,23 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatImageView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatImageView; + import org.schabi.newpipe.R; import org.schabi.newpipe.util.ThemeHelper; -public class AddTabDialog { +public final class AddTabDialog { private final AlertDialog dialog; - AddTabDialog(@NonNull final Context context, - @NonNull final ChooseTabListItem[] items, + AddTabDialog(@NonNull final Context context, @NonNull final ChooseTabListItem[] items, @NonNull final DialogInterface.OnClickListener actions) { dialog = new AlertDialog.Builder(context) @@ -32,29 +32,32 @@ public void show() { dialog.show(); } - public static final class ChooseTabListItem { + static final class ChooseTabListItem { final int tabId; final String itemName; - @DrawableRes final int itemIcon; + @DrawableRes + final int itemIcon; - ChooseTabListItem(Context context, Tab tab) { + ChooseTabListItem(final Context context, final Tab tab) { this(tab.getTabId(), tab.getTabName(context), tab.getTabIconRes(context)); } - ChooseTabListItem(int tabId, String itemName, @DrawableRes int itemIcon) { + ChooseTabListItem(final int tabId, final String itemName, + @DrawableRes final int itemIcon) { this.tabId = tabId; this.itemName = itemName; this.itemIcon = itemIcon; } } - private static class DialogListAdapter extends BaseAdapter { + private static final class DialogListAdapter extends BaseAdapter { private final LayoutInflater inflater; private final ChooseTabListItem[] items; - @DrawableRes private final int fallbackIcon; + @DrawableRes + private final int fallbackIcon; - private DialogListAdapter(Context context, ChooseTabListItem[] items) { + private DialogListAdapter(final Context context, final ChooseTabListItem[] items) { this.inflater = LayoutInflater.from(context); this.items = items; this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot); @@ -66,17 +69,18 @@ public int getCount() { } @Override - public ChooseTabListItem getItem(int position) { + public ChooseTabListItem getItem(final int position) { return items[position]; } @Override - public long getItemId(int position) { + public long getItemId(final int position) { return getItem(position).tabId; } @Override - public View getView(int position, View convertView, ViewGroup parent) { + public View getView(final int position, final View view, final ViewGroup parent) { + View convertView = view; if (convertView == null) { convertView = inflater.inflate(R.layout.list_choose_tabs_dialog, parent, false); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 6aba2783f..aafc05240 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -4,18 +4,6 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import androidx.fragment.app.Fragment; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.AppCompatImageView; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.ItemTouchHelper; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -26,6 +14,20 @@ import android.widget.ImageView; 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.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.report.ErrorActivity; @@ -42,17 +44,18 @@ import static org.schabi.newpipe.settings.tabs.Tab.typeFrom; public class ChooseTabsFragment extends Fragment { - + private static final int MENU_ITEM_RESTORE_ID = 123456; + private ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; private TabsManager tabsManager; + private List tabList = new ArrayList<>(); - public ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; /*////////////////////////////////////////////////////////////////////////// // Lifecycle //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); tabsManager = TabsManager.getManager(requireContext()); @@ -62,12 +65,14 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } @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_choose_tabs, container, false); } @Override - public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View rootView, + @Nullable final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); initButton(rootView); @@ -88,31 +93,31 @@ public void onResume() { updateTitle(); } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onPause() { super.onPause(); saveChanges(); } - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - - private final int MENU_ITEM_RESTORE_ID = 123456; - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); + final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, + R.string.restore_defaults); restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults); + final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), + R.attr.ic_restore_defaults); restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon)); } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == MENU_ITEM_RESTORE_ID) { restoreDefaults(); return true; @@ -133,7 +138,9 @@ private void updateTabList() { private void updateTitle() { if (getActivity() instanceof AppCompatActivity) { ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - if (actionBar != null) actionBar.setTitle(R.string.main_page_content); + if (actionBar != null) { + actionBar.setTitle(R.string.main_page_content); + } } } @@ -154,7 +161,7 @@ private void restoreDefaults() { .show(); } - private void initButton(View rootView) { + private void initButton(final View rootView) { final FloatingActionButton fab = rootView.findViewById(R.id.addTabsButton); fab.setOnClickListener(v -> { final ChooseTabListItem[] availableTabs = getAvailableTabs(requireContext()); @@ -179,37 +186,37 @@ private void addTab(final Tab tab) { selectedTabsAdapter.notifyDataSetChanged(); } - private void addTab(int tabId) { + private void addTab(final int tabId) { final Tab.Type type = typeFrom(tabId); if (type == null) { - ErrorActivity.reportError(requireContext(), new IllegalStateException("Tab id not found: " + tabId), null, null, - ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Choosing tabs on settings", 0)); + ErrorActivity.reportError(requireContext(), + new IllegalStateException("Tab id not found: " + tabId), null, null, + ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", + "Choosing tabs on settings", 0)); return; } switch (type) { - case KIOSK: { - SelectKioskFragment selectFragment = new SelectKioskFragment(); - selectFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) -> + case KIOSK: + SelectKioskFragment selectKioskFragment = new SelectKioskFragment(); + selectKioskFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) -> addTab(new Tab.KioskTab(serviceId, kioskId))); - selectFragment.show(requireFragmentManager(), "select_kiosk"); + selectKioskFragment.show(requireFragmentManager(), "select_kiosk"); return; - } - case CHANNEL: { - SelectChannelFragment selectFragment = new SelectChannelFragment(); - selectFragment.setOnSelectedLisener((serviceId, url, name) -> + case CHANNEL: + SelectChannelFragment selectChannelFragment = new SelectChannelFragment(); + selectChannelFragment.setOnSelectedLisener((serviceId, url, name) -> addTab(new Tab.ChannelTab(serviceId, url, name))); - selectFragment.show(requireFragmentManager(), "select_channel"); + selectChannelFragment.show(requireFragmentManager(), "select_channel"); return; - } default: addTab(type.getTab()); break; } } - public ChooseTabListItem[] getAvailableTabs(Context context) { + public ChooseTabListItem[] getAvailableTabs(final Context context) { final ArrayList returnList = new ArrayList<>(); for (Tab.Type type : Tab.Type.values()) { @@ -217,21 +224,25 @@ public ChooseTabListItem[] getAvailableTabs(Context context) { switch (type) { case BLANK: if (!tabList.contains(tab)) { - returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.blank_page_summary), + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.blank_page_summary), tab.getTabIconRes(context))); } break; case KIOSK: - returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.kiosk_page_summary), + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.kiosk_page_summary), ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot))); break; case CHANNEL: - returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.channel_page_summary), + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.channel_page_summary), tab.getTabIconRes(context))); break; case DEFAULT_KIOSK: if (!tabList.contains(tab)) { - returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.default_kiosk_page_summary), + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.default_kiosk_page_summary), ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot))); } break; @@ -250,29 +261,88 @@ public ChooseTabListItem[] getAvailableTabs(Context context) { // List Handling //////////////////////////////////////////////////////////////////////////*/ - private class SelectedTabsAdapter extends RecyclerView.Adapter { - private ItemTouchHelper itemTouchHelper; + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, + final int viewSize, + final int viewSizeOutOfBounds, + final int totalSize, + final long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int minimumAbsVelocity = Math.max(12, + Math.abs(standardSpeed)); + return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(final RecyclerView recyclerView, + final RecyclerView.ViewHolder source, + final RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() + || selectedTabsAdapter == null) { + return false; + } + + final int sourceIndex = source.getAdapterPosition(); + final int targetIndex = target.getAdapterPosition(); + selectedTabsAdapter.swapItems(sourceIndex, targetIndex); + return true; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return true; + } + + @Override + public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { + int position = viewHolder.getAdapterPosition(); + tabList.remove(position); + selectedTabsAdapter.notifyItemRemoved(position); + + if (tabList.isEmpty()) { + tabList.add(Tab.Type.BLANK.getTab()); + selectedTabsAdapter.notifyItemInserted(0); + } + } + }; + } + + private class SelectedTabsAdapter + extends RecyclerView.Adapter { private final LayoutInflater inflater; + private ItemTouchHelper itemTouchHelper; - SelectedTabsAdapter(Context context, ItemTouchHelper itemTouchHelper) { + SelectedTabsAdapter(final Context context, final ItemTouchHelper itemTouchHelper) { this.itemTouchHelper = itemTouchHelper; this.inflater = LayoutInflater.from(context); } - public void swapItems(int fromPosition, int toPosition) { + public void swapItems(final int fromPosition, final int toPosition) { Collections.swap(tabList, fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition); } @NonNull @Override - public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder( + @NonNull final ViewGroup parent, final int viewType) { View view = inflater.inflate(R.layout.list_choose_tabs, parent, false); return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view); } @Override - public void onBindViewHolder(@NonNull ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) { + public void onBindViewHolder( + @NonNull final ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, + final int position) { holder.bind(position, holder); } @@ -286,7 +356,7 @@ class TabViewHolder extends RecyclerView.ViewHolder { private TextView tabNameView; private ImageView handle; - TabViewHolder(View itemView) { + TabViewHolder(final View itemView) { super(itemView); tabNameView = itemView.findViewById(R.id.tabName); @@ -295,7 +365,7 @@ class TabViewHolder extends RecyclerView.ViewHolder { } @SuppressLint("ClickableViewAccessibility") - void bind(int position, TabViewHolder holder) { + void bind(final int position, final TabViewHolder holder) { handle.setOnTouchListener(getOnTouchListener(holder)); final Tab tab = tabList.get(position); @@ -314,10 +384,12 @@ void bind(int position, TabViewHolder holder) { tabName = getString(R.string.default_kiosk_page_summary); break; case KIOSK: - tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab).getKioskServiceId()) + "/" + tab.getTabName(requireContext()); + tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab) + .getKioskServiceId()) + "/" + tab.getTabName(requireContext()); break; case CHANNEL: - tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tab.getTabName(requireContext()); + tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab) + .getChannelServiceId()) + "/" + tab.getTabName(requireContext()); break; default: tabName = tab.getTabName(requireContext()); @@ -342,56 +414,4 @@ private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder it } } } - - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, - ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, - int viewSizeOutOfBounds, int totalSize, - long msSinceStartScroll) { - final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, - viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int minimumAbsVelocity = Math.max(12, - Math.abs(standardSpeed)); - return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, - RecyclerView.ViewHolder target) { - if (source.getItemViewType() != target.getItemViewType() || - selectedTabsAdapter == null) { - return false; - } - - final int sourceIndex = source.getAdapterPosition(); - final int targetIndex = target.getAdapterPosition(); - selectedTabsAdapter.swapItems(sourceIndex, targetIndex); - return true; - } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - - @Override - public boolean isItemViewSwipeEnabled() { - return true; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - int position = viewHolder.getAdapterPosition(); - tabList.remove(position); - selectedTabsAdapter.notifyItemRemoved(position); - - if (tabList.isEmpty()) { - tabList.add(Tab.Type.BLANK.getTab()); - selectedTabsAdapter.notifyItemInserted(0); - } - } - }; - } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index cc40298b9..ef4e35aef 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -31,59 +31,16 @@ import java.util.Objects; public abstract class Tab { - Tab() { - } - - Tab(@NonNull JsonObject jsonObject) { - readDataFromJson(jsonObject); - } - - public abstract int getTabId(); - public abstract String getTabName(Context context); - @DrawableRes public abstract int getTabIconRes(Context context); - - /** - * Return a instance of the fragment that this tab represent. - */ - public abstract Fragment getFragment(Context context) throws ExtractionException; - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - - return obj instanceof Tab && obj.getClass().equals(this.getClass()) - && ((Tab) obj).getTabId() == this.getTabId(); - } - - /*////////////////////////////////////////////////////////////////////////// - // JSON Handling - //////////////////////////////////////////////////////////////////////////*/ - private static final String JSON_TAB_ID_KEY = "tab_id"; - public void writeJsonOn(JsonSink jsonSink) { - jsonSink.object(); - - jsonSink.value(JSON_TAB_ID_KEY, getTabId()); - writeDataToJson(jsonSink); - - jsonSink.end(); - } - - protected void writeDataToJson(JsonSink writerSink) { - // No-op - } + Tab() { } - protected void readDataFromJson(JsonObject jsonObject) { - // No-op + Tab(@NonNull final JsonObject jsonObject) { + readDataFromJson(jsonObject); } - /*////////////////////////////////////////////////////////////////////////// - // Tab Handling - //////////////////////////////////////////////////////////////////////////*/ - @Nullable - public static Tab from(@NonNull JsonObject jsonObject) { + public static Tab from(@NonNull final JsonObject jsonObject) { final int tabId = jsonObject.getInt(Tab.JSON_TAB_ID_KEY, -1); if (tabId == -1) { @@ -99,7 +56,7 @@ public static Tab from(final int tabId) { } @Nullable - public static Type typeFrom(int tabId) { + public static Type typeFrom(final int tabId) { for (Type available : Type.values()) { if (available.getTabId() == tabId) { return available; @@ -109,7 +66,7 @@ public static Type typeFrom(int tabId) { } @Nullable - private static Tab from(final int tabId, @Nullable JsonObject jsonObject) { + private static Tab from(final int tabId, @Nullable final JsonObject jsonObject) { final Type type = typeFrom(tabId); if (type == null) { @@ -128,6 +85,56 @@ private static Tab from(final int tabId, @Nullable JsonObject jsonObject) { return type.getTab(); } + /*////////////////////////////////////////////////////////////////////////// + // JSON Handling + //////////////////////////////////////////////////////////////////////////*/ + + public abstract int getTabId(); + + public abstract String getTabName(Context context); + + @DrawableRes + public abstract int getTabIconRes(Context context); + + /** + * Return a instance of the fragment that this tab represent. + * + * @param context Android app context + * @return the fragment this tab represents + */ + public abstract Fragment getFragment(Context context) throws ExtractionException; + + /*////////////////////////////////////////////////////////////////////////// + // Tab Handling + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + + return obj instanceof Tab && obj.getClass().equals(this.getClass()) + && ((Tab) obj).getTabId() == this.getTabId(); + } + + public void writeJsonOn(final JsonSink jsonSink) { + jsonSink.object(); + + jsonSink.value(JSON_TAB_ID_KEY, getTabId()); + writeDataToJson(jsonSink); + + jsonSink.end(); + } + + protected void writeDataToJson(final JsonSink writerSink) { + // No-op + } + + protected void readDataFromJson(final JsonObject jsonObject) { + // No-op + } + /*////////////////////////////////////////////////////////////////////////// // Implementations //////////////////////////////////////////////////////////////////////////*/ @@ -144,7 +151,7 @@ public enum Type { private Tab tab; - Type(Tab tab) { + Type(final Tab tab) { this.tab = tab; } @@ -166,18 +173,18 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return "NewPipe"; //context.getString(R.string.blank_page_summary); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_blank_page); } @Override - public BlankFragment getFragment(Context context) { + public BlankFragment getFragment(final Context context) { return new BlankFragment(); } } @@ -191,18 +198,18 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return context.getString(R.string.tab_subscriptions); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel); } @Override - public SubscriptionFragment getFragment(Context context) { + public SubscriptionFragment getFragment(final Context context) { return new SubscriptionFragment(); } @@ -217,18 +224,18 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return context.getString(R.string.fragment_feed_title); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss); } @Override - public FeedFragment getFragment(Context context) { + public FeedFragment getFragment(final Context context) { return new FeedFragment(); } } @@ -242,18 +249,18 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return context.getString(R.string.tab_bookmarks); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark); } @Override - public BookmarkFragment getFragment(Context context) { + public BookmarkFragment getFragment(final Context context) { return new BookmarkFragment(); } } @@ -267,41 +274,39 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return context.getString(R.string.title_activity_history); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history); } @Override - public StatisticsPlaylistFragment getFragment(Context context) { + public StatisticsPlaylistFragment getFragment(final Context context) { return new StatisticsPlaylistFragment(); } } public static class KioskTab extends Tab { public static final int ID = 5; - - private int kioskServiceId; - private String kioskId; - private static final String JSON_KIOSK_SERVICE_ID_KEY = "service_id"; private static final String JSON_KIOSK_ID_KEY = "kiosk_id"; + private int kioskServiceId; + private String kioskId; private KioskTab() { this(-1, ""); } - public KioskTab(int kioskServiceId, String kioskId) { + public KioskTab(final int kioskServiceId, final String kioskId) { this.kioskServiceId = kioskServiceId; this.kioskId = kioskId; } - public KioskTab(JsonObject jsonObject) { + public KioskTab(final JsonObject jsonObject) { super(jsonObject); } @@ -311,13 +316,13 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return KioskTranslator.getTranslatedKioskName(kioskId, context); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context); if (kioskIcon <= 0) { @@ -328,26 +333,25 @@ public int getTabIconRes(Context context) { } @Override - public KioskFragment getFragment(Context context) throws ExtractionException { + public KioskFragment getFragment(final Context context) throws ExtractionException { return KioskFragment.getInstance(kioskServiceId, kioskId); } @Override - protected void writeDataToJson(JsonSink writerSink) { + protected void writeDataToJson(final JsonSink writerSink) { writerSink.value(JSON_KIOSK_SERVICE_ID_KEY, kioskServiceId) .value(JSON_KIOSK_ID_KEY, kioskId); } @Override - protected void readDataFromJson(JsonObject jsonObject) { + protected void readDataFromJson(final JsonObject jsonObject) { kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1); kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, ""); } @Override - public boolean equals(Object obj) { - return super.equals(obj) && - kioskServiceId == ((KioskTab) obj).kioskServiceId + public boolean equals(final Object obj) { + return super.equals(obj) && kioskServiceId == ((KioskTab) obj).kioskServiceId && Objects.equals(kioskId, ((KioskTab) obj).kioskId); } @@ -362,26 +366,25 @@ public String getKioskId() { public static class ChannelTab extends Tab { public static final int ID = 6; - - private int channelServiceId; - private String channelUrl; - private String channelName; - private static final String JSON_CHANNEL_SERVICE_ID_KEY = "channel_service_id"; private static final String JSON_CHANNEL_URL_KEY = "channel_url"; private static final String JSON_CHANNEL_NAME_KEY = "channel_name"; + private int channelServiceId; + private String channelUrl; + private String channelName; private ChannelTab() { this(-1, "", ""); } - public ChannelTab(int channelServiceId, String channelUrl, String channelName) { + public ChannelTab(final int channelServiceId, final String channelUrl, + final String channelName) { this.channelServiceId = channelServiceId; this.channelUrl = channelUrl; this.channelName = channelName; } - public ChannelTab(JsonObject jsonObject) { + public ChannelTab(final JsonObject jsonObject) { super(jsonObject); } @@ -391,39 +394,38 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return channelName; } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel); } @Override - public ChannelFragment getFragment(Context context) { + public ChannelFragment getFragment(final Context context) { return ChannelFragment.getInstance(channelServiceId, channelUrl, channelName); } @Override - protected void writeDataToJson(JsonSink writerSink) { + protected void writeDataToJson(final JsonSink writerSink) { writerSink.value(JSON_CHANNEL_SERVICE_ID_KEY, channelServiceId) .value(JSON_CHANNEL_URL_KEY, channelUrl) .value(JSON_CHANNEL_NAME_KEY, channelName); } @Override - protected void readDataFromJson(JsonObject jsonObject) { + protected void readDataFromJson(final JsonObject jsonObject) { channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1); channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, ""); channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, ""); } @Override - public boolean equals(Object obj) { - return super.equals(obj) && - channelServiceId == ((ChannelTab) obj).channelServiceId + public boolean equals(final Object obj) { + return super.equals(obj) && channelServiceId == ((ChannelTab) obj).channelServiceId && Objects.equals(channelUrl, ((ChannelTab) obj).channelUrl) && Objects.equals(channelName, ((ChannelTab) obj).channelName); } @@ -450,22 +452,22 @@ public int getTabId() { } @Override - public String getTabName(Context context) { + public String getTabName(final Context context) { return KioskTranslator.getTranslatedKioskName(getDefaultKioskId(context), context); } @DrawableRes @Override - public int getTabIconRes(Context context) { + public int getTabIconRes(final Context context) { return KioskTranslator.getKioskIcons(getDefaultKioskId(context), context); } @Override - public DefaultKioskFragment getFragment(Context context) throws ExtractionException { + public DefaultKioskFragment getFragment(final Context context) { return new DefaultKioskFragment(); } - private String getDefaultKioskId(Context context) { + private String getDefaultKioskId(final Context context) { final int kioskServiceId = ServiceHelper.getSelectedServiceId(context); String kioskId = ""; @@ -474,7 +476,8 @@ private String getDefaultKioskId(Context context) { kioskId = service.getKioskList().getDefaultKioskId(); } catch (ExtractionException e) { ErrorActivity.reportError(context, e, null, null, - ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none", "Loading default kiosk from selected service", 0)); + ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none", + "Loading default kiosk from selected service", 0)); } return kioskId; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 9f54d59f6..f1639fe53 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings.tabs; +import androidx.annotation.Nullable; + import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; @@ -12,33 +14,19 @@ import java.util.Collections; import java.util.List; -import androidx.annotation.Nullable; - /** * Class to get a JSON representation of a list of tabs, and the other way around. */ -public class TabsJsonHelper { +public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; - private static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList(Arrays.asList( - Tab.Type.DEFAULT_KIOSK.getTab(), - Tab.Type.SUBSCRIPTIONS.getTab(), - Tab.Type.BOOKMARKS.getTab() - )); - - public static class InvalidJsonException extends Exception { - private InvalidJsonException() { - super(); - } - - private InvalidJsonException(String message) { - super(message); - } + private static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList( + Arrays.asList( + Tab.Type.DEFAULT_KIOSK.getTab(), + Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.BOOKMARKS.getTab())); - private InvalidJsonException(Throwable cause) { - super(cause); - } - } + private TabsJsonHelper() { } /** * Try to reads the passed JSON and returns the list of tabs if no error were encountered. @@ -52,7 +40,8 @@ private InvalidJsonException(Throwable cause) { * @return a list of {@link Tab tabs}. * @throws InvalidJsonException if the JSON string is not valid */ - public static List getTabsFromJson(@Nullable String tabsJson) throws InvalidJsonException { + public static List getTabsFromJson(@Nullable final String tabsJson) + throws InvalidJsonException { if (tabsJson == null || tabsJson.isEmpty()) { return getDefaultTabs(); } @@ -65,11 +54,14 @@ public static List getTabsFromJson(@Nullable String tabsJson) throws Invali final JsonArray tabsArray = outerJsonObject.getArray(JSON_TABS_ARRAY_KEY); if (tabsArray == null) { - throw new InvalidJsonException("JSON doesn't contain \"" + JSON_TABS_ARRAY_KEY + "\" array"); + throw new InvalidJsonException("JSON doesn't contain \"" + JSON_TABS_ARRAY_KEY + + "\" array"); } for (Object o : tabsArray) { - if (!(o instanceof JsonObject)) continue; + if (!(o instanceof JsonObject)) { + continue; + } final Tab tab = Tab.from((JsonObject) o); @@ -94,13 +86,15 @@ public static List getTabsFromJson(@Nullable String tabsJson) throws Invali * @param tabList a list of {@link Tab tabs}. * @return a JSON string representing the list of tabs */ - public static String getJsonToSave(@Nullable List tabList) { + public static String getJsonToSave(@Nullable final List tabList) { final JsonStringWriter jsonWriter = JsonWriter.string(); jsonWriter.object(); jsonWriter.array(JSON_TABS_ARRAY_KEY); - if (tabList != null) for (Tab tab : tabList) { - tab.writeJsonOn(jsonWriter); + if (tabList != null) { + for (Tab tab : tabList) { + tab.writeJsonOn(jsonWriter); + } } jsonWriter.end(); @@ -108,7 +102,21 @@ public static String getJsonToSave(@Nullable List tabList) { return jsonWriter.done(); } - public static List getDefaultTabs(){ + public static List getDefaultTabs() { return FALLBACK_INITIAL_TABS_LIST; } -} \ No newline at end of file + + public static final class InvalidJsonException extends Exception { + private InvalidJsonException() { + super(); + } + + private InvalidJsonException(final String message) { + super(message); + } + + private InvalidJsonException(final Throwable cause) { + super(cause); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java index 1c99775e5..4c8e0c06b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java @@ -9,21 +9,23 @@ import java.util.List; -public class TabsManager { +public final class TabsManager { private final SharedPreferences sharedPreferences; private final String savedTabsKey; private final Context context; + private SavedTabsChangeListener savedTabsChangeListener; + private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; - public static TabsManager getManager(Context context) { - return new TabsManager(context); - } - - private TabsManager(Context context) { + private TabsManager(final Context context) { this.context = context; this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); this.savedTabsKey = context.getString(R.string.saved_tabs_key); } + public static TabsManager getManager(final Context context) { + return new TabsManager(context); + } + public List getTabs() { final String savedJson = sharedPreferences.getString(savedTabsKey, null); try { @@ -34,11 +36,15 @@ public List getTabs() { } } - public void saveTabs(List tabList) { + public void saveTabs(final List tabList) { final String jsonToSave = TabsJsonHelper.getJsonToSave(tabList); sharedPreferences.edit().putString(savedTabsKey, jsonToSave).apply(); } + /*////////////////////////////////////////////////////////////////////////// + // Listener + //////////////////////////////////////////////////////////////////////////*/ + public void resetTabs() { sharedPreferences.edit().remove(savedTabsKey).apply(); } @@ -47,18 +53,7 @@ public List getDefaultTabs() { return TabsJsonHelper.getDefaultTabs(); } - /*////////////////////////////////////////////////////////////////////////// - // Listener - //////////////////////////////////////////////////////////////////////////*/ - - public interface SavedTabsChangeListener { - void onTabsChanged(); - } - - private SavedTabsChangeListener savedTabsChangeListener; - private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; - - public void setSavedTabsListener(SavedTabsChangeListener listener) { + public void setSavedTabsListener(final SavedTabsChangeListener listener) { if (preferenceChangeListener != null) { sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener); } @@ -76,13 +71,19 @@ public void unsetSavedTabsListener() { } private SharedPreferences.OnSharedPreferenceChangeListener getPreferenceChangeListener() { - return (sharedPreferences, key) -> { + return (sp, key) -> { if (key.equals(savedTabsKey)) { - if (savedTabsChangeListener != null) savedTabsChangeListener.onTabsChanged(); + if (savedTabsChangeListener != null) { + savedTabsChangeListener.onTabsChanged(); + } } }; } + public interface SavedTabsChangeListener { + void onTabsChanged(); + } + } diff --git a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java index e47e14483..4fa14ed01 100644 --- a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java @@ -24,48 +24,51 @@ import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.content.res.ColorStateList; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.core.view.ViewCompat; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; -import android.util.Log; -import android.view.View; -import android.widget.TextView; import org.schabi.newpipe.MainActivity; -public class AnimationUtils { +public final class AnimationUtils { private static final String TAG = "AnimationUtils"; private static final boolean DEBUG = MainActivity.DEBUG; - public enum Type { - ALPHA, - SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, - SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA - } + private AnimationUtils() { } - public static void animateView(View view, boolean enterOrExit, long duration) { + public static void animateView(final View view, final boolean enterOrExit, + final long duration) { animateView(view, Type.ALPHA, enterOrExit, duration, 0, null); } - public static void animateView(View view, boolean enterOrExit, long duration, long delay) { + public static void animateView(final View view, final boolean enterOrExit, + final long duration, final long delay) { animateView(view, Type.ALPHA, enterOrExit, duration, delay, null); } - public static void animateView(View view, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) { + public static void animateView(final View view, final boolean enterOrExit, final long duration, + final long delay, final Runnable execOnEnd) { animateView(view, Type.ALPHA, enterOrExit, duration, delay, execOnEnd); } - public static void animateView(View view, Type animationType, boolean enterOrExit, long duration) { + public static void animateView(final View view, final Type animationType, + final boolean enterOrExit, final long duration) { animateView(view, animationType, enterOrExit, duration, 0, null); } - public static void animateView(View view, Type animationType, boolean enterOrExit, long duration, long delay) { + public static void animateView(final View view, final Type animationType, + final boolean enterOrExit, final long duration, + final long delay) { animateView(view, animationType, enterOrExit, duration, delay, null); } /** - * Animate the view + * Animate the view. * * @param view view that will be animated * @param animationType {@link Type} of the animation @@ -74,7 +77,9 @@ public static void animateView(View view, Type animationType, boolean enterOrExi * @param delay how long the animation will wait to start, in milliseconds * @param execOnEnd runnable that will be executed when the animation ends */ - public static void animateView(final View view, Type animationType, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) { + public static void animateView(final View view, final Type animationType, + final boolean enterOrExit, final long duration, + final long delay, final Runnable execOnEnd) { if (DEBUG) { String id; try { @@ -83,24 +88,33 @@ public static void animateView(final View view, Type animationType, boolean ente id = view.getId() + ""; } - String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", - enterOrExit, view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd); + String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit, + view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd); Log.d(TAG, "animateView()" + msg); } if (view.getVisibility() == View.VISIBLE && enterOrExit) { - if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]"); + if (DEBUG) { + Log.d(TAG, "animateView() view was already visible > view = [" + view + "]"); + } view.animate().setListener(null).cancel(); view.setVisibility(View.VISIBLE); view.setAlpha(1f); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } return; - } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) { - if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]"); + } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) + && !enterOrExit) { + if (DEBUG) { + Log.d(TAG, "animateView() view was already gone > view = [" + view + "]"); + } view.animate().setListener(null).cancel(); view.setVisibility(View.GONE); view.setAlpha(0f); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } return; } @@ -126,33 +140,44 @@ public static void animateView(final View view, Type animationType, boolean ente } } - /** - * Animate the background color of a view + * Animate the background color of a view. + * + * @param view the view to animate + * @param duration the duration of the animation + * @param colorStart the background color to start with + * @param colorEnd the background color to end with */ - public static void animateBackgroundColor(final View view, long duration, @ColorInt final int colorStart, @ColorInt final int colorEnd) { + public static void animateBackgroundColor(final View view, final long duration, + @ColorInt final int colorStart, + @ColorInt final int colorEnd) { if (DEBUG) { - Log.d(TAG, "animateBackgroundColor() called with: view = [" + view + "], duration = [" + duration + "], colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]"); + Log.d(TAG, "animateBackgroundColor() called with: " + + "view = [" + view + "], duration = [" + duration + "], " + + "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]"); } - final int[][] EMPTY = new int[][]{new int[0]}; - ValueAnimator viewPropertyAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorStart, colorEnd); + final int[][] empty = new int[][]{new int[0]}; + ValueAnimator viewPropertyAnimator = ValueAnimator + .ofObject(new ArgbEvaluator(), colorStart, colorEnd); viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator()); viewPropertyAnimator.setDuration(duration); viewPropertyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override - public void onAnimationUpdate(ValueAnimator animation) { - ViewCompat.setBackgroundTintList(view, new ColorStateList(EMPTY, new int[]{(int) animation.getAnimatedValue()})); + public void onAnimationUpdate(final ValueAnimator animation) { + ViewCompat.setBackgroundTintList(view, + new ColorStateList(empty, new int[]{(int) animation.getAnimatedValue()})); } }); viewPropertyAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - ViewCompat.setBackgroundTintList(view, new ColorStateList(EMPTY, new int[]{colorEnd})); + public void onAnimationEnd(final Animator animation) { + ViewCompat.setBackgroundTintList(view, + new ColorStateList(empty, new int[]{colorEnd})); } @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(final Animator animation) { onAnimationEnd(animation); } }); @@ -160,40 +185,52 @@ public void onAnimationCancel(Animator animation) { } /** - * Animate the text color of any view that extends {@link TextView} (Buttons, EditText...) + * Animate the text color of any view that extends {@link TextView} (Buttons, EditText...). + * + * @param view the text view to animate + * @param duration the duration of the animation + * @param colorStart the text color to start with + * @param colorEnd the text color to end with */ - public static void animateTextColor(final TextView view, long duration, @ColorInt final int colorStart, @ColorInt final int colorEnd) { + public static void animateTextColor(final TextView view, final long duration, + @ColorInt final int colorStart, + @ColorInt final int colorEnd) { if (DEBUG) { - Log.d(TAG, "animateTextColor() called with: view = [" + view + "], duration = [" + duration + "], colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]"); + Log.d(TAG, "animateTextColor() called with: " + + "view = [" + view + "], duration = [" + duration + "], " + + "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]"); } - ValueAnimator viewPropertyAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorStart, colorEnd); + ValueAnimator viewPropertyAnimator = ValueAnimator + .ofObject(new ArgbEvaluator(), colorStart, colorEnd); viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator()); viewPropertyAnimator.setDuration(duration); viewPropertyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override - public void onAnimationUpdate(ValueAnimator animation) { + public void onAnimationUpdate(final ValueAnimator animation) { view.setTextColor((int) animation.getAnimatedValue()); } }); viewPropertyAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setTextColor(colorEnd); } @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(final Animator animation) { view.setTextColor(colorEnd); } }); viewPropertyAnimator.start(); } - public static ValueAnimator animateHeight(final View view, long duration, int targetHeight) { + public static ValueAnimator animateHeight(final View view, final long duration, + final int targetHeight) { final int height = view.getHeight(); if (DEBUG) { - Log.d(TAG, "animateHeight: duration = [" + duration + "], from " + height + " to → " + targetHeight + " in: " + view); + Log.d(TAG, "animateHeight: duration = [" + duration + "], " + + "from " + height + " to → " + targetHeight + " in: " + view); } ValueAnimator animator = ValueAnimator.ofFloat(height, targetHeight); @@ -206,13 +243,13 @@ public static ValueAnimator animateHeight(final View view, long duration, int ta }); animator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.getLayoutParams().height = targetHeight; view.requestLayout(); } @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(final Animator animation) { view.getLayoutParams().height = targetHeight; view.requestLayout(); } @@ -222,155 +259,211 @@ public void onAnimationCancel(Animator animation) { return animator; } - public static void animateRotation(final View view, long duration, int targetRotation) { + public static void animateRotation(final View view, final long duration, + final int targetRotation) { if (DEBUG) { - Log.d(TAG, "animateRotation: duration = [" + duration + "], from " + view.getRotation() + " to → " + targetRotation + " in: " + view); + Log.d(TAG, "animateRotation: duration = [" + duration + "], " + + "from " + view.getRotation() + " to → " + targetRotation + " in: " + view); } view.animate().setListener(null).cancel(); - view.animate().rotation(targetRotation).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()) + view.animate() + .rotation(targetRotation).setDuration(duration) + .setInterpolator(new FastOutSlowInInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationCancel(Animator animation) { + public void onAnimationCancel(final Animator animation) { view.setRotation(targetRotation); } @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setRotation(targetRotation); } }).start(); } - /*////////////////////////////////////////////////////////////////////////// - // Internals - //////////////////////////////////////////////////////////////////////////*/ - - private static void animateAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + private static void animateAlpha(final View view, final boolean enterOrExit, + final long duration, final long delay, + final Runnable execOnEnd) { if (enterOrExit) { view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); + public void onAnimationEnd(final Animator animation) { + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } else { view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } } - private static void animateScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + /*////////////////////////////////////////////////////////////////////////// + // Internals + //////////////////////////////////////////////////////////////////////////*/ + + private static void animateScaleAndAlpha(final View view, final boolean enterOrExit, + final long duration, final long delay, + final Runnable execOnEnd) { if (enterOrExit) { view.setScaleX(.8f); view.setScaleY(.8f); - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).scaleX(1f).scaleY(1f) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(1f).scaleX(1f).scaleY(1f) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); + public void onAnimationEnd(final Animator animation) { + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } else { view.setScaleX(1f); view.setScaleY(1f); - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).scaleX(.8f).scaleY(.8f) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(0f).scaleX(.8f).scaleY(.8f) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } } - private static void animateLightScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + private static void animateLightScaleAndAlpha(final View view, final boolean enterOrExit, + final long duration, final long delay, + final Runnable execOnEnd) { if (enterOrExit) { view.setAlpha(.5f); view.setScaleX(.95f); view.setScaleY(.95f); - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).scaleX(1f).scaleY(1f) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(1f).scaleX(1f).scaleY(1f) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); + public void onAnimationEnd(final Animator animation) { + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } else { view.setAlpha(1f); view.setScaleX(1f); view.setScaleY(1f); - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).scaleX(.95f).scaleY(.95f) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(0f).scaleX(.95f).scaleY(.95f) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } } - private static void animateSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + private static void animateSlideAndAlpha(final View view, final boolean enterOrExit, + final long duration, final long delay, + final Runnable execOnEnd) { if (enterOrExit) { view.setTranslationY(-view.getHeight()); view.setAlpha(0f); - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); + public void onAnimationEnd(final Animator animation) { + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } else { - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight()) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .alpha(0f).translationY(-view.getHeight()) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } } - private static void animateLightSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { + private static void animateLightSlideAndAlpha(final View view, final boolean enterOrExit, + final long duration, final long delay, + final Runnable execOnEnd) { if (enterOrExit) { view.setTranslationY(-view.getHeight() / 2); view.setAlpha(0f); - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate() + .setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - if (execOnEnd != null) execOnEnd.run(); + public void onAnimationEnd(final Animator animation) { + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } else { - view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight() / 2) - .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { + view.animate().setInterpolator(new FastOutSlowInInterpolator()) + .alpha(0f).translationY(-view.getHeight() / 2) + .setDuration(duration).setStartDelay(delay) + .setListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationEnd(final Animator animation) { view.setVisibility(View.GONE); - if (execOnEnd != null) execOnEnd.run(); + if (execOnEnd != null) { + execOnEnd.run(); + } } }).start(); } } - public static void slideUp(final View view, - long duration, - long delay, - @FloatRange(from = 0.0f, to = 1.0f) float translationPercent) { - int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels * - (translationPercent)); + public static void slideUp(final View view, final long duration, final long delay, + @FloatRange(from = 0.0f, to = 1.0f) + final float translationPercent) { + int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels + * (translationPercent)); view.animate().setListener(null).cancel(); view.setAlpha(0f); @@ -384,4 +477,10 @@ public static void slideUp(final View view, .setInterpolator(new FastOutSlowInInterpolator()) .start(); } + + public enum Type { + ALPHA, + SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, + SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java b/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java index 7ad71eb5c..5b1c46372 100644 --- a/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java @@ -4,10 +4,12 @@ import androidx.annotation.Nullable; -public class BitmapUtils { +public final class BitmapUtils { + private BitmapUtils() { } @Nullable - public static Bitmap centerCrop(Bitmap inputBitmap, int newWidth, int newHeight) { + public static Bitmap centerCrop(final Bitmap inputBitmap, final int newWidth, + final int newHeight) { if (inputBitmap == null || inputBitmap.isRecycled()) { return null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java index ac79fee23..770592537 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -28,14 +28,13 @@ import io.reactivex.schedulers.Schedulers; public class CommentTextOnTouchListener implements View.OnTouchListener { - public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); - private static final Pattern timestampPattern = Pattern.compile("(.*)#timestamp=(\\d+)"); + private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)"); @Override - public boolean onTouch(View v, MotionEvent event) { - if(!(v instanceof TextView)){ + public boolean onTouch(final View v, final MotionEvent event) { + if (!(v instanceof TextView)) { return false; } TextView widget = (TextView) v; @@ -66,10 +65,12 @@ public boolean onTouch(View v, MotionEvent event) { if (link.length != 0) { if (action == MotionEvent.ACTION_UP) { boolean handled = false; - if(link[0] instanceof URLSpan){ + if (link[0] instanceof URLSpan) { handled = handleUrl(v.getContext(), (URLSpan) link[0]); } - if(!handled) link[0].onClick(widget); + if (!handled) { + link[0].onClick(widget); + } } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), @@ -78,17 +79,15 @@ public boolean onTouch(View v, MotionEvent event) { return true; } } - } - return false; } - private boolean handleUrl(Context context, URLSpan urlSpan) { + private boolean handleUrl(final Context context, final URLSpan urlSpan) { String url = urlSpan.getURL(); int seconds = -1; - Matcher matcher = timestampPattern.matcher(url); - if(matcher.matches()){ + Matcher matcher = TIMESTAMP_PATTERN.matcher(url); + if (matcher.matches()) { url = matcher.group(1); seconds = Integer.parseInt(matcher.group(2)); } @@ -100,18 +99,19 @@ private boolean handleUrl(Context context, URLSpan urlSpan) { } catch (ExtractionException e) { return false; } - if(linkType == StreamingService.LinkType.NONE){ + if (linkType == StreamingService.LinkType.NONE) { return false; } - if(linkType == StreamingService.LinkType.STREAM && seconds != -1){ + if (linkType == StreamingService.LinkType.STREAM && seconds != -1) { return playOnPopup(context, url, service, seconds); - }else{ + } else { NavigationHelper.openRouterActivity(context, url); return true; } } - private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) { + private boolean playOnPopup(final Context context, final String url, + final StreamingService service, final int seconds) { LinkHandlerFactory factory = service.getStreamLHFactory(); String cleanUrl = null; try { @@ -123,7 +123,7 @@ private boolean playOnPopup(Context context, String url, StreamingService servic single.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { - PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds*1000); + PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds * 1000); NavigationHelper.playOnPopupPlayer(context, playQueue, false); }); return true; diff --git a/app/src/main/java/org/schabi/newpipe/util/Constants.java b/app/src/main/java/org/schabi/newpipe/util/Constants.java index b01b6df6a..e71dd16f9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Constants.java +++ b/app/src/main/java/org/schabi/newpipe/util/Constants.java @@ -1,6 +1,6 @@ package org.schabi.newpipe.util; -public class Constants { +public final class Constants { public static final String KEY_SERVICE_ID = "key_service_id"; public static final String KEY_URL = "key_url"; public static final String KEY_TITLE = "key_title"; @@ -12,4 +12,6 @@ public class Constants { public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change"; public static final int NO_SERVICE_ID = -1; + + private Constants() { } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index cf4477223..9c6ab1898 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -61,28 +61,27 @@ public final class ExtractorHelper { private static final String TAG = ExtractorHelper.class.getSimpleName(); - private static final InfoCache cache = InfoCache.getInstance(); + private static final InfoCache CACHE = InfoCache.getInstance(); private ExtractorHelper() { //no instance } - private static void checkServiceId(int serviceId) { + private static void checkServiceId(final int serviceId) { if (serviceId == Constants.NO_SERVICE_ID) { throw new IllegalArgumentException("serviceId is NO_SERVICE_ID"); } } - public static Single searchFor(final int serviceId, - final String searchString, + public static Single searchFor(final int serviceId, final String searchString, final List contentFilter, final String sortFilter) { checkServiceId(serviceId); return Single.fromCallable(() -> - SearchInfo.getInfo(NewPipe.getService(serviceId), - NewPipe.getService(serviceId) - .getSearchQHFactory() - .fromQuery(searchString, contentFilter, sortFilter))); + SearchInfo.getInfo(NewPipe.getService(serviceId), + NewPipe.getService(serviceId) + .getSearchQHFactory() + .fromQuery(searchString, contentFilter, sortFilter))); } public static Single getMoreSearchItems(final int serviceId, @@ -94,14 +93,13 @@ public static Single getMoreSearchItems(final int serviceId, return Single.fromCallable(() -> SearchInfo.getMoreItems(NewPipe.getService(serviceId), NewPipe.getService(serviceId) - .getSearchQHFactory() - .fromQuery(searchString, contentFilter, sortFilter), + .getSearchQHFactory() + .fromQuery(searchString, contentFilter, sortFilter), pageUrl)); } - public static Single> suggestionsFor(final int serviceId, - final String query) { + public static Single> suggestionsFor(final int serviceId, final String query) { checkServiceId(serviceId); return Single.fromCallable(() -> { SuggestionExtractor extractor = NewPipe.getService(serviceId) @@ -112,32 +110,30 @@ public static Single> suggestionsFor(final int serviceId, }); } - public static Single getStreamInfo(final int serviceId, - final String url, - boolean forceLoad) { + public static Single getStreamInfo(final int serviceId, final String url, + final boolean forceLoad) { checkServiceId(serviceId); - return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, Single.fromCallable(() -> - StreamInfo.getInfo(NewPipe.getService(serviceId), url))); + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, + Single.fromCallable(() -> StreamInfo.getInfo(NewPipe.getService(serviceId), url))); } - public static Single getChannelInfo(final int serviceId, - final String url, - boolean forceLoad) { + public static Single getChannelInfo(final int serviceId, final String url, + final boolean forceLoad) { checkServiceId(serviceId); - return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL, Single.fromCallable(() -> - ChannelInfo.getInfo(NewPipe.getService(serviceId), url))); + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL, + Single.fromCallable(() -> + ChannelInfo.getInfo(NewPipe.getService(serviceId), url))); } - public static Single getMoreChannelItems(final int serviceId, - final String url, + public static Single getMoreChannelItems(final int serviceId, final String url, final String nextStreamsUrl) { checkServiceId(serviceId); return Single.fromCallable(() -> ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); } - public static Single> getFeedInfoFallbackToChannelInfo(final int serviceId, - final String url) { + public static Single> getFeedInfoFallbackToChannelInfo( + final int serviceId, final String url) { final Maybe> maybeFeedInfo = Maybe.fromCallable(() -> { final StreamingService service = NewPipe.getService(serviceId); final FeedExtractor feedExtractor = service.getFeedExtractor(url); @@ -152,12 +148,12 @@ public static Single> getFeedInfoFallbackToChannelInfo( return maybeFeedInfo.switchIfEmpty(getChannelInfo(serviceId, url, true)); } - public static Single getCommentsInfo(final int serviceId, - final String url, - boolean forceLoad) { + public static Single getCommentsInfo(final int serviceId, final String url, + final boolean forceLoad) { checkServiceId(serviceId); - return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, Single.fromCallable(() -> - CommentsInfo.getInfo(NewPipe.getService(serviceId), url))); + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, + Single.fromCallable(() -> + CommentsInfo.getInfo(NewPipe.getService(serviceId), url))); } public static Single getMoreCommentItems(final int serviceId, @@ -168,32 +164,30 @@ public static Single getMoreCommentItems(final int serviceId, CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPageUrl)); } - public static Single getPlaylistInfo(final int serviceId, - final String url, - boolean forceLoad) { + public static Single getPlaylistInfo(final int serviceId, final String url, + final boolean forceLoad) { checkServiceId(serviceId); - return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() -> - PlaylistInfo.getInfo(NewPipe.getService(serviceId), url))); + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, + Single.fromCallable(() -> + PlaylistInfo.getInfo(NewPipe.getService(serviceId), url))); } - public static Single getMorePlaylistItems(final int serviceId, - final String url, + public static Single getMorePlaylistItems(final int serviceId, final String url, final String nextStreamsUrl) { checkServiceId(serviceId); return Single.fromCallable(() -> PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); } - public static Single getKioskInfo(final int serviceId, - final String url, - boolean forceLoad) { - return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() -> - KioskInfo.getInfo(NewPipe.getService(serviceId), url))); + public static Single getKioskInfo(final int serviceId, final String url, + final boolean forceLoad) { + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, + Single.fromCallable(() -> KioskInfo.getInfo(NewPipe.getService(serviceId), url))); } public static Single getMoreKioskItems(final int serviceId, - final String url, - final String nextStreamsUrl) { + final String url, + final String nextStreamsUrl) { return Single.fromCallable(() -> KioskInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); @@ -207,23 +201,31 @@ public static Single getMoreKioskItems(final int serviceId, * Check if we can load it from the cache (forceLoad parameter), if we can't, * load from the network (Single loadFromNetwork) * and put the results in the cache. + * + * @param the item type's class that extends {@link Info} + * @param forceLoad whether to force loading from the network instead of from the cache + * @param serviceId the service to load from + * @param url the URL to load + * @param infoType the {@link InfoItem.InfoType} of the item + * @param loadFromNetwork the {@link Single} to load the item from the network + * @return a {@link Single} that loads the item */ - private static Single checkCache(boolean forceLoad, - int serviceId, - String url, - InfoItem.InfoType infoType, - Single loadFromNetwork) { + private static Single checkCache(final boolean forceLoad, + final int serviceId, final String url, + final InfoItem.InfoType infoType, + final Single loadFromNetwork) { checkServiceId(serviceId); - loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType)); + Single actualLoadFromNetwork = loadFromNetwork + .doOnSuccess(info -> CACHE.putInfo(serviceId, url, info, infoType)); Single load; if (forceLoad) { - cache.removeInfo(serviceId, url, infoType); - load = loadFromNetwork; + CACHE.removeInfo(serviceId, url, infoType); + load = actualLoadFromNetwork; } else { load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType), - loadFromNetwork.toMaybe()) - .firstElement() //Take the first valid + actualLoadFromNetwork.toMaybe()) + .firstElement() // Take the first valid .toSingle(); } @@ -231,14 +233,23 @@ private static Single checkCache(boolean forceLoad, } /** - * Default implementation uses the {@link InfoCache} to get cached results + * Default implementation uses the {@link InfoCache} to get cached results. + * + * @param the item type's class that extends {@link Info} + * @param serviceId the service to load from + * @param url the URL to load + * @param infoType the {@link InfoItem.InfoType} of the item + * @return a {@link Single} that loads the item */ - public static Maybe loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) { + public static Maybe loadFromCache(final int serviceId, final String url, + final InfoItem.InfoType infoType) { checkServiceId(serviceId); return Maybe.defer(() -> { //noinspection unchecked - I info = (I) cache.getFromKey(serviceId, url, infoType); - if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info); + I info = (I) CACHE.getFromKey(serviceId, url, infoType); + if (MainActivity.DEBUG) { + Log.d(TAG, "loadFromCache() called, info > " + info); + } // Only return info if it's not null (it is cached) if (info != null) { @@ -249,14 +260,26 @@ public static Maybe loadFromCache(final int serviceId, final }); } - public static boolean isCached(final int serviceId, final String url, InfoItem.InfoType infoType) { + public static boolean isCached(final int serviceId, final String url, + final InfoItem.InfoType infoType) { return null != loadFromCache(serviceId, url, infoType).blockingGet(); } /** - * A simple and general error handler that show a Toast for known exceptions, and for others, opens the report error activity with the (optional) error message. + * A simple and general error handler that show a Toast for known exceptions, + * and for others, opens the report error activity with the (optional) error message. + * + * @param context Android app context + * @param serviceId the service the exception happened in + * @param url the URL where the exception happened + * @param exception the exception to be handled + * @param userAction the action of the user that caused the exception + * @param optionalErrorMessage the optional error message */ - public static void handleGeneralException(Context context, int serviceId, String url, Throwable exception, UserAction userAction, String optionalErrorMessage) { + public static void handleGeneralException(final Context context, final int serviceId, + final String url, final Throwable exception, + final UserAction userAction, + final String optionalErrorMessage) { final Handler handler = new Handler(context.getMainLooper()); handler.post(() -> { @@ -271,10 +294,15 @@ public static void handleGeneralException(Context context, int serviceId, String } else if (exception instanceof ContentNotAvailableException) { Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); } else { - int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error : - exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error; - ErrorActivity.reportError(handler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, - serviceId == -1 ? "none" : NewPipe.getNameOfService(serviceId), url + (optionalErrorMessage == null ? "" : optionalErrorMessage), errorId)); + int errorId = exception instanceof YoutubeStreamExtractor.DecryptException + ? R.string.youtube_signature_decryption_error + : exception instanceof ParsingException + ? R.string.parsing_error : R.string.general_error; + ErrorActivity.reportError(handler, context, exception, MainActivity.class, null, + ErrorActivity.ErrorInfo.make(userAction, serviceId == -1 ? "none" + : NewPipe.getNameOfService(serviceId), + url + (optionalErrorMessage == null ? "" + : optionalErrorMessage), errorId)); } }); } @@ -283,12 +311,17 @@ public static void handleGeneralException(Context context, int serviceId, String * Check if throwable have the cause that can be assignable from the causes to check. * * @see Class#isAssignableFrom(Class) + * @param throwable the throwable to be checked + * @param causesToCheck the causes to check + * @return whether the exception is an instance of a subclass of one of the causes + * or is caused by an instance of a subclass of one of the causes */ - public static boolean hasAssignableCauseThrowable(Throwable throwable, - Class... causesToCheck) { + public static boolean hasAssignableCauseThrowable(final Throwable throwable, + final Class... causesToCheck) { // Check if getCause is not the same as cause (the getCause is already the root), // as it will cause a infinite loop if it is - Throwable cause, getCause = throwable; + Throwable cause; + Throwable getCause = throwable; // Check if throwable is a subclass of any of the filtered classes final Class throwableClass = throwable.getClass(); @@ -313,11 +346,18 @@ public static boolean hasAssignableCauseThrowable(Throwable throwable, /** * Check if throwable have the exact cause from one of the causes to check. + * + * @param throwable the throwable to be checked + * @param causesToCheck the causes to check + * @return whether the exception is an instance of one of the causes + * or is caused by an instance of one of the causes */ - public static boolean hasExactCauseThrowable(Throwable throwable, Class... causesToCheck) { + public static boolean hasExactCauseThrowable(final Throwable throwable, + final Class... causesToCheck) { // Check if getCause is not the same as cause (the getCause is already the root), // as it will cause a infinite loop if it is - Throwable cause, getCause = throwable; + Throwable cause; + Throwable getCause = throwable; for (Class causesEl : causesToCheck) { if (throwable.getClass().equals(causesEl)) { @@ -338,8 +378,11 @@ public static boolean hasExactCauseThrowable(Throwable throwable, Class... ca /** * Check if throwable have Interrupted* exception as one of its causes. + * + * @param throwable the throwable to be checkes + * @return whether the throwable is caused by an interruption */ - public static boolean isInterruptedCaused(Throwable throwable) { + public static boolean isInterruptedCaused(final Throwable throwable) { return ExtractorHelper.hasExactCauseThrowable(throwable, InterruptedIOException.class, InterruptedException.class); diff --git a/app/src/main/java/org/schabi/newpipe/util/FallbackViewHolder.java b/app/src/main/java/org/schabi/newpipe/util/FallbackViewHolder.java index bfe0ae5c5..967a54f0a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FallbackViewHolder.java +++ b/app/src/main/java/org/schabi/newpipe/util/FallbackViewHolder.java @@ -1,10 +1,11 @@ package org.schabi.newpipe.util; -import androidx.recyclerview.widget.RecyclerView; import android.view.View; +import androidx.recyclerview.widget.RecyclerView; + public class FallbackViewHolder extends RecyclerView.ViewHolder { - public FallbackViewHolder(View itemView) { + public FallbackViewHolder(final View itemView) { super(itemView); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java index 420322c27..6ede163a3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java @@ -5,11 +5,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Environment; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.SortedList; -import androidx.recyclerview.widget.RecyclerView; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; @@ -17,6 +12,12 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.loader.content.Loader; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SortedList; + import com.nononsenseapps.filepicker.AbstractFilePickerFragment; import com.nononsenseapps.filepicker.FilePickerFragment; @@ -25,11 +26,36 @@ import java.io.File; public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.FilePickerActivity { - private CustomFilePickerFragment currentFragment; + public static Intent chooseSingleFile(@NonNull final Context context) { + return new Intent(context, FilePickerActivityHelper.class) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false) + .putExtra(FilePickerActivityHelper.EXTRA_SINGLE_CLICK, true) + .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE); + } + + public static Intent chooseFileToSave(@NonNull final Context context, + @Nullable final String startPath) { + return new Intent(context, FilePickerActivityHelper.class) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_EXISTING_FILE, true) + .putExtra(FilePickerActivityHelper.EXTRA_START_PATH, startPath) + .putExtra(FilePickerActivityHelper.EXTRA_MODE, + FilePickerActivityHelper.MODE_NEW_FILE); + } + + public static boolean isOwnFileUri(@NonNull final Context context, @NonNull final Uri uri) { + if (uri.getAuthority() == null) { + return false; + } + return uri.getAuthority().startsWith(context.getPackageName()); + } + @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { if (ThemeHelper.isLightThemeSelected(this)) { this.setTheme(R.style.FilePickerThemeLight); } else { @@ -50,33 +76,18 @@ public void onBackPressed() { } @Override - protected AbstractFilePickerFragment getFragment(@Nullable String startPath, int mode, boolean allowMultiple, boolean allowCreateDir, boolean allowExistingFile, boolean singleClick) { + protected AbstractFilePickerFragment getFragment(@Nullable final String startPath, + final int mode, + final boolean allowMultiple, + final boolean allowCreateDir, + final boolean allowExistingFile, + final boolean singleClick) { final CustomFilePickerFragment fragment = new CustomFilePickerFragment(); - fragment.setArgs(startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(), + fragment.setArgs(startPath != null ? startPath + : Environment.getExternalStorageDirectory().getPath(), mode, allowMultiple, allowCreateDir, allowExistingFile, singleClick); - return currentFragment = fragment; - } - - public static Intent chooseSingleFile(@NonNull Context context) { - return new Intent(context, FilePickerActivityHelper.class) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false) - .putExtra(FilePickerActivityHelper.EXTRA_SINGLE_CLICK, true) - .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE); - } - - public static Intent chooseFileToSave(@NonNull Context context, @Nullable String startPath) { - return new Intent(context, FilePickerActivityHelper.class) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_EXISTING_FILE, true) - .putExtra(FilePickerActivityHelper.EXTRA_START_PATH, startPath) - .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_NEW_FILE); - } - - public static boolean isOwnFileUri(@NonNull Context context, @NonNull Uri uri) { - if (uri.getAuthority() == null) return false; - return uri.getAuthority().startsWith(context.getPackageName()); + currentFragment = fragment; + return currentFragment; } /*////////////////////////////////////////////////////////////////////////// @@ -84,30 +95,35 @@ public static boolean isOwnFileUri(@NonNull Context context, @NonNull Uri uri) { //////////////////////////////////////////////////////////////////////////*/ public static class CustomFilePickerFragment extends FilePickerFragment { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @NonNull @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int viewType) { final RecyclerView.ViewHolder viewHolder = super.onCreateViewHolder(parent, viewType); final View view = viewHolder.itemView.findViewById(android.R.id.text1); if (view instanceof TextView) { - ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.file_picker_items_text_size)); + ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, + getResources().getDimension(R.dimen.file_picker_items_text_size)); } return viewHolder; } @Override - public void onClickOk(@NonNull View view) { + public void onClickOk(@NonNull final View view) { if (mode == MODE_NEW_FILE && getNewFileName().isEmpty()) { - if (mToast != null) mToast.cancel(); - mToast = Toast.makeText(getActivity(), R.string.file_name_empty_error, Toast.LENGTH_SHORT); + if (mToast != null) { + mToast.cancel(); + } + mToast = Toast.makeText(getActivity(), R.string.file_name_empty_error, + Toast.LENGTH_SHORT); mToast.show(); return; } @@ -116,13 +132,17 @@ public void onClickOk(@NonNull View view) { } @Override - protected boolean isItemVisible(@NonNull File file) { - if (file.isDirectory() && file.isHidden()) return true; + protected boolean isItemVisible(@NonNull final File file) { + if (file.isDirectory() && file.isHidden()) { + return true; + } return super.isItemVisible(file); } public File getBackTop() { - if (getArguments() == null) return Environment.getExternalStorageDirectory(); + if (getArguments() == null) { + return Environment.getExternalStorageDirectory(); + } final String path = getArguments().getString(KEY_START_PATH, "/"); if (path.contains(Environment.getExternalStorageDirectory().getPath())) { @@ -133,11 +153,13 @@ public File getBackTop() { } public boolean isBackTop() { - return compareFiles(mCurrentPath, getBackTop()) == 0 || compareFiles(mCurrentPath, new File("/")) == 0; + return compareFiles(mCurrentPath, + getBackTop()) == 0 || compareFiles(mCurrentPath, new File("/")) == 0; } @Override - public void onLoadFinished(Loader> loader, SortedList data) { + public void onLoadFinished(final Loader> loader, + final SortedList data) { super.onLoadFinished(loader, data); layoutManager.scrollToPosition(0); } diff --git a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java index 37d94cd16..3179662ba 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java @@ -8,37 +8,44 @@ import java.util.regex.Pattern; -public class FilenameUtils { - +public final class FilenameUtils { private static final String CHARSET_MOST_SPECIAL = "[\\n\\r|?*<\":\\\\>/']+"; private static final String CHARSET_ONLY_LETTERS_AND_DIGITS = "[^\\w\\d]+"; + private FilenameUtils() { } + /** * #143 #44 #42 #22: make sure that the filename does not contain illegal chars. + * * @param context the context to retrieve strings and preferences from - * @param title the title to create a filename from + * @param title the title to create a filename from * @return the filename */ - public static String createFilename(Context context, String title) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + public static String createFilename(final Context context, final String title) { + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context); - final String charset_ld = context.getString(R.string.charset_letters_and_digits_value); - final String charset_ms = context.getString(R.string.charset_most_special_value); + final String charsetLd = context.getString(R.string.charset_letters_and_digits_value); + final String charsetMs = context.getString(R.string.charset_most_special_value); final String defaultCharset = context.getString(R.string.default_file_charset_value); - final String replacementChar = sharedPreferences.getString(context.getString(R.string.settings_file_replacement_character_key), "_"); - String selectedCharset = sharedPreferences.getString(context.getString(R.string.settings_file_charset_key), null); + final String replacementChar = sharedPreferences.getString( + context.getString(R.string.settings_file_replacement_character_key), "_"); + String selectedCharset = sharedPreferences.getString( + context.getString(R.string.settings_file_charset_key), null); final String charset; - if (selectedCharset == null || selectedCharset.isEmpty()) selectedCharset = defaultCharset; + if (selectedCharset == null || selectedCharset.isEmpty()) { + selectedCharset = defaultCharset; + } - if (selectedCharset.equals(charset_ld)) { + if (selectedCharset.equals(charsetLd)) { charset = CHARSET_ONLY_LETTERS_AND_DIGITS; - } else if (selectedCharset.equals(charset_ms)) { + } else if (selectedCharset.equals(charsetMs)) { charset = CHARSET_MOST_SPECIAL; } else { - charset = selectedCharset;// ¿is the user using a custom charset? + charset = selectedCharset; // Is the user using a custom charset? } Pattern pattern = Pattern.compile(charset); @@ -47,13 +54,15 @@ public static String createFilename(Context context, String title) { } /** - * Create a valid filename - * @param title the title to create a filename from + * Create a valid filename. + * + * @param title the title to create a filename from * @param invalidCharacters patter matching invalid characters - * @param replacementChar the replacement + * @param replacementChar the replacement * @return the filename */ - private static String createFilename(String title, Pattern invalidCharacters, String replacementChar) { + private static String createFilename(final String title, final Pattern invalidCharacters, + final String replacementChar) { return title.replaceAll(invalidCharacters.pattern(), replacementChar); } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java b/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java index 69666463e..76634bf8a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/FireTvUtils.java @@ -2,9 +2,10 @@ import org.schabi.newpipe.App; -public class FireTvUtils { - public static boolean isFireTv(){ - final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; - return App.getApp().getPackageManager().hasSystemFeature(AMAZON_FEATURE_FIRE_TV); +public final class FireTvUtils { + private FireTvUtils() { } + + public static boolean isFireTv() { + return App.getApp().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv"); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java index 9ee8a1095..37ebd636a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java +++ b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java @@ -8,11 +8,11 @@ import org.schabi.newpipe.R; -public class ImageDisplayConstants { +public final class ImageDisplayConstants { private static final int BITMAP_FADE_IN_DURATION_MILLIS = 250; /** - * Base display options + * This constant contains the base display options. */ private static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS = new DisplayImageOptions.Builder() @@ -55,4 +55,6 @@ public class ImageDisplayConstants { .showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist) .showImageOnFail(R.drawable.dummy_thumbnail_playlist) .build(); + + private ImageDisplayConstants() { } } diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java index afb7604c5..03eae344a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java @@ -19,10 +19,11 @@ package org.schabi.newpipe.util; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.collection.LruCache; -import android.util.Log; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.Info; @@ -30,104 +31,119 @@ import java.util.Map; - public final class InfoCache { private static final boolean DEBUG = MainActivity.DEBUG; - private final String TAG = getClass().getSimpleName(); - - private static final InfoCache instance = new InfoCache(); + private static final InfoCache INSTANCE = new InfoCache(); private static final int MAX_ITEMS_ON_CACHE = 60; /** - * Trim the cache to this size + * Trim the cache to this size. */ private static final int TRIM_CACHE_TO = 30; - - private static final LruCache lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE); + private static final LruCache LRU_CACHE = new LruCache<>(MAX_ITEMS_ON_CACHE); + private final String TAG = getClass().getSimpleName(); private InfoCache() { //no instance } public static InfoCache getInstance() { - return instance; + return INSTANCE; + } + + @NonNull + private static String keyOf(final int serviceId, @NonNull final String url, + @NonNull final InfoItem.InfoType infoType) { + return serviceId + url + infoType.toString(); + } + + private static void removeStaleCache() { + for (Map.Entry entry : InfoCache.LRU_CACHE.snapshot().entrySet()) { + final CacheData data = entry.getValue(); + if (data != null && data.isExpired()) { + InfoCache.LRU_CACHE.remove(entry.getKey()); + } + } } @Nullable - public Info getFromKey(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) { - if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); - synchronized (lruCache) { + private static Info getInfo(@NonNull final String key) { + final CacheData data = InfoCache.LRU_CACHE.get(key); + if (data == null) { + return null; + } + + if (data.isExpired()) { + InfoCache.LRU_CACHE.remove(key); + return null; + } + + return data.info; + } + + @Nullable + public Info getFromKey(final int serviceId, @NonNull final String url, + @NonNull final InfoItem.InfoType infoType) { + if (DEBUG) { + Log.d(TAG, "getFromKey() called with: " + + "serviceId = [" + serviceId + "], url = [" + url + "]"); + } + synchronized (LRU_CACHE) { return getInfo(keyOf(serviceId, url, infoType)); } } - public void putInfo(int serviceId, @NonNull String url, @NonNull Info info, @NonNull InfoItem.InfoType infoType) { - if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]"); + public void putInfo(final int serviceId, @NonNull final String url, @NonNull final Info info, + @NonNull final InfoItem.InfoType infoType) { + if (DEBUG) { + Log.d(TAG, "putInfo() called with: info = [" + info + "]"); + } final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId()); - synchronized (lruCache) { + synchronized (LRU_CACHE) { final CacheData data = new CacheData(info, expirationMillis); - lruCache.put(keyOf(serviceId, url, infoType), data); + LRU_CACHE.put(keyOf(serviceId, url, infoType), data); } } - public void removeInfo(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) { - if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); - synchronized (lruCache) { - lruCache.remove(keyOf(serviceId, url, infoType)); + public void removeInfo(final int serviceId, @NonNull final String url, + @NonNull final InfoItem.InfoType infoType) { + if (DEBUG) { + Log.d(TAG, "removeInfo() called with: " + + "serviceId = [" + serviceId + "], url = [" + url + "]"); + } + synchronized (LRU_CACHE) { + LRU_CACHE.remove(keyOf(serviceId, url, infoType)); } } public void clearCache() { - if (DEBUG) Log.d(TAG, "clearCache() called"); - synchronized (lruCache) { - lruCache.evictAll(); + if (DEBUG) { + Log.d(TAG, "clearCache() called"); + } + synchronized (LRU_CACHE) { + LRU_CACHE.evictAll(); } } public void trimCache() { - if (DEBUG) Log.d(TAG, "trimCache() called"); - synchronized (lruCache) { + if (DEBUG) { + Log.d(TAG, "trimCache() called"); + } + synchronized (LRU_CACHE) { removeStaleCache(); - lruCache.trimToSize(TRIM_CACHE_TO); + LRU_CACHE.trimToSize(TRIM_CACHE_TO); } } public long getSize() { - synchronized (lruCache) { - return lruCache.size(); - } - } - - @NonNull - private static String keyOf(final int serviceId, @NonNull final String url, @NonNull InfoItem.InfoType infoType) { - return serviceId + url + infoType.toString(); - } - - private static void removeStaleCache() { - for (Map.Entry entry : InfoCache.lruCache.snapshot().entrySet()) { - final CacheData data = entry.getValue(); - if (data != null && data.isExpired()) { - InfoCache.lruCache.remove(entry.getKey()); - } - } - } - - @Nullable - private static Info getInfo(@NonNull final String key) { - final CacheData data = InfoCache.lruCache.get(key); - if (data == null) return null; - - if (data.isExpired()) { - InfoCache.lruCache.remove(key); - return null; + synchronized (LRU_CACHE) { + return LRU_CACHE.size(); } - - return data.info; } - final private static class CacheData { - final private long expireTimestamp; - final private Info info; + private static final class CacheData { + private final long expireTimestamp; + private final Info info; private CacheData(@NonNull final Info info, final long timeoutMillis) { this.expireTimestamp = System.currentTimeMillis() + timeoutMillis; diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java index 18c95e394..15d4bf22f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java +++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java @@ -7,23 +7,28 @@ /** * Created by Chrsitian Schabesberger on 28.09.17. * KioskTranslator.java is part of NewPipe. - * + *

* 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. - * + *

+ *

* 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. - * + *

+ *

* You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . + *

*/ -public class KioskTranslator { - public static String getTranslatedKioskName(String kioskId, Context c) { +public final class KioskTranslator { + private KioskTranslator() { } + + public static String getTranslatedKioskName(final String kioskId, final Context c) { switch (kioskId) { case "Trending": return c.getString(R.string.trending); @@ -44,13 +49,12 @@ public static String getTranslatedKioskName(String kioskId, Context c) { } } - public static int getKioskIcons(String kioskId, Context c) { - switch(kioskId) { + public static int getKioskIcons(final String kioskId, final Context c) { + switch (kioskId) { case "Trending": - return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); case "Top 50": - return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); case "New & hot": + case "conferences": return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); case "Local": return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local); @@ -58,8 +62,6 @@ public static int getKioskIcons(String kioskId, Context c) { return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent); case "Most liked": return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.thumbs_up); - case "conferences": - return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); default: return 0; } diff --git a/app/src/main/java/org/schabi/newpipe/util/KoreUtil.java b/app/src/main/java/org/schabi/newpipe/util/KoreUtil.java index 2ed3c698d..85cf82db1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KoreUtil.java +++ b/app/src/main/java/org/schabi/newpipe/util/KoreUtil.java @@ -3,21 +3,21 @@ import android.content.Context; import android.content.DialogInterface; + import androidx.appcompat.app.AlertDialog; import org.schabi.newpipe.R; - -public class KoreUtil { +public final class KoreUtil { private KoreUtil() { } public static void showInstallKoreDialog(final Context context) { final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage(R.string.kore_not_found) - .setPositiveButton(R.string.install, - (DialogInterface dialog, int which) -> NavigationHelper.installKore(context)) - .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { - }); + .setPositiveButton(R.string.install, (DialogInterface dialog, int which) -> + NavigationHelper.installKore(context)) + .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { + }); builder.create().show(); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java b/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java index df7549c47..2ca128409 100644 --- a/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java +++ b/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java @@ -2,35 +2,38 @@ import android.content.Context; import android.graphics.PointF; + import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; public class LayoutManagerSmoothScroller extends LinearLayoutManager { - - public LayoutManagerSmoothScroller(Context context) { + public LayoutManagerSmoothScroller(final Context context) { super(context, VERTICAL, false); } - public LayoutManagerSmoothScroller(Context context, int orientation, boolean reverseLayout) { + public LayoutManagerSmoothScroller(final Context context, final int orientation, + final boolean reverseLayout) { super(context, orientation, reverseLayout); } @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { - RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()); + public void smoothScrollToPosition(final RecyclerView recyclerView, + final RecyclerView.State state, final int position) { + RecyclerView.SmoothScroller smoothScroller + = new TopSnappedSmoothScroller(recyclerView.getContext()); smoothScroller.setTargetPosition(position); startSmoothScroll(smoothScroller); } private class TopSnappedSmoothScroller extends LinearSmoothScroller { - public TopSnappedSmoothScroller(Context context) { + TopSnappedSmoothScroller(final Context context) { super(context); } @Override - public PointF computeScrollVectorForPosition(int targetPosition) { + public PointF computeScrollVectorForPosition(final int targetPosition) { return LayoutManagerSmoothScroller.this .computeScrollVectorForPosition(targetPosition); } @@ -40,4 +43,4 @@ protected int getVerticalSnapPreference() { return SNAP_TO_START; } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index eb950b1ed..1b2b74c6f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -4,6 +4,7 @@ import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.preference.PreferenceManager; + import androidx.annotation.StringRes; import org.schabi.newpipe.R; @@ -17,26 +18,31 @@ import java.util.HashMap; import java.util.List; -@SuppressWarnings("WeakerAccess") public final class ListHelper { - // Video format in order of quality. 0=lowest quality, n=highest quality private static final List VIDEO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); + Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); // Audio format in order of quality. 0=lowest quality, n=highest quality private static final List AUDIO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); + Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); - private static final List HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60"); + private static final List HIGH_RESOLUTION_LIST + = Arrays.asList("1440p", "2160p", "1440p60", "2160p60"); + + private ListHelper() { } /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) + * @param context Android app context + * @param videoStreams list of the video streams to check + * @return index of the video stream with the default index */ - public static int getDefaultResolutionIndex(Context context, List videoStreams) { + public static int getDefaultResolutionIndex(final Context context, + final List videoStreams) { String defaultResolution = computeDefaultResolution(context, R.string.default_resolution_key, R.string.default_resolution_value); return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); @@ -44,15 +50,25 @@ public static int getDefaultResolutionIndex(Context context, List v /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) + * @param context Android app context + * @param videoStreams list of the video streams to check + * @param defaultResolution the default resolution to look for + * @return index of the video stream with the default index */ - public static int getResolutionIndex(Context context, List videoStreams, String defaultResolution) { + public static int getResolutionIndex(final Context context, + final List videoStreams, + final String defaultResolution) { return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } /** * @see #getDefaultResolutionIndex(String, String, MediaFormat, List) + * @param context Android app context + * @param videoStreams list of the video streams to check + * @return index of the video stream with the default index */ - public static int getPopupDefaultResolutionIndex(Context context, List videoStreams) { + public static int getPopupDefaultResolutionIndex(final Context context, + final List videoStreams) { String defaultResolution = computeDefaultResolution(context, R.string.default_popup_resolution_key, R.string.default_popup_resolution_value); return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); @@ -60,12 +76,19 @@ public static int getPopupDefaultResolutionIndex(Context context, List videoStreams, String defaultResolution) { + public static int getPopupResolutionIndex(final Context context, + final List videoStreams, + final String defaultResolution) { return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams); } - public static int getDefaultAudioFormat(Context context, List audioStreams) { + public static int getDefaultAudioFormat(final Context context, + final List audioStreams) { MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key, R.string.default_audio_format_value); @@ -79,8 +102,8 @@ public static int getDefaultAudioFormat(Context context, List audio } /** - * Join the two lists of video streams (video_only and normal videos), and sort them according with default format - * chosen by the user + * Join the two lists of video streams (video_only and normal videos), + * and sort them according with default format chosen by the user. * * @param context context to search for the format to give preference * @param videoStreams normal videos list @@ -88,20 +111,28 @@ public static int getDefaultAudioFormat(Context context, List audio * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest * @return the sorted list */ - public static List getSortedStreamVideosList(Context context, List videoStreams, List videoOnlyStreams, boolean ascendingOrder) { + public static List getSortedStreamVideosList(final Context context, + final List videoStreams, + final List + videoOnlyStreams, + final boolean ascendingOrder) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - boolean showHigherResolutions = preferences.getBoolean(context.getString(R.string.show_higher_resolutions_key), false); - MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value); + boolean showHigherResolutions = preferences.getBoolean( + context.getString(R.string.show_higher_resolutions_key), false); + MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, + R.string.default_video_format_value); - return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder); + return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, + videoOnlyStreams, ascendingOrder); } /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ - private static String computeDefaultResolution(Context context, int key, int value) { + private static String computeDefaultResolution(final Context context, final int key, + final int value) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); // Load the prefered resolution otherwise the best available @@ -110,7 +141,8 @@ private static String computeDefaultResolution(Context context, int key, int val : context.getString(R.string.best_resolution_key); String maxResolution = getResolutionLimit(context); - if (maxResolution != null && (resolution.equals(context.getString(R.string.best_resolution_key)) + if (maxResolution != null + && (resolution.equals(context.getString(R.string.best_resolution_key)) || compareVideoStreamResolution(maxResolution, resolution) < 1)) { resolution = maxResolution; } @@ -119,20 +151,29 @@ private static String computeDefaultResolution(Context context, int key, int val /** * Return the index of the default stream in the list, based on the parameters - * defaultResolution and defaultFormat + * defaultResolution and defaultFormat. * + * @param defaultResolution the default resolution to look for + * @param bestResolutionKey key of the best resolution + * @param defaultFormat the default fomat to look for + * @param videoStreams list of the video streams to check * @return index of the default resolution&format */ - static int getDefaultResolutionIndex(String defaultResolution, String bestResolutionKey, - MediaFormat defaultFormat, List videoStreams) { - if (videoStreams == null || videoStreams.isEmpty()) return -1; + static int getDefaultResolutionIndex(final String defaultResolution, + final String bestResolutionKey, + final MediaFormat defaultFormat, + final List videoStreams) { + if (videoStreams == null || videoStreams.isEmpty()) { + return -1; + } sortStreamList(videoStreams, false); if (defaultResolution.equals(bestResolutionKey)) { return 0; } - int defaultStreamIndex = getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); + int defaultStreamIndex + = getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); // this is actually an error, // but maybe there is really no stream fitting to the default value. @@ -143,39 +184,53 @@ static int getDefaultResolutionIndex(String defaultResolution, String bestResolu } /** - * Join the two lists of video streams (video_only and normal videos), and sort them according with default format - * chosen by the user + * Join the two lists of video streams (video_only and normal videos), + * and sort them according with default format chosen by the user. * - * @param defaultFormat format to give preference + * @param defaultFormat format to give preference * @param showHigherResolutions show >1080p resolutions * @param videoStreams normal videos list * @param videoOnlyStreams video only stream list - * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list + * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest * @return the sorted list */ - static List getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List videoStreams, List videoOnlyStreams, boolean ascendingOrder) { + static List getSortedStreamVideosList(final MediaFormat defaultFormat, + final boolean showHigherResolutions, + final List videoStreams, + final List videoOnlyStreams, + final boolean ascendingOrder) { ArrayList retList = new ArrayList<>(); HashMap hashMap = new HashMap<>(); if (videoOnlyStreams != null) { for (VideoStream stream : videoOnlyStreams) { - if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue; + if (!showHigherResolutions + && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) { + continue; + } retList.add(stream); } } if (videoStreams != null) { for (VideoStream stream : videoStreams) { - if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue; + if (!showHigherResolutions + && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) { + continue; + } retList.add(stream); } } // Add all to the hashmap - for (VideoStream videoStream : retList) hashMap.put(videoStream.getResolution(), videoStream); + for (VideoStream videoStream : retList) { + hashMap.put(videoStream.getResolution(), videoStream); + } // Override the values when the key == resolution, with the defaultFormat for (VideoStream videoStream : retList) { - if (videoStream.getFormat() == defaultFormat) hashMap.put(videoStream.getResolution(), videoStream); + if (videoStream.getFormat() == defaultFormat) { + hashMap.put(videoStream.getResolution(), videoStream); + } } retList.clear(); @@ -203,7 +258,8 @@ static List getSortedStreamVideosList(MediaFormat defaultFormat, bo * @param videoStreams list that the sorting will be applied * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest */ - private static void sortStreamList(List videoStreams, final boolean ascendingOrder) { + private static void sortStreamList(final List videoStreams, + final boolean ascendingOrder) { Collections.sort(videoStreams, (o1, o2) -> { int result = compareVideoStreamResolution(o1, o2); return result == 0 ? 0 : (ascendingOrder ? result : -result); @@ -214,18 +270,21 @@ private static void sortStreamList(List videoStreams, final boolean * Get the audio from the list with the highest quality. Format will be ignored if it yields * no results. * + * @param format the format to look for * @param audioStreams list the audio streams * @return index of the audio with the highest average bitrate of the default format */ - static int getHighestQualityAudioIndex(MediaFormat format, List audioStreams) { + static int getHighestQualityAudioIndex(final MediaFormat format, + final List audioStreams) { int result = -1; + boolean hasOneFormat = false; if (audioStreams != null) { - while(result == -1) { + while (result == -1) { AudioStream prevStream = null; for (int idx = 0; idx < audioStreams.size(); idx++) { AudioStream stream = audioStreams.get(idx); - if ((format == null || stream.getFormat() == format) && - (prevStream == null || compareAudioStreamBitrate(prevStream, stream, + if ((format == null || stream.getFormat() == format || hasOneFormat) + && (prevStream == null || compareAudioStreamBitrate(prevStream, stream, AUDIO_FORMAT_QUALITY_RANKING) < 0)) { prevStream = stream; result = idx; @@ -234,7 +293,7 @@ static int getHighestQualityAudioIndex(MediaFormat format, List aud if (result == -1 && format == null) { break; } - format = null; + hasOneFormat = true; } } return result; @@ -244,19 +303,21 @@ static int getHighestQualityAudioIndex(MediaFormat format, List aud * Get the audio from the list with the lowest bitrate and efficient format. Format will be * ignored if it yields no results. * - * @param format The target format type or null if it doesn't matter - * @param audioStreams list the audio streams - * @return index of the audio stream that can produce the most compact results or -1 if not found. + * @param format The target format type or null if it doesn't matter + * @param audioStreams List of audio streams + * @return Index of audio stream that can produce the most compact results or -1 if not found */ - static int getMostCompactAudioIndex(MediaFormat format, List audioStreams) { + static int getMostCompactAudioIndex(final MediaFormat format, + final List audioStreams) { int result = -1; + boolean hasOneFormat = false; if (audioStreams != null) { - while(result == -1) { + while (result == -1) { AudioStream prevStream = null; for (int idx = 0; idx < audioStreams.size(); idx++) { AudioStream stream = audioStreams.get(idx); - if ((format == null || stream.getFormat() == format) && - (prevStream == null || compareAudioStreamBitrate(prevStream, stream, + if ((format == null || stream.getFormat() == format || hasOneFormat) + && (prevStream == null || compareAudioStreamBitrate(prevStream, stream, AUDIO_FORMAT_EFFICIENCY_RANKING) > 0)) { prevStream = stream; result = idx; @@ -265,7 +326,7 @@ static int getMostCompactAudioIndex(MediaFormat format, List audioS if (result == -1 && format == null) { break; } - format = null; + hasOneFormat = true; } } return result; @@ -273,16 +334,25 @@ static int getMostCompactAudioIndex(MediaFormat format, List audioS /** * Locates a possible match for the given resolution and format in the provided list. - * In this order: - * 1. Find a format and resolution match - * 2. Find a format and resolution match and ignore the refresh - * 3. Find a resolution match - * 4. Find a resolution match and ignore the refresh - * 5. Find a resolution just below the requested resolution and ignore the refresh - * 6. Give up + * + *

In this order:

+ * + *
    + *
  1. Find a format and resolution match
  2. + *
  3. Find a format and resolution match and ignore the refresh
  4. + *
  5. Find a resolution match
  6. + *
  7. Find a resolution match and ignore the refresh
  8. + *
  9. Find a resolution just below the requested resolution and ignore the refresh
  10. + *
  11. Give up
  12. + *
+ * + * @param targetResolution the resolution to look for + * @param targetFormat the format to look for + * @param videoStreams the available video streams + * @return the index of the prefered video stream */ - static int getVideoStreamIndex(String targetResolution, MediaFormat targetFormat, - List videoStreams) { + static int getVideoStreamIndex(final String targetResolution, final MediaFormat targetFormat, + final List videoStreams) { int fullMatchIndex = -1; int fullMatchNoRefreshIndex = -1; int resMatchOnlyIndex = -1; @@ -307,11 +377,13 @@ static int getVideoStreamIndex(String targetResolution, MediaFormat targetFormat resMatchOnlyIndex = idx; } - if (resMatchOnlyNoRefreshIndex == -1 && resolutionNoRefresh.equals(targetResolutionNoRefresh)) { + if (resMatchOnlyNoRefreshIndex == -1 + && resolutionNoRefresh.equals(targetResolutionNoRefresh)) { resMatchOnlyNoRefreshIndex = idx; } - if (lowerResMatchNoRefreshIndex == -1 && compareVideoStreamResolution(resolutionNoRefresh, targetResolutionNoRefresh) < 0) { + if (lowerResMatchNoRefreshIndex == -1 && compareVideoStreamResolution( + resolutionNoRefresh, targetResolutionNoRefresh) < 0) { lowerResMatchNoRefreshIndex = idx; } } @@ -332,30 +404,44 @@ static int getVideoStreamIndex(String targetResolution, MediaFormat targetFormat } /** - * Fetches the desired resolution or returns the default if it is not found. The resolution - * will be reduced if video chocking is active. + * Fetches the desired resolution or returns the default if it is not found. + * The resolution will be reduced if video chocking is active. + * + * @param context Android app context + * @param defaultResolution the default resolution + * @param videoStreams the list of video streams to check + * @return the index of the prefered video stream */ - private static int getDefaultResolutionWithDefaultFormat(Context context, String defaultResolution, List videoStreams) { - MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value); - return getDefaultResolutionIndex(defaultResolution, context.getString(R.string.best_resolution_key), defaultFormat, videoStreams); + private static int getDefaultResolutionWithDefaultFormat(final Context context, + final String defaultResolution, + final List videoStreams) { + MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, + R.string.default_video_format_value); + return getDefaultResolutionIndex(defaultResolution, + context.getString(R.string.best_resolution_key), defaultFormat, videoStreams); } - private static MediaFormat getDefaultFormat(Context context, @StringRes int defaultFormatKey, @StringRes int defaultFormatValueKey) { + private static MediaFormat getDefaultFormat(final Context context, + @StringRes final int defaultFormatKey, + @StringRes final int defaultFormatValueKey) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); String defaultFormat = context.getString(defaultFormatValueKey); - String defaultFormatString = preferences.getString(context.getString(defaultFormatKey), defaultFormat); + String defaultFormatString = preferences.getString( + context.getString(defaultFormatKey), defaultFormat); MediaFormat defaultMediaFormat = getMediaFormatFromKey(context, defaultFormatString); if (defaultMediaFormat == null) { - preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat).apply(); + preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat) + .apply(); defaultMediaFormat = getMediaFormatFromKey(context, defaultFormat); } return defaultMediaFormat; } - private static MediaFormat getMediaFormatFromKey(Context context, String formatKey) { + private static MediaFormat getMediaFormatFromKey(final Context context, + final String formatKey) { MediaFormat format = null; if (formatKey.equals(context.getString(R.string.video_webm_key))) { format = MediaFormat.WEBM; @@ -372,8 +458,9 @@ private static MediaFormat getMediaFormatFromKey(Context context, String formatK } // Compares the quality of two audio streams - private static int compareAudioStreamBitrate(AudioStream streamA, AudioStream streamB, - List formatRanking) { + private static int compareAudioStreamBitrate(final AudioStream streamA, + final AudioStream streamB, + final List formatRanking) { if (streamA == null) { return -1; } @@ -388,10 +475,11 @@ private static int compareAudioStreamBitrate(AudioStream streamA, AudioStream st } // Same bitrate and format - return formatRanking.indexOf(streamA.getFormat()) - formatRanking.indexOf(streamB.getFormat()); + return formatRanking.indexOf(streamA.getFormat()) + - formatRanking.indexOf(streamB.getFormat()); } - private static int compareVideoStreamResolution(String r1, String r2) { + private static int compareVideoStreamResolution(final String r1, final String r2) { int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1") .replaceAll("[^\\d.]", "")); int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1") @@ -400,7 +488,8 @@ private static int compareVideoStreamResolution(String r1, String r2) { } // Compares the quality of two video streams. - private static int compareVideoStreamResolution(VideoStream streamA, VideoStream streamB) { + private static int compareVideoStreamResolution(final VideoStream streamA, + final VideoStream streamB) { if (streamA == null) { return -1; } @@ -408,27 +497,29 @@ private static int compareVideoStreamResolution(VideoStream streamA, VideoStream return 1; } - int resComp = compareVideoStreamResolution(streamA.getResolution(), streamB.getResolution()); + int resComp = compareVideoStreamResolution(streamA.getResolution(), + streamB.getResolution()); if (resComp != 0) { return resComp; } // Same bitrate and format - return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat()); + return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) + - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat()); } - - private static boolean isLimitingDataUsage(Context context) { + private static boolean isLimitingDataUsage(final Context context) { return getResolutionLimit(context) != null; } /** - * The maximum resolution allowed + * The maximum resolution allowed. + * * @param context App context * @return maximum resolution allowed or null if there is no maximum */ - private static String getResolutionLimit(Context context) { + private static String getResolutionLimit(final Context context) { String resolutionLimit = null; if (isMeteredNetwork(context)) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); @@ -442,13 +533,16 @@ private static String getResolutionLimit(Context context) { /** * The current network is metered (like mobile data)? + * * @param context App context * @return {@code true} if connected to a metered network */ - private static boolean isMeteredNetwork(Context context) - { - ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (manager == null || manager.getActiveNetworkInfo() == null) return false; + private static boolean isMeteredNetwork(final Context context) { + ConnectivityManager manager + = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (manager == null || manager.getActiveNetworkInfo() == null) { + return false; + } return manager.isActiveNetworkMetered(); } diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 9c8fc25b8..0b81df07d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -49,15 +49,14 @@ * along with NewPipe. If not, see . */ -public class Localization { +public final class Localization { private static final String DOT_SEPARATOR = " • "; private static PrettyTime prettyTime; - private Localization() { - } + private Localization() { } - public static void init(Context context) { + public static void init(final Context context) { initPrettyTime(context); } @@ -68,7 +67,9 @@ public static String concatenateStrings(final String... strings) { @NonNull public static String concatenateStrings(final List strings) { - if (strings.isEmpty()) return ""; + if (strings.isEmpty()) { + return ""; + } final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(strings.get(0)); @@ -83,27 +84,31 @@ public static String concatenateStrings(final List strings) { return stringBuilder.toString(); } - public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(final Context context) { + public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization( + final Context context) { final String contentLanguage = PreferenceManager .getDefaultSharedPreferences(context) - .getString(context.getString(R.string.content_language_key), context.getString(R.string.default_localization_key)); + .getString(context.getString(R.string.content_language_key), + context.getString(R.string.default_localization_key)); if (contentLanguage.equals(context.getString(R.string.default_localization_key))) { - return org.schabi.newpipe.extractor.localization.Localization.fromLocale(Locale.getDefault()); + return org.schabi.newpipe.extractor.localization.Localization + .fromLocale(Locale.getDefault()); } - return org.schabi.newpipe.extractor.localization.Localization.fromLocalizationCode(contentLanguage); + return org.schabi.newpipe.extractor.localization.Localization + .fromLocalizationCode(contentLanguage); } public static ContentCountry getPreferredContentCountry(final Context context) { - final String contentCountry = PreferenceManager - .getDefaultSharedPreferences(context) - .getString(context.getString(R.string.content_country_key), context.getString(R.string.default_localization_key)); + final String contentCountry = PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.content_country_key), + context.getString(R.string.default_localization_key)); if (contentCountry.equals(context.getString(R.string.default_localization_key))) { return new ContentCountry(Locale.getDefault().getCountry()); } return new ContentCountry(contentCountry); } - public static Locale getPreferredLocale(Context context) { + public static Locale getPreferredLocale(final Context context) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); String languageCode = sp.getString(context.getString(R.string.content_language_key), @@ -122,88 +127,103 @@ public static Locale getPreferredLocale(Context context) { return Locale.getDefault(); } - public static String localizeNumber(Context context, long number) { + public static String localizeNumber(final Context context, final long number) { return localizeNumber(context, (double) number); } - public static String localizeNumber(Context context, double number) { + public static String localizeNumber(final Context context, final double number) { NumberFormat nf = NumberFormat.getInstance(getAppLocale(context)); return nf.format(number); } - public static String formatDate(Date date, Context context) { + public static String formatDate(final Date date, final Context context) { return DateFormat.getDateInstance(DateFormat.MEDIUM, getAppLocale(context)).format(date); } @SuppressLint("StringFormatInvalid") - public static String localizeUploadDate(Context context, Date date) { + public static String localizeUploadDate(final Context context, final Date date) { return context.getString(R.string.upload_date_text, formatDate(date, context)); } - public static String localizeViewCount(Context context, long viewCount) { - return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, localizeNumber(context, viewCount)); + public static String localizeViewCount(final Context context, final long viewCount) { + return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, + localizeNumber(context, viewCount)); } - public static String localizeStreamCount(Context context, long streamCount) { - return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount)); + public static String localizeStreamCount(final Context context, final long streamCount) { + return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, + localizeNumber(context, streamCount)); } - public static String localizeWatchingCount(Context context, long watchingCount) { - return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, localizeNumber(context, watchingCount)); + public static String localizeWatchingCount(final Context context, final long watchingCount) { + return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, + localizeNumber(context, watchingCount)); } - public static String shortCount(Context context, long count) { + public static String shortCount(final Context context, final long count) { double value = (double) count; if (count >= 1000000000) { - return localizeNumber(context, round(value / 1000000000, 1)) + context.getString(R.string.short_billion); + return localizeNumber(context, round(value / 1000000000, 1)) + + context.getString(R.string.short_billion); } else if (count >= 1000000) { - return localizeNumber(context, round(value / 1000000, 1)) + context.getString(R.string.short_million); + return localizeNumber(context, round(value / 1000000, 1)) + + context.getString(R.string.short_million); } else if (count >= 1000) { - return localizeNumber(context, round(value / 1000, 1)) + context.getString(R.string.short_thousand); + return localizeNumber(context, round(value / 1000, 1)) + + context.getString(R.string.short_thousand); } else { return localizeNumber(context, value); } } - public static String listeningCount(Context context, long listeningCount) { - return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount, shortCount(context, listeningCount)); + public static String listeningCount(final Context context, final long listeningCount) { + return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount, + shortCount(context, listeningCount)); } - public static String shortWatchingCount(Context context, long watchingCount) { - return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, shortCount(context, watchingCount)); + public static String shortWatchingCount(final Context context, final long watchingCount) { + return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, + shortCount(context, watchingCount)); } - public static String shortViewCount(Context context, long viewCount) { - return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount)); + public static String shortViewCount(final Context context, final long viewCount) { + return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, + shortCount(context, viewCount)); } - public static String shortSubscriberCount(Context context, long subscriberCount) { - return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, shortCount(context, subscriberCount)); + public static String shortSubscriberCount(final Context context, final long subscriberCount) { + return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, + shortCount(context, subscriberCount)); } - private static String getQuantity(Context context, @PluralsRes int pluralId, @StringRes int zeroCaseStringId, long count, String formattedCount) { - if (count == 0) return context.getString(zeroCaseStringId); + private static String getQuantity(final Context context, @PluralsRes final int pluralId, + @StringRes final int zeroCaseStringId, final long count, + final String formattedCount) { + if (count == 0) { + return context.getString(zeroCaseStringId); + } - // As we use the already formatted count, is not the responsibility of this method handle long numbers - // (it probably will fall in the "other" category, or some language have some specific rule... then we have to change it) - int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count; + // As we use the already formatted count + // is not the responsibility of this method handle long numbers + // (it probably will fall in the "other" category, + // or some language have some specific rule... then we have to change it) + int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE + ? Integer.MIN_VALUE : (int) count; return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); } - public static String getDurationString(long duration) { + public static String getDurationString(final long duration) { + final String output; + + final long days = duration / (24 * 60 * 60L); /* greater than a day */ + final long hours = duration % (24 * 60 * 60L) / (60 * 60L); /* greater than an hour */ + final long minutes = duration % (24 * 60 * 60L) % (60 * 60L) / 60L; + final long seconds = duration % 60L; + if (duration < 0) { - duration = 0; - } - String output; - long days = duration / (24 * 60 * 60L); /* greater than a day */ - duration %= (24 * 60 * 60L); - long hours = duration / (60 * 60L); /* greater than an hour */ - duration %= (60 * 60L); - long minutes = duration / 60L; - long seconds = duration % 60L; - - //handle days - if (days > 0) { + output = "0:00"; + } else if (days > 0) { + //handle days output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds); } else if (hours > 0) { output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); @@ -219,22 +239,20 @@ public static String getDurationString(long duration) { *

The seconds will be converted to the closest whole time unit. *

For example, 60 seconds would give "1 minute", 119 would also give "1 minute". * - * @param context used to get plurals resources. + * @param context used to get plurals resources. * @param durationInSecs an amount of seconds. * @return duration in a human readable string. */ @NonNull - public static String localizeDuration(Context context, int durationInSecs) { + public static String localizeDuration(final Context context, final int durationInSecs) { if (durationInSecs < 0) { throw new IllegalArgumentException("duration can not be negative"); } - final int days = (int) (durationInSecs / (24 * 60 * 60L)); /* greater than a day */ - durationInSecs %= (24 * 60 * 60L); - final int hours = (int) (durationInSecs / (60 * 60L)); /* greater than an hour */ - durationInSecs %= (60 * 60L); - final int minutes = (int) (durationInSecs / 60L); - final int seconds = (int) (durationInSecs % 60L); + final int days = (int) (durationInSecs / (24 * 60 * 60L)); + final int hours = (int) (durationInSecs % (24 * 60 * 60L) / (60 * 60L)); + final int minutes = (int) (durationInSecs % (24 * 60 * 60L) % (60 * 60L) / 60L); + final int seconds = (int) (durationInSecs % (24 * 60 * 60L) % (60 * 60L) % 60L); final Resources resources = context.getResources(); @@ -253,7 +271,7 @@ public static String localizeDuration(Context context, int durationInSecs) { // Pretty Time //////////////////////////////////////////////////////////////////////////*/ - private static void initPrettyTime(Context context) { + private static void initPrettyTime(final Context context) { prettyTime = new PrettyTime(getAppLocale(context)); // Do not use decades as YouTube doesn't either. prettyTime.removeUnit(Decade.class); @@ -263,20 +281,20 @@ private static PrettyTime getPrettyTime() { return prettyTime; } - public static String relativeTime(Calendar calendarTime) { + public static String relativeTime(final Calendar calendarTime) { String time = getPrettyTime().formatUnrounded(calendarTime); return time.startsWith("-") ? time.substring(1) : time; //workaround fix for russian showing -1 day ago, -19hrs ago… } - private static void changeAppLanguage(Locale loc, Resources res) { + private static void changeAppLanguage(final Locale loc, final Resources res) { DisplayMetrics dm = res.getDisplayMetrics(); Configuration conf = res.getConfiguration(); conf.setLocale(loc); res.updateConfiguration(conf, dm); } - public static Locale getAppLocale(Context context) { + public static Locale getAppLocale(final Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String lang = prefs.getString(context.getString(R.string.app_language_key), "en"); Locale loc; @@ -295,11 +313,11 @@ public static Locale getAppLocale(Context context) { return loc; } - public static void assureCorrectAppLanguage(Context c) { + public static void assureCorrectAppLanguage(final Context c) { changeAppLanguage(getAppLocale(c), c.getResources()); } - private static double round(double value, int places) { + private static double round(final double value, final int places) { return new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue(); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index b6f73dac7..32c062571 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -8,14 +8,15 @@ import android.net.Uri; import android.os.Build; import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import androidx.appcompat.app.AlertDialog; -import android.util.Log; -import android.widget.Toast; import com.nostra13.universalimageloader.core.ImageLoader; @@ -58,10 +59,12 @@ import java.util.ArrayList; @SuppressWarnings({"unused", "WeakerAccess"}) -public class NavigationHelper { +public final class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; + private NavigationHelper() { } + /*////////////////////////////////////////////////////////////////////////// // Players //////////////////////////////////////////////////////////////////////////*/ @@ -75,8 +78,12 @@ public static Intent getPlayerIntent(@NonNull final Context context, Intent intent = new Intent(context, targetClazz); final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class); - if (cacheKey != null) intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey); - if (quality != null) intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality); + if (cacheKey != null) { + intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey); + } + if (quality != null) { + intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality); + } intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback); return intent; @@ -105,13 +112,11 @@ public static Intent getPlayerEnqueueIntent(@NonNull final Context context, public static Intent getPlayerIntent(@NonNull final Context context, @NonNull final Class targetClazz, @NonNull final PlayQueue playQueue, - final int repeatMode, - final float playbackSpeed, + final int repeatMode, final float playbackSpeed, final float playbackPitch, final boolean playbackSkipSilence, @Nullable final String playbackQuality, - final boolean resumePlayback, - final boolean startPaused, + final boolean resumePlayback, final boolean startPaused, final boolean isMuted) { return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) .putExtra(BasePlayer.REPEAT_MODE, repeatMode) @@ -122,50 +127,63 @@ public static Intent getPlayerIntent(@NonNull final Context context, .putExtra(BasePlayer.IS_MUTED, isMuted); } - public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { - final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback); + public static void playOnMainPlayer(final Context context, final PlayQueue queue, + final boolean resumePlayback) { + final Intent playerIntent + = getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback); playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(playerIntent); } - public static void playOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { + public static void playOnPopupPlayer(final Context context, final PlayQueue queue, + final boolean resumePlayback) { if (!PermissionHelper.isPopupEnabled(context)) { PermissionHelper.showPopupEnablementToast(context); return; } Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - startService(context, getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback)); + startService(context, + getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback)); } - public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { - Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show(); - startService(context, getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback)); + public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, + final boolean resumePlayback) { + Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT) + .show(); + startService(context, + getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback)); } - public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, + final boolean resumePlayback) { enqueueOnPopupPlayer(context, queue, false, resumePlayback); } - public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) { + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, + final boolean selectOnAppend, + final boolean resumePlayback) { if (!PermissionHelper.isPopupEnabled(context)) { PermissionHelper.showPopupEnablementToast(context); return; } Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); - startService(context, - getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend, resumePlayback)); + startService(context, getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, + selectOnAppend, resumePlayback)); } - public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { + public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, + final boolean resumePlayback) { enqueueOnBackgroundPlayer(context, queue, false, resumePlayback); } - public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) { + public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, + final boolean selectOnAppend, + final boolean resumePlayback) { Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); - startService(context, - getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend, resumePlayback)); + startService(context, getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, + selectOnAppend, resumePlayback)); } public static void startService(@NonNull final Context context, @NonNull final Intent intent) { @@ -180,7 +198,7 @@ public static void startService(@NonNull final Context context, @NonNull final I // External Players //////////////////////////////////////////////////////////////////////////*/ - public static void playOnExternalAudioPlayer(Context context, StreamInfo info) { + public static void playOnExternalAudioPlayer(final Context context, final StreamInfo info) { final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); if (index == -1) { @@ -192,8 +210,9 @@ public static void playOnExternalAudioPlayer(Context context, StreamInfo info) { playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream); } - public static void playOnExternalVideoPlayer(Context context, StreamInfo info) { - ArrayList videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false)); + public static void playOnExternalVideoPlayer(final Context context, final StreamInfo info) { + ArrayList videoStreamsList = new ArrayList<>( + ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false)); int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList); if (index == -1) { @@ -205,7 +224,8 @@ public static void playOnExternalVideoPlayer(Context context, StreamInfo info) { playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream); } - public static void playOnExternalPlayer(Context context, String name, String artist, Stream stream) { + public static void playOnExternalPlayer(final Context context, final String name, + final String artist, final Stream stream) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType()); @@ -217,7 +237,7 @@ public static void playOnExternalPlayer(Context context, String name, String art resolveActivityOrAskToInstall(context, intent); } - public static void resolveActivityOrAskToInstall(Context context, Intent intent) { + public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) { if (intent.resolveActivity(context.getPackageManager()) != null) { context.startActivity(intent); } else { @@ -230,9 +250,12 @@ public static void resolveActivityOrAskToInstall(Context context, Intent intent) i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url))); context.startActivity(i); }) - .setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) + .setNegativeButton(R.string.cancel, (dialog, which) + -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) .show(); - //Log.e("NavigationHelper", "Either no Streaming player for audio was installed, or something important crashed:"); +// Log.e("NavigationHelper", +// "Either no Streaming player for audio was installed, " +// + "or something important crashed:"); } else { Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show(); } @@ -244,19 +267,22 @@ public static void resolveActivityOrAskToInstall(Context context, Intent intent) //////////////////////////////////////////////////////////////////////////*/ @SuppressLint("CommitTransaction") - private static FragmentTransaction defaultTransaction(FragmentManager fragmentManager) { + private static FragmentTransaction defaultTransaction(final FragmentManager fragmentManager) { return fragmentManager.beginTransaction() - .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out); + .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, + R.animator.custom_fade_in, R.animator.custom_fade_out); } - public static void gotoMainFragment(FragmentManager fragmentManager) { + public static void gotoMainFragment(final FragmentManager fragmentManager) { ImageLoader.getInstance().clearMemoryCache(); boolean popped = fragmentManager.popBackStackImmediate(MAIN_FRAGMENT_TAG, 0); - if (!popped) openMainFragment(fragmentManager); + if (!popped) { + openMainFragment(fragmentManager); + } } - public static void openMainFragment(FragmentManager fragmentManager) { + public static void openMainFragment(final FragmentManager fragmentManager) { InfoCache.getInstance().trimCache(); fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); @@ -266,41 +292,45 @@ public static void openMainFragment(FragmentManager fragmentManager) { .commit(); } - public static boolean tryGotoSearchFragment(FragmentManager fragmentManager) { + public static boolean tryGotoSearchFragment(final FragmentManager fragmentManager) { if (MainActivity.DEBUG) { for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) { - Log.d("NavigationHelper", "tryGoToSearchFragment() [" + i + "] = [" + fragmentManager.getBackStackEntryAt(i) + "]"); + Log.d("NavigationHelper", "tryGoToSearchFragment() [" + i + "]" + + " = [" + fragmentManager.getBackStackEntryAt(i) + "]"); } } return fragmentManager.popBackStackImmediate(SEARCH_FRAGMENT_TAG, 0); } - public static void openSearchFragment(FragmentManager fragmentManager, - int serviceId, - String searchString) { + public static void openSearchFragment(final FragmentManager fragmentManager, + final int serviceId, final String searchString) { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, searchString)) .addToBackStack(SEARCH_FRAGMENT_TAG) .commit(); } - public static void openVideoDetailFragment(FragmentManager fragmentManager, int serviceId, String url, String title) { + public static void openVideoDetailFragment(final FragmentManager fragmentManager, + final int serviceId, final String url, + final String title) { openVideoDetailFragment(fragmentManager, serviceId, url, title, false); } - public static void openVideoDetailFragment(FragmentManager fragmentManager, int serviceId, String url, String title, boolean autoPlay) { + public static void openVideoDetailFragment(final FragmentManager fragmentManager, + final int serviceId, final String url, + final String name, final boolean autoPlay) { Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_holder); - if (title == null) title = ""; if (fragment instanceof VideoDetailFragment && fragment.isVisible()) { VideoDetailFragment detailFragment = (VideoDetailFragment) fragment; detailFragment.setAutoplay(autoPlay); - detailFragment.selectAndLoadVideo(serviceId, url, title); + detailFragment.selectAndLoadVideo(serviceId, url, name == null ? "" : name); return; } - VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, title); + VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, + name == null ? "" : name); instance.setAutoplay(autoPlay); defaultTransaction(fragmentManager) @@ -309,89 +339,89 @@ public static void openVideoDetailFragment(FragmentManager fragmentManager, int .commit(); } - public static void openChannelFragment( - FragmentManager fragmentManager, - int serviceId, - String url, - String name) { - if (name == null) name = ""; + public static void openChannelFragment(final FragmentManager fragmentManager, + final int serviceId, final String url, + final String name) { defaultTransaction(fragmentManager) - .replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name)) + .replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, + name == null ? "" : name)) .addToBackStack(null) .commit(); } - public static void openCommentsFragment( - FragmentManager fragmentManager, - int serviceId, - String url, - String name) { - if (name == null) name = ""; - fragmentManager.beginTransaction().setCustomAnimations(R.anim.switch_service_in, R.anim.switch_service_out) - .replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url, name)) + public static void openCommentsFragment(final FragmentManager fragmentManager, + final int serviceId, final String url, + final String name) { + fragmentManager.beginTransaction() + .setCustomAnimations(R.anim.switch_service_in, R.anim.switch_service_out) + .replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url, + name == null ? "" : name)) .addToBackStack(null) .commit(); } - public static void openPlaylistFragment(FragmentManager fragmentManager, - int serviceId, - String url, - String name) { - if (name == null) name = ""; + public static void openPlaylistFragment(final FragmentManager fragmentManager, + final int serviceId, final String url, + final String name) { defaultTransaction(fragmentManager) - .replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url, name)) + .replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url, + name == null ? "" : name)) .addToBackStack(null) .commit(); } - public static void openFeedFragment(FragmentManager fragmentManager) { + public static void openFeedFragment(final FragmentManager fragmentManager) { openFeedFragment(fragmentManager, FeedGroupEntity.GROUP_ALL_ID, null); } - public static void openFeedFragment(FragmentManager fragmentManager, long groupId, @Nullable String groupName) { + public static void openFeedFragment(final FragmentManager fragmentManager, final long groupId, + @Nullable final String groupName) { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, FeedFragment.newInstance(groupId, groupName)) .addToBackStack(null) .commit(); } - public static void openBookmarksFragment(FragmentManager fragmentManager) { + public static void openBookmarksFragment(final FragmentManager fragmentManager) { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, new BookmarkFragment()) .addToBackStack(null) .commit(); } - public static void openSubscriptionFragment(FragmentManager fragmentManager) { + public static void openSubscriptionFragment(final FragmentManager fragmentManager) { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, new SubscriptionFragment()) .addToBackStack(null) .commit(); } - public static void openKioskFragment(FragmentManager fragmentManager, int serviceId, String kioskId) throws ExtractionException { + public static void openKioskFragment(final FragmentManager fragmentManager, final int serviceId, + final String kioskId) throws ExtractionException { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, KioskFragment.getInstance(serviceId, kioskId)) .addToBackStack(null) .commit(); } - public static void openLocalPlaylistFragment(FragmentManager fragmentManager, long playlistId, String name) { - if (name == null) name = ""; + public static void openLocalPlaylistFragment(final FragmentManager fragmentManager, + final long playlistId, final String name) { defaultTransaction(fragmentManager) - .replace(R.id.fragment_holder, LocalPlaylistFragment.getInstance(playlistId, name)) + .replace(R.id.fragment_holder, LocalPlaylistFragment.getInstance(playlistId, + name == null ? "" : name)) .addToBackStack(null) .commit(); } - public static void openStatisticFragment(FragmentManager fragmentManager) { + public static void openStatisticFragment(final FragmentManager fragmentManager) { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, new StatisticsPlaylistFragment()) .addToBackStack(null) .commit(); } - public static void openSubscriptionsImportFragment(FragmentManager fragmentManager, int serviceId) { + public static void openSubscriptionsImportFragment(final FragmentManager fragmentManager, + final int serviceId) { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, SubscriptionsImportFragment.getInstance(serviceId)) .addToBackStack(null) @@ -402,7 +432,8 @@ public static void openSubscriptionsImportFragment(FragmentManager fragmentManag // Through Intents //////////////////////////////////////////////////////////////////////////*/ - public static void openSearch(Context context, int serviceId, String searchString) { + public static void openSearch(final Context context, final int serviceId, + final String searchString) { Intent mIntent = new Intent(context, MainActivity.class); mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId); mIntent.putExtra(Constants.KEY_SEARCH_STRING, searchString); @@ -410,52 +441,62 @@ public static void openSearch(Context context, int serviceId, String searchStrin context.startActivity(mIntent); } - public static void openChannel(Context context, int serviceId, String url) { + public static void openChannel(final Context context, final int serviceId, final String url) { openChannel(context, serviceId, url, null); } - public static void openChannel(Context context, int serviceId, String url, String name) { - Intent openIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL); - if (name != null && !name.isEmpty()) openIntent.putExtra(Constants.KEY_TITLE, name); + public static void openChannel(final Context context, final int serviceId, + final String url, final String name) { + Intent openIntent = getOpenIntent(context, url, serviceId, + StreamingService.LinkType.CHANNEL); + if (name != null && !name.isEmpty()) { + openIntent.putExtra(Constants.KEY_TITLE, name); + } context.startActivity(openIntent); } - public static void openVideoDetail(Context context, int serviceId, String url) { + public static void openVideoDetail(final Context context, final int serviceId, + final String url) { openVideoDetail(context, serviceId, url, null); } - public static void openVideoDetail(Context context, int serviceId, String url, String title) { - Intent openIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.STREAM); - if (title != null && !title.isEmpty()) openIntent.putExtra(Constants.KEY_TITLE, title); + public static void openVideoDetail(final Context context, final int serviceId, + final String url, final String title) { + Intent openIntent = getOpenIntent(context, url, serviceId, + StreamingService.LinkType.STREAM); + if (title != null && !title.isEmpty()) { + openIntent.putExtra(Constants.KEY_TITLE, title); + } context.startActivity(openIntent); } - public static void openMainActivity(Context context) { + public static void openMainActivity(final Context context) { Intent mIntent = new Intent(context, MainActivity.class); mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(mIntent); } - public static void openRouterActivity(Context context, String url) { + public static void openRouterActivity(final Context context, final String url) { Intent mIntent = new Intent(context, RouterActivity.class); mIntent.setData(Uri.parse(url)); - mIntent.putExtra(RouterActivity.internalRouteKey, true); + mIntent.putExtra(RouterActivity.INTERNAL_ROUTE_KEY, true); context.startActivity(mIntent); } - public static void openAbout(Context context) { + public static void openAbout(final Context context) { Intent intent = new Intent(context, AboutActivity.class); context.startActivity(intent); } - public static void openSettings(Context context) { + public static void openSettings(final Context context) { Intent intent = new Intent(context, SettingsActivity.class); context.startActivity(intent); } - public static boolean openDownloads(Activity activity) { - if (!PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOADS_REQUEST_CODE)) { + public static boolean openDownloads(final Activity activity) { + if (!PermissionHelper.checkStoragePermissions( + activity, PermissionHelper.DOWNLOADS_REQUEST_CODE)) { return false; } Intent intent = new Intent(activity, DownloadActivity.class); @@ -483,7 +524,8 @@ private static Intent getServicePlayerActivityIntent(final Context context, // Link handling //////////////////////////////////////////////////////////////////////////*/ - private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) { + private static Intent getOpenIntent(final Context context, final String url, + final int serviceId, final StreamingService.LinkType type) { Intent mIntent = new Intent(context, MainActivity.class); mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId); mIntent.putExtra(Constants.KEY_URL, url); @@ -491,45 +533,46 @@ private static Intent getOpenIntent(Context context, String url, int serviceId, return mIntent; } - public static Intent getIntentByLink(Context context, String url) throws ExtractionException { + public static Intent getIntentByLink(final Context context, final String url) + throws ExtractionException { return getIntentByLink(context, NewPipe.getServiceByUrl(url), url); } - public static Intent getIntentByLink(Context context, StreamingService service, String url) throws ExtractionException { + public static Intent getIntentByLink(final Context context, final StreamingService service, + final String url) throws ExtractionException { StreamingService.LinkType linkType = service.getLinkTypeByUrl(url); if (linkType == StreamingService.LinkType.NONE) { - throw new ExtractionException("Url not known to service. service=" + service + " url=" + url); + throw new ExtractionException("Url not known to service. service=" + service + + " url=" + url); } Intent rIntent = getOpenIntent(context, url, service.getServiceId(), linkType); - switch (linkType) { - case STREAM: - rIntent.putExtra(VideoDetailFragment.AUTO_PLAY, - PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.autoplay_through_intent_key), false)); - break; + if (linkType == StreamingService.LinkType.STREAM) { + rIntent.putExtra(VideoDetailFragment.AUTO_PLAY, + PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + context.getString(R.string.autoplay_through_intent_key), false)); } return rIntent; } - private static Uri openMarketUrl(String packageName) { + private static Uri openMarketUrl(final String packageName) { return Uri.parse("market://details") .buildUpon() .appendQueryParameter("id", packageName) .build(); } - private static Uri getGooglePlayUrl(String packageName) { + private static Uri getGooglePlayUrl(final String packageName) { return Uri.parse("https://play.google.com/store/apps/details") .buildUpon() .appendQueryParameter("id", packageName) .build(); } - private static void installApp(Context context, String packageName) { + private static void installApp(final Context context, final String packageName) { try { // Try market:// scheme context.startActivity(new Intent(Intent.ACTION_VIEW, openMarketUrl(packageName))); @@ -540,25 +583,26 @@ private static void installApp(Context context, String packageName) { } /** - * Start an activity to install Kore + * Start an activity to install Kore. + * * @param context the context */ - public static void installKore(Context context) { + public static void installKore(final Context context) { installApp(context, context.getString(R.string.kore_package)); } /** - * Start Kore app to show a video on Kodi - * + * Start Kore app to show a video on Kodi. + *

* For a list of supported urls see the * - * Kore source code + * Kore source code * . * - * @param context the context to use + * @param context the context to use * @param videoURL the url to the video */ - public static void playWithKore(Context context, Uri videoURL) { + public static void playWithKore(final Context context, final Uri videoURL) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setPackage(context.getString(R.string.kore_package)); intent.setData(videoURL); diff --git a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java index 18f4f67f4..5f44cab8b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java +++ b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java @@ -6,11 +6,11 @@ public abstract class OnClickGesture { public abstract void selected(T selectedItem); - public void held(T selectedItem) { + public void held(final T selectedItem) { // Optional gesture } - public void drag(T selectedItem, RecyclerView.ViewHolder viewHolder) { + public void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { // Optional gesture } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index 0d695e275..e89cbf5db 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -19,10 +19,12 @@ import java.util.Collections; import java.util.List; -public class PeertubeHelper { +public final class PeertubeHelper { + private PeertubeHelper() { } - public static List getInstanceList(Context context) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + public static List getInstanceList(final Context context) { + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context); String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); if (null == savedJson) { @@ -47,8 +49,10 @@ public static List getInstanceList(Context context) { } - public static PeertubeInstance selectInstance(PeertubeInstance instance, Context context) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + public static PeertubeInstance selectInstance(final PeertubeInstance instance, + final Context context) { + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context); String selectedInstanceKey = context.getString(R.string.peertube_selected_instance_key); JsonStringWriter jsonWriter = JsonWriter.string().object(); jsonWriter.value("name", instance.getName()); @@ -59,7 +63,7 @@ public static PeertubeInstance selectInstance(PeertubeInstance instance, Context return instance; } - public static PeertubeInstance getCurrentInstance(){ + public static PeertubeInstance getCurrentInstance() { return ServiceList.PeerTube.getInstance(); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index f32bb6587..6530243ef 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -8,28 +8,34 @@ import android.net.Uri; import android.os.Build; import android.provider.Settings; -import androidx.annotation.RequiresApi; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; import android.view.Gravity; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + import org.schabi.newpipe.R; -public class PermissionHelper { +public final class PermissionHelper { public static final int DOWNLOAD_DIALOG_REQUEST_CODE = 778; public static final int DOWNLOADS_REQUEST_CODE = 777; - public static boolean checkStoragePermissions(Activity activity, int requestCode) { + private PermissionHelper() { } + + public static boolean checkStoragePermissions(final Activity activity, final int requestCode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - if (!checkReadStoragePermissions(activity, requestCode)) return false; + if (!checkReadStoragePermissions(activity, requestCode)) { + return false; + } } return checkWriteStoragePermissions(activity, requestCode); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) - public static boolean checkReadStoragePermissions(Activity activity, int requestCode) { + public static boolean checkReadStoragePermissions(final Activity activity, + final int requestCode) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(activity, @@ -44,7 +50,8 @@ public static boolean checkReadStoragePermissions(Activity activity, int request } - public static boolean checkWriteStoragePermissions(Activity activity, int requestCode) { + public static boolean checkWriteStoragePermissions(final Activity activity, + final int requestCode) { // Here, thisActivity is the current activity if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) @@ -75,34 +82,45 @@ public static boolean checkWriteStoragePermissions(Activity activity, int reques /** - * In order to be able to draw over other apps, the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted. + * In order to be able to draw over other apps, + * the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted. *

- * On < API 23 (MarshMallow) the permission was granted when the user installed the application (via AndroidManifest), + * On < API 23 (MarshMallow) the permission was granted + * when the user installed the application (via AndroidManifest), * on > 23, however, it have to start a activity asking the user if he agrees. + *

*

- * This method just return if the app has permission to draw over other apps, and if it doesn't, it will try to get the permission. + * This method just return if the app has permission to draw over other apps, + * and if it doesn't, it will try to get the permission. + *

* - * @return returns {@link Settings#canDrawOverlays(Context)} + * @param context {@link Context} + * @return {@link Settings#canDrawOverlays(Context)} **/ @RequiresApi(api = Build.VERSION_CODES.M) - public static boolean checkSystemAlertWindowPermission(Context context) { + public static boolean checkSystemAlertWindowPermission(final Context context) { if (!Settings.canDrawOverlays(context)) { - Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName())); + Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.getPackageName())); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); return false; - } else return true; + } else { + return true; + } } - public static boolean isPopupEnabled(Context context) { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || - PermissionHelper.checkSystemAlertWindowPermission(context); + public static boolean isPopupEnabled(final Context context) { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M + || PermissionHelper.checkSystemAlertWindowPermission(context); } - public static void showPopupEnablementToast(Context context) { + public static void showPopupEnablementToast(final Context context) { Toast toast = Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); TextView messageView = toast.getView().findViewById(android.R.id.message); - if (messageView != null) messageView.setGravity(Gravity.CENTER); + if (messageView != null) { + messageView.setGravity(Gravity.CENTER); + } toast.show(); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java b/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java index 6de663c13..ce642da5e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java +++ b/app/src/main/java/org/schabi/newpipe/util/RelatedStreamInfo.java @@ -14,15 +14,18 @@ public class RelatedStreamInfo extends ListInfo { private StreamInfoItem nextStream; - public RelatedStreamInfo(int serviceId, ListLinkHandler listUrlIdHandler, String name) { + public RelatedStreamInfo(final int serviceId, final ListLinkHandler listUrlIdHandler, + final String name) { super(serviceId, listUrlIdHandler, name); } - public static RelatedStreamInfo getInfo(StreamInfo info) { - ListLinkHandler handler = new ListLinkHandler(info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null); - RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(info.getServiceId(), handler, info.getName()); + public static RelatedStreamInfo getInfo(final StreamInfo info) { + ListLinkHandler handler = new ListLinkHandler( + info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null); + RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo( + info.getServiceId(), handler, info.getName()); List streams = new ArrayList<>(); - if(info.getNextVideo() != null){ + if (info.getNextVideo() != null) { streams.add(info.getNextVideo()); } streams.addAll(info.getRelatedStreams()); @@ -35,7 +38,7 @@ public StreamInfoItem getNextStream() { return nextStream; } - public void setNextStream(StreamInfoItem nextStream) { + public void setNextStream(final StreamInfoItem nextStream) { this.nextStream = nextStream; } } diff --git a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java index ab58bc917..081d981a1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java @@ -14,28 +14,23 @@ public class SecondaryStreamHelper { private final int position; private final StreamSizeWrapper streams; - public SecondaryStreamHelper(StreamSizeWrapper streams, T selectedStream) { + public SecondaryStreamHelper(final StreamSizeWrapper streams, final T selectedStream) { this.streams = streams; this.position = streams.getStreamsList().indexOf(selectedStream); - if (this.position < 0) throw new RuntimeException("selected stream not found"); - } - - public T getStream() { - return streams.getStreamsList().get(position); - } - - public long getSizeInBytes() { - return streams.getSizeInBytes(position); + if (this.position < 0) { + throw new RuntimeException("selected stream not found"); + } } /** - * find the correct audio stream for the desired video stream + * Find the correct audio stream for the desired video stream. * * @param audioStreams list of audio streams * @param videoStream desired video ONLY stream * @return selected audio stream or null if a candidate was not found */ - public static AudioStream getAudioStreamFor(@NonNull List audioStreams, @NonNull VideoStream videoStream) { + public static AudioStream getAudioStreamFor(@NonNull final List audioStreams, + @NonNull final VideoStream videoStream) { switch (videoStream.getFormat()) { case WEBM: case MPEG_4:// ¿is mpeg-4 DASH? @@ -52,7 +47,9 @@ public static AudioStream getAudioStreamFor(@NonNull List audioStre } } - if (m4v) return null; + if (m4v) { + return null; + } // retry, but this time in reverse order for (int i = audioStreams.size() - 1; i >= 0; i--) { @@ -64,4 +61,12 @@ public static AudioStream getAudioStreamFor(@NonNull List audioStre return null; } + + public T getStream() { + return streams.getStreamsList().get(position); + } + + public long getSizeInBytes() { + return streams.getSizeInBytes(position); + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java b/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java index 7680daf48..9d97e013a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java @@ -1,9 +1,10 @@ package org.schabi.newpipe.util; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.collection.LruCache; -import android.util.Log; import org.schabi.newpipe.MainActivity; @@ -14,53 +15,58 @@ import java.io.Serializable; import java.util.UUID; -public class SerializedCache { +public final class SerializedCache { private static final boolean DEBUG = MainActivity.DEBUG; - private final String TAG = getClass().getSimpleName(); - - private static final SerializedCache instance = new SerializedCache(); + private static final SerializedCache INSTANCE = new SerializedCache(); private static final int MAX_ITEMS_ON_CACHE = 5; - - private static final LruCache lruCache = + private static final LruCache LRU_CACHE = new LruCache<>(MAX_ITEMS_ON_CACHE); + private static final String TAG = "SerializedCache"; private SerializedCache() { //no instance } public static SerializedCache getInstance() { - return instance; + return INSTANCE; } @Nullable public T take(@NonNull final String key, @NonNull final Class type) { - if (DEBUG) Log.d(TAG, "take() called with: key = [" + key + "]"); - synchronized (lruCache) { - return lruCache.get(key) != null ? getItem(lruCache.remove(key), type) : null; + if (DEBUG) { + Log.d(TAG, "take() called with: key = [" + key + "]"); + } + synchronized (LRU_CACHE) { + return LRU_CACHE.get(key) != null ? getItem(LRU_CACHE.remove(key), type) : null; } } @Nullable public T get(@NonNull final String key, @NonNull final Class type) { - if (DEBUG) Log.d(TAG, "get() called with: key = [" + key + "]"); - synchronized (lruCache) { - final CacheData data = lruCache.get(key); + if (DEBUG) { + Log.d(TAG, "get() called with: key = [" + key + "]"); + } + synchronized (LRU_CACHE) { + final CacheData data = LRU_CACHE.get(key); return data != null ? getItem(data, type) : null; } } @Nullable - public String put(@NonNull T item, @NonNull final Class type) { + public String put(@NonNull final T item, + @NonNull final Class type) { final String key = UUID.randomUUID().toString(); return put(key, item, type) ? key : null; } - public boolean put(@NonNull final String key, @NonNull T item, + public boolean put(@NonNull final String key, @NonNull final T item, @NonNull final Class type) { - if (DEBUG) Log.d(TAG, "put() called with: key = [" + key + "], item = [" + item + "]"); - synchronized (lruCache) { + if (DEBUG) { + Log.d(TAG, "put() called with: key = [" + key + "], item = [" + item + "]"); + } + synchronized (LRU_CACHE) { try { - lruCache.put(key, new CacheData<>(clone(item, type), type)); + LRU_CACHE.put(key, new CacheData<>(clone(item, type), type)); return true; } catch (final Exception error) { Log.e(TAG, "Serialization failed for: ", error); @@ -70,15 +76,17 @@ public boolean put(@NonNull final String key, @NonNull } public void clear() { - if (DEBUG) Log.d(TAG, "clear() called"); - synchronized (lruCache) { - lruCache.evictAll(); + if (DEBUG) { + Log.d(TAG, "clear() called"); + } + synchronized (LRU_CACHE) { + LRU_CACHE.evictAll(); } } public long size() { - synchronized (lruCache) { - return lruCache.size(); + synchronized (LRU_CACHE) { + return LRU_CACHE.size(); } } @@ -88,10 +96,10 @@ private T getItem(@NonNull final CacheData data, @NonNull final Class typ } @NonNull - private T clone(@NonNull T item, + private T clone(@NonNull final T item, @NonNull final Class type) throws Exception { final ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream(); - try (final ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) { + try (ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) { objectOutput.writeObject(item); objectOutput.flush(); } @@ -100,11 +108,11 @@ private T clone(@NonNull T item, return type.cast(clone); } - final private static class CacheData { + private static final class CacheData { private final T item; private final Class type; - private CacheData(@NonNull final T item, @NonNull Class type) { + private CacheData(@NonNull final T item, @NonNull final Class type) { this.item = item; this.type = type; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 6726e4cfc..cea624663 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -22,11 +22,13 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; -public class ServiceHelper { +public final class ServiceHelper { private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; + private ServiceHelper() { } + @DrawableRes - public static int getIcon(int serviceId) { + public static int getIcon(final int serviceId) { switch (serviceId) { case 0: return R.drawable.place_holder_youtube; @@ -41,27 +43,37 @@ public static int getIcon(int serviceId) { } } - public static String getTranslatedFilterString(String filter, Context c) { + public static String getTranslatedFilterString(final String filter, final Context c) { switch (filter) { - case "all": return c.getString(R.string.all); - case "videos": return c.getString(R.string.videos_string); - case "channels": return c.getString(R.string.channels); - case "playlists": return c.getString(R.string.playlists); - case "tracks": return c.getString(R.string.tracks); - case "users": return c.getString(R.string.users); - case "conferences" : return c.getString(R.string.conferences); - case "events" : return c.getString(R.string.events); - default: return filter; + case "all": + return c.getString(R.string.all); + case "videos": + return c.getString(R.string.videos_string); + case "channels": + return c.getString(R.string.channels); + case "playlists": + return c.getString(R.string.playlists); + case "tracks": + return c.getString(R.string.tracks); + case "users": + return c.getString(R.string.users); + case "conferences": + return c.getString(R.string.conferences); + case "events": + return c.getString(R.string.events); + default: + return filter; } } /** * Get a resource string with instructions for importing subscriptions for each service. * + * @param serviceId service to get the instructions for * @return the string resource containing the instructions or -1 if the service don't support it */ @StringRes - public static int getImportInstructions(int serviceId) { + public static int getImportInstructions(final int serviceId) { switch (serviceId) { case 0: return R.string.import_youtube_instructions; @@ -76,10 +88,11 @@ public static int getImportInstructions(int serviceId) { * For services that support importing from a channel url, return a hint that will * be used in the EditText that the user will type in his channel url. * + * @param serviceId service to get the hint for * @return the hint's string resource or -1 if the service don't support it */ @StringRes - public static int getImportInstructionsHint(int serviceId) { + public static int getImportInstructionsHint(final int serviceId) { switch (serviceId) { case 1: return R.string.import_soundcloud_instructions_hint; @@ -88,10 +101,10 @@ public static int getImportInstructionsHint(int serviceId) { } } - public static int getSelectedServiceId(Context context) { - + public static int getSelectedServiceId(final Context context) { final String serviceName = PreferenceManager.getDefaultSharedPreferences(context) - .getString(context.getString(R.string.current_service_key), context.getString(R.string.default_service_value)); + .getString(context.getString(R.string.current_service_key), + context.getString(R.string.default_service_value)); int serviceId; try { @@ -103,7 +116,7 @@ public static int getSelectedServiceId(Context context) { return serviceId; } - public static void setSelectedServiceId(Context context, int serviceId) { + public static void setSelectedServiceId(final Context context, final int serviceId) { String serviceName; try { serviceName = NewPipe.getService(serviceId).getServiceInfo().getName(); @@ -114,14 +127,18 @@ public static void setSelectedServiceId(Context context, int serviceId) { setSelectedServicePreferences(context, serviceName); } - public static void setSelectedServiceId(Context context, String serviceName) { + public static void setSelectedServiceId(final Context context, final String serviceName) { int serviceId = NewPipe.getIdOfService(serviceName); - if (serviceId == -1) serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName(); - - setSelectedServicePreferences(context, serviceName); + if (serviceId == -1) { + setSelectedServicePreferences(context, + DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName()); + } else { + setSelectedServicePreferences(context, serviceName); + } } - private static void setSelectedServicePreferences(Context context, String serviceName) { + private static void setSelectedServicePreferences(final Context context, + final String serviceName) { PreferenceManager.getDefaultSharedPreferences(context).edit(). putString(context.getString(R.string.current_service_key), serviceName).apply(); } @@ -136,15 +153,19 @@ public static long getCacheExpirationMillis(final int serviceId) { public static boolean isBeta(final StreamingService s) { switch (s.getServiceInfo().getName()) { - case "YouTube": return false; - default: return true; + case "YouTube": + return false; + default: + return true; } } - public static void initService(Context context, int serviceId) { + public static void initService(final Context context, final int serviceId) { if (serviceId == ServiceList.PeerTube.getServiceId()) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - String json = sharedPreferences.getString(context.getString(R.string.peertube_selected_instance_key), null); + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context); + String json = sharedPreferences.getString(context.getString( + R.string.peertube_selected_instance_key), null); if (null == json) { return; } @@ -162,7 +183,7 @@ public static void initService(Context context, int serviceId) { } } - public static void initServices(Context context) { + public static void initServices(final Context context) { for (StreamingService s : ServiceList.all()) { initService(context, s.getServiceId()); } diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java index c5c78a726..8cefa08eb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java @@ -6,17 +6,21 @@ import org.schabi.newpipe.R; -public class ShareUtils { - public static void openUrlInBrowser(Context context, String url) { +public final class ShareUtils { + private ShareUtils() { } + + public static void openUrlInBrowser(final Context context, final String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title))); + context.startActivity(Intent.createChooser( + intent, context.getString(R.string.share_dialog_title))); } - public static void shareUrl(Context context, String subject, String url) { + public static void shareUrl(final Context context, final String subject, final String url) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_TEXT, url); - context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title))); + context.startActivity(Intent.createChooser( + intent, context.getString(R.string.share_dialog_title))); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java b/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java index efec1abb0..c6191fcc2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java +++ b/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java @@ -3,15 +3,21 @@ public interface SliderStrategy { /** * Converts from zeroed double with a minimum offset to the nearest rounded slider - * equivalent integer - * */ - int progressOf(final double value); + * equivalent integer. + * + * @param value the value to convert + * @return the converted value + */ + int progressOf(double value); /** * Converts from slider integer value to an equivalent double value with a given - * minimum offset - * */ - double valueOf(final int progress); + * minimum offset. + * + * @param progress the value to convert + * @return the converted value + */ + double valueOf(int progress); // TODO: also implement linear strategy when needed @@ -27,18 +33,19 @@ final class Quadratic implements SliderStrategy { * progress is from the center of the slider. The further away from the center, * the faster the interpreted value changes, and vice versa. * - * @param minimum the minimum value of the interpreted value of the slider. - * @param maximum the maximum value of the interpreted value of the slider. - * @param center center of the interpreted value between the minimum and maximum, which - * will be used as the center value on the slider progress. Doesn't need - * to be the average of the minimum and maximum values, but must be in - * between the two. + * @param minimum the minimum value of the interpreted value of the slider. + * @param maximum the maximum value of the interpreted value of the slider. + * @param center center of the interpreted value between the minimum and maximum, which + * will be used as the center value on the slider progress. Doesn't need + * to be the average of the minimum and maximum values, but must be in + * between the two. * @param maxProgress the maximum possible progress of the slider, this is the * value that is shown for the UI and controls the granularity of * the slider. Should be as large as possible to avoid floating * point round-off error. Using odd number is recommended. - * */ - public Quadratic(double minimum, double maximum, double center, int maxProgress) { + */ + public Quadratic(final double minimum, final double maximum, final double center, + final int maxProgress) { if (center < minimum || center > maximum) { throw new IllegalArgumentException("Center must be in between minimum and maximum"); } @@ -51,18 +58,17 @@ public Quadratic(double minimum, double maximum, double center, int maxProgress) } @Override - public int progressOf(double value) { + public int progressOf(final double value) { final double difference = value - center; - final double root = difference >= 0 ? - Math.sqrt(difference / rightGap) : - -Math.sqrt(Math.abs(difference / leftGap)); + final double root = difference >= 0 ? Math.sqrt(difference / rightGap) + : -Math.sqrt(Math.abs(difference / leftGap)); final double offset = Math.round(root * centerProgress); return (int) (centerProgress + offset); } @Override - public double valueOf(int progress) { + public double valueOf(final int progress) { final int offset = progress - centerProgress; final double square = Math.pow(((double) offset) / ((double) centerProgress), 2); final double difference = square * (offset >= 0 ? rightGap : leftGap); diff --git a/app/src/main/java/org/schabi/newpipe/util/SparseArrayUtils.java b/app/src/main/java/org/schabi/newpipe/util/SparseArrayUtils.java deleted file mode 100644 index d17c9aa42..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/SparseArrayUtils.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.schabi.newpipe.util; - -import android.util.SparseArray; - -public abstract class SparseArrayUtils { - - public static void shiftItemsDown(SparseArray sparseArray, int lower, int upper) { - for (int i = lower + 1; i <= upper; i++) { - final T o = sparseArray.get(i); - sparseArray.put(i - 1, o); - sparseArray.remove(i); - } - } - - public static void shiftItemsUp(SparseArray sparseArray, int lower, int upper) { - for (int i = upper - 1; i >= lower; i--) { - final T o = sparseArray.get(i); - sparseArray.put(i + 1, o); - sparseArray.remove(i); - } - } - - public static int[] getKeys(SparseArray sparseArray) { - final int[] result = new int[sparseArray.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = sparseArray.keyAt(i); - } - return result; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java index fffa9e99f..2a1dff5c9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java +++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java @@ -24,11 +24,12 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.MainActivity; @@ -44,14 +45,15 @@ import java.util.concurrent.ConcurrentHashMap; /** - * A way to save state to disk or in a in-memory map if it's just changing configurations (i.e. rotating the phone). + * A way to save state to disk or in a in-memory map + * if it's just changing configurations (i.e. rotating the phone). */ -public class StateSaver { - private static final ConcurrentHashMap> stateObjectsHolder = new ConcurrentHashMap<>(); +public final class StateSaver { + public static final String KEY_SAVED_STATE = "key_saved_state"; + private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER + = new ConcurrentHashMap<>(); private static final String TAG = "StateSaver"; private static final String CACHE_DIR_NAME = "state_cache"; - - public static final String KEY_SAVED_STATE = "key_saved_state"; private static String cacheDirPath; private StateSaver() { @@ -59,78 +61,70 @@ private StateSaver() { } /** - * Initialize the StateSaver, usually you want to call this in the Application class + * Initialize the StateSaver, usually you want to call this in the Application class. * * @param context used to get the available cache dir */ - public static void init(Context context) { + public static void init(final Context context) { File externalCacheDir = context.getExternalCacheDir(); - if (externalCacheDir != null) cacheDirPath = externalCacheDir.getAbsolutePath(); - if (TextUtils.isEmpty(cacheDirPath)) cacheDirPath = context.getCacheDir().getAbsolutePath(); - } - - /** - * Used for describe how to save/read the objects. - *

- * Queue was chosen by its FIFO property. - */ - public interface WriteRead { - /** - * Generate a changing suffix that will name the cache file, - * and be used to identify if it changed (thus reducing useless reading/saving). - * - * @return a unique value - */ - String generateSuffix(); - - /** - * Add to this queue objects that you want to save. - */ - void writeTo(Queue objectsToSave); - - /** - * Poll saved objects from the queue in the order they were written. - * - * @param savedObjects queue of objects returned by {@link #writeTo(Queue)} - */ - void readFrom(@NonNull Queue savedObjects) throws Exception; + if (externalCacheDir != null) { + cacheDirPath = externalCacheDir.getAbsolutePath(); + } + if (TextUtils.isEmpty(cacheDirPath)) { + cacheDirPath = context.getCacheDir().getAbsolutePath(); + } } /** * @see #tryToRestore(SavedState, WriteRead) + * @param outState + * @param writeRead + * @return the saved state */ - public static SavedState tryToRestore(Bundle outState, WriteRead writeRead) { - if (outState == null || writeRead == null) return null; + public static SavedState tryToRestore(final Bundle outState, final WriteRead writeRead) { + if (outState == null || writeRead == null) { + return null; + } SavedState savedState = outState.getParcelable(KEY_SAVED_STATE); - if (savedState == null) return null; + if (savedState == null) { + return null; + } return tryToRestore(savedState, writeRead); } /** - * Try to restore the state from memory and disk, using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead. + * Try to restore the state from memory and disk, + * using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead. + * @param savedState + * @param writeRead + * @return the saved state */ @Nullable - private static SavedState tryToRestore(@NonNull SavedState savedState, @NonNull WriteRead writeRead) { + private static SavedState tryToRestore(@NonNull final SavedState savedState, + @NonNull final WriteRead writeRead) { if (MainActivity.DEBUG) { - Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], writeRead = [" + writeRead + "]"); + Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], " + + "writeRead = [" + writeRead + "]"); } FileInputStream fileInputStream = null; try { - Queue savedObjects = stateObjectsHolder.remove(savedState.getPrefixFileSaved()); + Queue savedObjects + = STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); if (savedObjects != null) { writeRead.readFrom(savedObjects); if (MainActivity.DEBUG) { - Log.d(TAG, "tryToSave: reading objects from holder > " + savedObjects + ", stateObjectsHolder > " + stateObjectsHolder); + Log.d(TAG, "tryToSave: reading objects from holder > " + savedObjects + + ", stateObjectsHolder > " + STATE_OBJECTS_HOLDER); } return savedState; } File file = new File(savedState.getPathFileSaved()); if (!file.exists()) { - if(MainActivity.DEBUG) { + if (MainActivity.DEBUG) { Log.d(TAG, "Cache file doesn't exist: " + file.getAbsolutePath()); } return null; @@ -160,9 +154,16 @@ private static SavedState tryToRestore(@NonNull SavedState savedState, @NonNull /** * @see #tryToSave(boolean, String, String, WriteRead) + * @param isChangingConfig + * @param savedState + * @param outState + * @param writeRead + * @return the saved state or {@code null} */ @Nullable - public static SavedState tryToSave(boolean isChangingConfig, @Nullable SavedState savedState, Bundle outState, WriteRead writeRead) { + public static SavedState tryToSave(final boolean isChangingConfig, + @Nullable final SavedState savedState, final Bundle outState, + final WriteRead writeRead) { @NonNull String currentSavedPrefix; if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) { @@ -173,34 +174,45 @@ public static SavedState tryToSave(boolean isChangingConfig, @Nullable SavedStat currentSavedPrefix = savedState.getPrefixFileSaved(); } - savedState = tryToSave(isChangingConfig, currentSavedPrefix, writeRead.generateSuffix(), writeRead); - if (savedState != null) { - outState.putParcelable(StateSaver.KEY_SAVED_STATE, savedState); - return savedState; + final SavedState newSavedState = tryToSave(isChangingConfig, currentSavedPrefix, + writeRead.generateSuffix(), writeRead); + if (newSavedState != null) { + outState.putParcelable(StateSaver.KEY_SAVED_STATE, newSavedState); + return newSavedState; } return null; } /** - * If it's not changing configuration (i.e. rotating screen), try to write the state from {@link StateSaver.WriteRead#writeTo(Queue)} - * to the file with the name of prefixFileName + suffixFileName, in a cache folder got from the {@link #init(Context)}. + * If it's not changing configuration (i.e. rotating screen), + * try to write the state from {@link StateSaver.WriteRead#writeTo(Queue)} + * to the file with the name of prefixFileName + suffixFileName, + * in a cache folder got from the {@link #init(Context)}. *

- * It checks if the file already exists and if it does, just return the path, so a good way to save is: + * It checks if the file already exists and if it does, just return the path, + * so a good way to save is: + *

*
    - *
  • A fixed prefix for the file
  • - *
  • A changing suffix
  • + *
  • A fixed prefix for the file
  • + *
  • A changing suffix
  • *
* * @param isChangingConfig * @param prefixFileName * @param suffixFileName * @param writeRead + * @return the saved state or {@code null} */ @Nullable - private static SavedState tryToSave(boolean isChangingConfig, final String prefixFileName, String suffixFileName, WriteRead writeRead) { + private static SavedState tryToSave(final boolean isChangingConfig, final String prefixFileName, + final String suffixFileName, final WriteRead writeRead) { if (MainActivity.DEBUG) { - Log.d(TAG, "tryToSave() called with: isChangingConfig = [" + isChangingConfig + "], prefixFileName = [" + prefixFileName + "], suffixFileName = [" + suffixFileName + "], writeRead = [" + writeRead + "]"); + Log.d(TAG, "tryToSave() called with: " + + "isChangingConfig = [" + isChangingConfig + "], " + + "prefixFileName = [" + prefixFileName + "], " + + "suffixFileName = [" + suffixFileName + "], " + + "writeRead = [" + writeRead + "]"); } LinkedList savedObjects = new LinkedList<>(); @@ -208,10 +220,12 @@ private static SavedState tryToSave(boolean isChangingConfig, final String prefi if (isChangingConfig) { if (savedObjects.size() > 0) { - stateObjectsHolder.put(prefixFileName, savedObjects); + STATE_OBJECTS_HOLDER.put(prefixFileName, savedObjects); return new SavedState(prefixFileName, ""); } else { - if(MainActivity.DEBUG) Log.d(TAG, "Nothing to save"); + if (MainActivity.DEBUG) { + Log.d(TAG, "Nothing to save"); + } return null; } } @@ -219,19 +233,22 @@ private static SavedState tryToSave(boolean isChangingConfig, final String prefi FileOutputStream fileOutputStream = null; try { File cacheDir = new File(cacheDirPath); - if (!cacheDir.exists()) throw new RuntimeException("Cache dir does not exist > " + cacheDirPath); + if (!cacheDir.exists()) { + throw new RuntimeException("Cache dir does not exist > " + cacheDirPath); + } cacheDir = new File(cacheDir, CACHE_DIR_NAME); if (!cacheDir.exists()) { - if(!cacheDir.mkdir()) { - if(BuildConfig.DEBUG) { - Log.e(TAG, "Failed to create cache directory " + cacheDir.getAbsolutePath()); + if (!cacheDir.mkdir()) { + if (BuildConfig.DEBUG) { + Log.e(TAG, + "Failed to create cache directory " + cacheDir.getAbsolutePath()); } return null; } } - if (TextUtils.isEmpty(suffixFileName)) suffixFileName = ".cache"; - File file = new File(cacheDir, prefixFileName + suffixFileName); + File file = new File(cacheDir, prefixFileName + + (TextUtils.isEmpty(suffixFileName) ? ".cache" : suffixFileName)); if (file.exists() && file.length() > 0) { // If the file already exists, just return it return new SavedState(prefixFileName, file.getAbsolutePath()); @@ -239,7 +256,7 @@ private static SavedState tryToSave(boolean isChangingConfig, final String prefi // Delete any file that contains the prefix File[] files = cacheDir.listFiles(new FilenameFilter() { @Override - public boolean accept(File dir, String name) { + public boolean accept(final File dir, final String name) { return name.contains(prefixFileName); } }); @@ -259,21 +276,25 @@ public boolean accept(File dir, String name) { if (fileOutputStream != null) { try { fileOutputStream.close(); - } catch (IOException ignored) { - } + } catch (IOException ignored) { } } } return null; } /** - * Delete the cache file contained in the savedState and remove any possible-existing value in the memory-cache. + * Delete the cache file contained in the savedState. + * Also remove any possible-existing value in the memory-cache. + * + * @param savedState the saved state to delete */ - public static void onDestroy(SavedState savedState) { - if (MainActivity.DEBUG) Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]"); + public static void onDestroy(final SavedState savedState) { + if (MainActivity.DEBUG) { + Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]"); + } if (savedState != null && !TextUtils.isEmpty(savedState.getPathFileSaved())) { - stateObjectsHolder.remove(savedState.getPrefixFileSaved()); + STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); try { //noinspection ResultOfMethodCallIgnored new File(savedState.getPathFileSaved()).delete(); @@ -286,35 +307,83 @@ public static void onDestroy(SavedState savedState) { * Clear all the files in cache (in memory and disk). */ public static void clearStateFiles() { - if (MainActivity.DEBUG) Log.d(TAG, "clearStateFiles() called"); + if (MainActivity.DEBUG) { + Log.d(TAG, "clearStateFiles() called"); + } - stateObjectsHolder.clear(); + STATE_OBJECTS_HOLDER.clear(); File cacheDir = new File(cacheDirPath); - if (!cacheDir.exists()) return; + if (!cacheDir.exists()) { + return; + } cacheDir = new File(cacheDir, CACHE_DIR_NAME); if (cacheDir.exists()) { - for (File file : cacheDir.listFiles()) file.delete(); + for (File file : cacheDir.listFiles()) { + file.delete(); + } } } + /** + * Used for describe how to save/read the objects. + *

+ * Queue was chosen by its FIFO property. + */ + public interface WriteRead { + /** + * Generate a changing suffix that will name the cache file, + * and be used to identify if it changed (thus reducing useless reading/saving). + * + * @return a unique value + */ + String generateSuffix(); + + /** + * Add to this queue objects that you want to save. + * + * @param objectsToSave the objects to save + */ + void writeTo(Queue objectsToSave); + + /** + * Poll saved objects from the queue in the order they were written. + * + * @param savedObjects queue of objects returned by {@link #writeTo(Queue)} + */ + void readFrom(@NonNull Queue savedObjects) throws Exception; + } + /*////////////////////////////////////////////////////////////////////////// // Inner //////////////////////////////////////////////////////////////////////////*/ /** - * Information about the saved state on the disk + * Information about the saved state on the disk. */ public static class SavedState implements Parcelable { + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public SavedState createFromParcel(final Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(final int size) { + return new SavedState[size]; + } + }; private final String prefixFileSaved; private final String pathFileSaved; - public SavedState(String prefixFileSaved, String pathFileSaved) { + public SavedState(final String prefixFileSaved, final String pathFileSaved) { this.prefixFileSaved = prefixFileSaved; this.pathFileSaved = pathFileSaved; } - protected SavedState(Parcel in) { + protected SavedState(final Parcel in) { prefixFileSaved = in.readString(); pathFileSaved = in.readString(); } @@ -330,26 +399,14 @@ public int describeContents() { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(final Parcel dest, final int flags) { dest.writeString(prefixFileSaved); dest.writeString(pathFileSaved); } - @SuppressWarnings("unused") - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - /** - * Get the prefix of the saved file + * Get the prefix of the saved file. + * * @return the file prefix */ public String getPrefixFileSaved() { @@ -357,7 +414,8 @@ public String getPrefixFileSaved() { } /** - * Get the path to the saved file + * Get the path to the saved file. + * * @return the path to the saved file */ public String getPathFileSaved() { diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java index b3ec4d14e..92aee8ba7 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.util; import android.content.Context; + import androidx.fragment.app.Fragment; import org.schabi.newpipe.R; @@ -16,26 +17,33 @@ public enum StreamDialogEntry { ////////////////////////////////////// enqueue_on_background(R.string.enqueue_on_background, (fragment, item) -> - NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(), new SinglePlayQueue(item), false)), + NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(), + new SinglePlayQueue(item), false)), enqueue_on_popup(R.string.enqueue_on_popup, (fragment, item) -> - NavigationHelper.enqueueOnPopupPlayer(fragment.getContext(), new SinglePlayQueue(item), false)), + NavigationHelper.enqueueOnPopupPlayer(fragment.getContext(), + new SinglePlayQueue(item), false)), start_here_on_background(R.string.start_here_on_background, (fragment, item) -> - NavigationHelper.playOnBackgroundPlayer(fragment.getContext(), new SinglePlayQueue(item), true)), + NavigationHelper.playOnBackgroundPlayer(fragment.getContext(), + new SinglePlayQueue(item), true)), start_here_on_popup(R.string.start_here_on_popup, (fragment, item) -> - NavigationHelper.playOnPopupPlayer(fragment.getContext(), new SinglePlayQueue(item), true)), + NavigationHelper.playOnPopupPlayer(fragment.getContext(), + new SinglePlayQueue(item), true)), - set_as_playlist_thumbnail(R.string.set_as_playlist_thumbnail, (fragment, item) -> {}), // has to be set manually + set_as_playlist_thumbnail(R.string.set_as_playlist_thumbnail, (fragment, item) -> { + }), // has to be set manually - delete(R.string.delete, (fragment, item) -> {}), // has to be set manually + delete(R.string.delete, (fragment, item) -> { + }), // has to be set manually append_playlist(R.string.append_playlist, (fragment, item) -> { if (fragment.getFragmentManager() != null) { PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) .show(fragment.getFragmentManager(), "StreamDialogEntry@append_playlist"); - }}), + } + }), share(R.string.share, (fragment, item) -> ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl())); @@ -45,43 +53,28 @@ public enum StreamDialogEntry { // variables // /////////////// - public interface StreamDialogEntryAction { - void onClick(Fragment fragment, final StreamInfoItem infoItem); - } - + private static StreamDialogEntry[] enabledEntries; private final int resource; private final StreamDialogEntryAction defaultAction; private StreamDialogEntryAction customAction; - private static StreamDialogEntry[] enabledEntries; - - - /////////////////////////////////////////////////////// - // non-static methods to initialize and edit entries // - /////////////////////////////////////////////////////// - - StreamDialogEntry(final int resource, StreamDialogEntryAction defaultAction) { + StreamDialogEntry(final int resource, final StreamDialogEntryAction defaultAction) { this.resource = resource; this.defaultAction = defaultAction; this.customAction = null; } - /** - * Can be used after {@link #setEnabledEntries(StreamDialogEntry...)} has been called - */ - public void setCustomAction(StreamDialogEntryAction action) { - this.customAction = action; - } - - //////////////////////////////////////////////// - // static methods that act on enabled entries // - //////////////////////////////////////////////// + /////////////////////////////////////////////////////// + // non-static methods to initialize and edit entries // + /////////////////////////////////////////////////////// /** - * To be called before using {@link #setCustomAction(StreamDialogEntryAction)} + * To be called before using {@link #setCustomAction(StreamDialogEntryAction)}. + * + * @param entries the entries to be enabled */ - public static void setEnabledEntries(StreamDialogEntry... entries) { + public static void setEnabledEntries(final StreamDialogEntry... entries) { // cleanup from last time StreamDialogEntry was used for (StreamDialogEntry streamDialogEntry : values()) { streamDialogEntry.customAction = null; @@ -90,7 +83,7 @@ public static void setEnabledEntries(StreamDialogEntry... entries) { enabledEntries = entries; } - public static String[] getCommands(Context context) { + public static String[] getCommands(final Context context) { String[] commands = new String[enabledEntries.length]; for (int i = 0; i != enabledEntries.length; ++i) { commands[i] = context.getResources().getString(enabledEntries[i].resource); @@ -99,11 +92,30 @@ public static String[] getCommands(Context context) { return commands; } - public static void clickOn(int which, Fragment fragment, StreamInfoItem infoItem) { + + //////////////////////////////////////////////// + // static methods that act on enabled entries // + //////////////////////////////////////////////// + + public static void clickOn(final int which, final Fragment fragment, + final StreamInfoItem infoItem) { if (enabledEntries[which].customAction == null) { enabledEntries[which].defaultAction.onClick(fragment, infoItem); } else { enabledEntries[which].customAction.onClick(fragment, infoItem); } } + + /** + * Can be used after {@link #setEnabledEntries(StreamDialogEntry...)} has been called. + * + * @param action the action to be set + */ + public void setCustomAction(final StreamDialogEntryAction action) { + this.customAction = action; + } + + public interface StreamDialogEntryAction { + void onClick(Fragment fragment, StreamInfoItem infoItem); + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java index cb2fae4f0..6a244a69b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java @@ -18,6 +18,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream; import java.io.Serializable; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; @@ -28,8 +29,11 @@ import us.shandian.giga.util.Utility; /** - * A list adapter for a list of {@link Stream streams}, - * currently supporting {@link VideoStream}, {@link AudioStream} and {@link SubtitlesStream} + * A list adapter for a list of {@link Stream streams}. + * It currently supports {@link VideoStream}, {@link AudioStream} and {@link SubtitlesStream}. + * + * @param the primary stream type's class extending {@link Stream} + * @param the secondary stream type's class extending {@link Stream} */ public class StreamItemAdapter extends BaseAdapter { private final Context context; @@ -37,17 +41,19 @@ public class StreamItemAdapter extends BaseA private final StreamSizeWrapper streamsWrapper; private final SparseArray> secondaryStreams; - public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper, SparseArray> secondaryStreams) { + public StreamItemAdapter(final Context context, final StreamSizeWrapper streamsWrapper, + final SparseArray> secondaryStreams) { this.context = context; this.streamsWrapper = streamsWrapper; this.secondaryStreams = secondaryStreams; } - public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper, boolean showIconNoAudio) { + public StreamItemAdapter(final Context context, final StreamSizeWrapper streamsWrapper, + final boolean showIconNoAudio) { this(context, streamsWrapper, showIconNoAudio ? new SparseArray<>() : null); } - public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper) { + public StreamItemAdapter(final Context context, final StreamSizeWrapper streamsWrapper) { this(context, streamsWrapper, null); } @@ -65,28 +71,33 @@ public int getCount() { } @Override - public T getItem(int position) { + public T getItem(final int position) { return streamsWrapper.getStreamsList().get(position); } @Override - public long getItemId(int position) { + public long getItemId(final int position) { return position; } @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { + public View getDropDownView(final int position, final View convertView, + final ViewGroup parent) { return getCustomView(position, convertView, parent, true); } @Override - public View getView(int position, View convertView, ViewGroup parent) { - return getCustomView(((Spinner) parent).getSelectedItemPosition(), convertView, parent, false); + public View getView(final int position, final View convertView, final ViewGroup parent) { + return getCustomView(((Spinner) parent).getSelectedItemPosition(), + convertView, parent, false); } - private View getCustomView(int position, View convertView, ViewGroup parent, boolean isDropdownItem) { + private View getCustomView(final int position, final View view, final ViewGroup parent, + final boolean isDropdownItem) { + View convertView = view; if (convertView == null) { - convertView = LayoutInflater.from(context).inflate(R.layout.stream_quality_item, parent, false); + convertView = LayoutInflater.from(context).inflate( + R.layout.stream_quality_item, parent, false); } final ImageView woSoundIconView = convertView.findViewById(R.id.wo_sound_icon); @@ -105,7 +116,8 @@ private View getCustomView(int position, View convertView, ViewGroup parent, boo if (secondaryStreams != null) { if (videoStream.isVideoOnly()) { - woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE : View.INVISIBLE; + woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE + : View.INVISIBLE; } else if (isDropdownItem) { woSoundIconVisibility = View.INVISIBLE; } @@ -125,7 +137,8 @@ private View getCustomView(int position, View convertView, ViewGroup parent, boo } if (streamsWrapper.getSizeInBytes(position) > 0) { - SecondaryStreamHelper secondary = secondaryStreams == null ? null : secondaryStreams.get(position); + SecondaryStreamHelper secondary = secondaryStreams == null ? null + : secondaryStreams.get(position); if (secondary != null) { long size = secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position); sizeView.setText(Utility.formatBytes(size)); @@ -159,30 +172,36 @@ private View getCustomView(int position, View convertView, ViewGroup parent, boo /** * A wrapper class that includes a way of storing the stream sizes. + * + * @param the stream type's class extending {@link Stream} */ public static class StreamSizeWrapper implements Serializable { - private static final StreamSizeWrapper EMPTY = new StreamSizeWrapper<>(Collections.emptyList(), null); + private static final StreamSizeWrapper EMPTY = new StreamSizeWrapper<>( + Collections.emptyList(), null); private final List streamsList; private final long[] streamSizes; private final String unknownSize; - public StreamSizeWrapper(List sL, Context context) { + public StreamSizeWrapper(final List sL, final Context context) { this.streamsList = sL != null ? sL : Collections.emptyList(); this.streamSizes = new long[streamsList.size()]; - this.unknownSize = context == null ? "--.-" : context.getString(R.string.unknown_content); + this.unknownSize = context == null + ? "--.-" : context.getString(R.string.unknown_content); - for (int i = 0; i < streamSizes.length; i++) streamSizes[i] = -2; + Arrays.fill(streamSizes, -2); } /** * Helper method to fetch the sizes of all the streams in a wrapper. * + * @param the stream type's class extending {@link Stream} * @param streamsWrapper the wrapper * @return a {@link Single} that returns a boolean indicating if any elements were changed */ - public static Single fetchSizeForWrapper(StreamSizeWrapper streamsWrapper) { + public static Single fetchSizeForWrapper( + final StreamSizeWrapper streamsWrapper) { final Callable fetchAndSet = () -> { boolean hasChanged = false; for (X stream : streamsWrapper.getStreamsList()) { @@ -190,7 +209,8 @@ public static Single fetchSizeForWrapper(StreamSizeW continue; } - final long contentLength = DownloaderImpl.getInstance().getContentLength(stream.getUrl()); + final long contentLength = DownloaderImpl.getInstance().getContentLength( + stream.getUrl()); streamsWrapper.setSize(stream, contentLength); hasChanged = true; } @@ -203,44 +223,44 @@ public static Single fetchSizeForWrapper(StreamSizeW .onErrorReturnItem(true); } + public static StreamSizeWrapper empty() { + //noinspection unchecked + return (StreamSizeWrapper) EMPTY; + } + public List getStreamsList() { return streamsList; } - public long getSizeInBytes(int streamIndex) { + public long getSizeInBytes(final int streamIndex) { return streamSizes[streamIndex]; } - public long getSizeInBytes(T stream) { + public long getSizeInBytes(final T stream) { return streamSizes[streamsList.indexOf(stream)]; } - public String getFormattedSize(int streamIndex) { + public String getFormattedSize(final int streamIndex) { return formatSize(getSizeInBytes(streamIndex)); } - public String getFormattedSize(T stream) { + public String getFormattedSize(final T stream) { return formatSize(getSizeInBytes(stream)); } - private String formatSize(long size) { + private String formatSize(final long size) { if (size > -1) { return Utility.formatBytes(size); } return unknownSize; } - public void setSize(int streamIndex, long sizeInBytes) { + public void setSize(final int streamIndex, final long sizeInBytes) { streamSizes[streamIndex] = sizeInBytes; } - public void setSize(T stream, long sizeInBytes) { + public void setSize(final T stream, final long sizeInBytes) { streamSizes[streamsList.indexOf(stream)] = sizeInBytes; } - - public static StreamSizeWrapper empty() { - //noinspection unchecked - return (StreamSizeWrapper) EMPTY; - } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java b/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java index d8b6f78f5..105af5086 100644 --- a/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.net.InetAddress; import java.net.Socket; -import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; @@ -27,31 +26,36 @@ public class TLSSocketFactoryCompat extends SSLSocketFactory { private SSLSocketFactory internalSSLSocketFactory; - public static TLSSocketFactoryCompat getInstance() throws NoSuchAlgorithmException, KeyManagementException { - if (instance != null) { - return instance; - } - return instance = new TLSSocketFactoryCompat(); - } - - public TLSSocketFactoryCompat() throws KeyManagementException, NoSuchAlgorithmException { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, null, null); internalSSLSocketFactory = context.getSocketFactory(); } - public TLSSocketFactoryCompat(TrustManager[] tm) throws KeyManagementException, NoSuchAlgorithmException { + + public TLSSocketFactoryCompat(final TrustManager[] tm) + throws KeyManagementException, NoSuchAlgorithmException { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, tm, new java.security.SecureRandom()); internalSSLSocketFactory = context.getSocketFactory(); } + public static TLSSocketFactoryCompat getInstance() + throws NoSuchAlgorithmException, KeyManagementException { + if (instance != null) { + return instance; + } + instance = new TLSSocketFactoryCompat(); + return instance; + } + public static void setAsDefault() { try { HttpsURLConnection.setDefaultSSLSocketFactory(getInstance()); } catch (NoSuchAlgorithmException | KeyManagementException e) { - if (DEBUG) e.printStackTrace(); + if (DEBUG) { + e.printStackTrace(); + } } } @@ -71,34 +75,40 @@ public Socket createSocket() throws IOException { } @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + public Socket createSocket(final Socket s, final String host, final int port, + final boolean autoClose) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); } @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + public Socket createSocket(final String host, final int port) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); } @Override - public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); + public Socket createSocket(final String host, final int port, final InetAddress localHost, + final int localPort) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket( + host, port, localHost, localPort)); } @Override - public Socket createSocket(InetAddress host, int port) throws IOException { + public Socket createSocket(final InetAddress host, final int port) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); } @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); + public Socket createSocket(final InetAddress address, final int port, + final InetAddress localAddress, final int localPort) + throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket( + address, port, localAddress, localPort)); } - private Socket enableTLSOnSocket(Socket socket) { + private Socket enableTLSOnSocket(final Socket socket) { if (socket != null && (socket instanceof SSLSocket)) { ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"}); } return socket; } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index bd51919c7..0041968d6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -22,18 +22,20 @@ import android.content.Context; import android.content.res.TypedArray; import android.preference.PreferenceManager; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; + import androidx.annotation.AttrRes; import androidx.annotation.StyleRes; import androidx.core.content.ContextCompat; -import android.util.TypedValue; -import android.view.ContextThemeWrapper; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -public class ThemeHelper { +public final class ThemeHelper { + private ThemeHelper() { } /** * Apply the selected theme (on NewPipe settings) in the context @@ -41,7 +43,7 @@ public class ThemeHelper { * * @param context context that the theme will be applied */ - public static void setTheme(Context context) { + public static void setTheme(final Context context) { setTheme(context, -1); } @@ -53,17 +55,19 @@ public static void setTheme(Context context) { * @param serviceId the theme will be styled to the service with this id, * pass -1 to get the default style */ - public static void setTheme(Context context, int serviceId) { + public static void setTheme(final Context context, final int serviceId) { context.setTheme(getThemeForService(context, serviceId)); } /** - * Return true if the selected theme (on NewPipe settings) is the Light theme + * Return true if the selected theme (on NewPipe settings) is the Light theme. * * @param context context to get the preference + * @return whether the light theme is selected */ - public static boolean isLightThemeSelected(Context context) { - return getSelectedThemeString(context).equals(context.getResources().getString(R.string.light_theme_key)); + public static boolean isLightThemeSelected(final Context context) { + return getSelectedThemeString(context).equals(context.getResources() + .getString(R.string.light_theme_key)); } @@ -73,18 +77,19 @@ public static boolean isLightThemeSelected(Context context) { * @param baseContext the base context for the wrapper * @return a wrapped-styled context */ - public static Context getThemedContext(Context baseContext) { + public static Context getThemedContext(final Context baseContext) { return new ContextThemeWrapper(baseContext, getThemeForService(baseContext, -1)); } /** - * Return the selected theme without being styled to any service (see {@link #getThemeForService(Context, int)}). + * Return the selected theme without being styled to any service. + * See {@link #getThemeForService(Context, int)}. * * @param context context to get the selected theme * @return the selected style (the default one) */ @StyleRes - public static int getDefaultTheme(Context context) { + public static int getDefaultTheme(final Context context) { return getThemeForService(context, -1); } @@ -95,7 +100,7 @@ public static int getDefaultTheme(Context context) { * @return the dialog style (the default one) */ @StyleRes - public static int getDialogTheme(Context context) { + public static int getDialogTheme(final Context context) { return isLightThemeSelected(context) ? R.style.LightDialogTheme : R.style.DarkDialogTheme; } @@ -106,8 +111,9 @@ public static int getDialogTheme(Context context) { * @return the dialog style (the default one) */ @StyleRes - public static int getMinWidthDialogTheme(Context context) { - return isLightThemeSelected(context) ? R.style.LightDialogMinWidthTheme : R.style.DarkDialogMinWidthTheme; + public static int getMinWidthDialogTheme(final Context context) { + return isLightThemeSelected(context) ? R.style.LightDialogMinWidthTheme + : R.style.DarkDialogMinWidthTheme; } /** @@ -119,7 +125,7 @@ public static int getMinWidthDialogTheme(Context context) { * @return the selected style (styled) */ @StyleRes - public static int getThemeForService(Context context, int serviceId) { + public static int getThemeForService(final Context context, final int serviceId) { String lightTheme = context.getResources().getString(R.string.light_theme_key); String darkTheme = context.getResources().getString(R.string.dark_theme_key); String blackTheme = context.getResources().getString(R.string.black_theme_key); @@ -127,9 +133,13 @@ public static int getThemeForService(Context context, int serviceId) { String selectedTheme = getSelectedThemeString(context); int defaultTheme = R.style.DarkTheme; - if (selectedTheme.equals(lightTheme)) defaultTheme = R.style.LightTheme; - else if (selectedTheme.equals(blackTheme)) defaultTheme = R.style.BlackTheme; - else if (selectedTheme.equals(darkTheme)) defaultTheme = R.style.DarkTheme; + if (selectedTheme.equals(lightTheme)) { + defaultTheme = R.style.LightTheme; + } else if (selectedTheme.equals(blackTheme)) { + defaultTheme = R.style.BlackTheme; + } else if (selectedTheme.equals(darkTheme)) { + defaultTheme = R.style.DarkTheme; + } if (serviceId <= -1) { return defaultTheme; @@ -143,9 +153,13 @@ public static int getThemeForService(Context context, int serviceId) { } String themeName = "DarkTheme"; - if (selectedTheme.equals(lightTheme)) themeName = "LightTheme"; - else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; - else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; + if (selectedTheme.equals(lightTheme)) { + themeName = "LightTheme"; + } else if (selectedTheme.equals(blackTheme)) { + themeName = "BlackTheme"; + } else if (selectedTheme.equals(darkTheme)) { + themeName = "DarkTheme"; + } themeName += "." + service.getServiceInfo().getName(); int resourceId = context @@ -160,24 +174,33 @@ public static int getThemeForService(Context context, int serviceId) { } @StyleRes - public static int getSettingsThemeStyle(Context context) { + public static int getSettingsThemeStyle(final Context context) { String lightTheme = context.getResources().getString(R.string.light_theme_key); String darkTheme = context.getResources().getString(R.string.dark_theme_key); String blackTheme = context.getResources().getString(R.string.black_theme_key); String selectedTheme = getSelectedThemeString(context); - if (selectedTheme.equals(lightTheme)) return R.style.LightSettingsTheme; - else if (selectedTheme.equals(blackTheme)) return R.style.BlackSettingsTheme; - else if (selectedTheme.equals(darkTheme)) return R.style.DarkSettingsTheme; + if (selectedTheme.equals(lightTheme)) { + return R.style.LightSettingsTheme; + } else if (selectedTheme.equals(blackTheme)) { + return R.style.BlackSettingsTheme; + } else if (selectedTheme.equals(darkTheme)) { + return R.style.DarkSettingsTheme; + } else { // Fallback - else return R.style.DarkSettingsTheme; + return R.style.DarkSettingsTheme; + } } /** * Get a resource id from a resource styled according to the the context's theme. + * + * @param context Android app context + * @param attr attribute reference of the resource + * @return resource ID */ - public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) { + public 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(); @@ -186,8 +209,12 @@ public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) /** * Get a color from an attr styled according to the the context's theme. + * + * @param context Android app context + * @param attrColor attribute reference of the resource + * @return the color */ - public static int resolveColorFromAttr(Context context, @AttrRes int attrColor) { + public static int resolveColorFromAttr(final Context context, @AttrRes final int attrColor) { final TypedValue value = new TypedValue(); context.getTheme().resolveAttribute(attrColor, value, true); @@ -198,21 +225,22 @@ public static int resolveColorFromAttr(Context context, @AttrRes int attrColor) return value.data; } - private static String getSelectedThemeString(Context context) { + private static String getSelectedThemeString(final Context context) { String themeKey = context.getString(R.string.theme_key); String defaultTheme = context.getResources().getString(R.string.default_theme_value); - return PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, defaultTheme); + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(themeKey, defaultTheme); } /** * This will get the R.drawable.* resource to which attr is currently pointing to. * - * @param attr a R.attribute.* resource value + * @param attr a R.attribute.* resource value * @param context the context to use * @return a R.drawable.* resource value */ public static int getIconByAttr(final int attr, final Context context) { - return context.obtainStyledAttributes(new int[] {attr}) + return context.obtainStyledAttributes(new int[]{attr}) .getResourceId(0, -1); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java index 3142ad8dc..31f5fd222 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java @@ -12,42 +12,45 @@ * Created by Christian Schabesberger on 28.01.18. * Copyright 2018 Christian Schabesberger * ZipHelper.java is part of NewPipe - * + *

* License: GPL-3.0+ * This program 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. - * + *

* This program 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. - * + *

* You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -public class ZipHelper { +public final class ZipHelper { + private ZipHelper() { } private static final int BUFFER_SIZE = 2048; /** * This function helps to create zip files. * Caution this will override the original file. + * * @param outZip The ZipOutputStream where the data should be stored in - * @param file The path of the file that should be added to zip. - * @param name The path of the file inside the zip. + * @param file The path of the file that should be added to zip. + * @param name The path of the file inside the zip. * @throws Exception */ - public static void addFileToZip(ZipOutputStream outZip, String file, String name) throws Exception { - byte data[] = new byte[BUFFER_SIZE]; + public static void addFileToZip(final ZipOutputStream outZip, final String file, + final String name) throws Exception { + byte[] data = new byte[BUFFER_SIZE]; FileInputStream fi = new FileInputStream(file); BufferedInputStream inputStream = new BufferedInputStream(fi, BUFFER_SIZE); ZipEntry entry = new ZipEntry(name); outZip.putNextEntry(entry); int count; - while((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) { + while ((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) { outZip.write(data, 0, count); } inputStream.close(); @@ -56,36 +59,39 @@ public static void addFileToZip(ZipOutputStream outZip, String file, String name /** * This will extract data from Zipfiles. * Caution this will override the original file. + * + * @param filePath The path of the zip * @param file The path of the file on the disk where the data should be extracted to. * @param name The path of the file inside the zip. * @return will return true if the file was found within the zip file * @throws Exception */ - public static boolean extractFileFromZip(String filePath, String file, String name) throws Exception { + public static boolean extractFileFromZip(final String filePath, final String file, + final String name) throws Exception { ZipInputStream inZip = new ZipInputStream( new BufferedInputStream( new FileInputStream(filePath))); - byte data[] = new byte[BUFFER_SIZE]; + byte[] data = new byte[BUFFER_SIZE]; boolean found = false; ZipEntry ze; - while((ze = inZip.getNextEntry()) != null) { - if(ze.getName().equals(name)) { + while ((ze = inZip.getNextEntry()) != null) { + if (ze.getName().equals(name)) { found = true; // delete old file first File oldFile = new File(file); - if(oldFile.exists()) { - if(!oldFile.delete()) { + if (oldFile.exists()) { + if (!oldFile.delete()) { throw new Exception("Could not delete " + file); } } FileOutputStream outFile = new FileOutputStream(file); int count = 0; - while((count = inZip.read(data)) != -1) { + while ((count = inZip.read(data)) != -1) { outFile.write(data, 0, count); } diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index bbad56c37..5a0dbb003 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -35,122 +35,139 @@ public final class PatternsCompat { * http://data.iana.org/TLD/tlds-alpha-by-domain.txt * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py */ - static final String IANA_TOP_LEVEL_DOMAINS = - "(?:" - + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" - + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam" - + "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates" - + "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" - + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" - + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black" - + "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique" - + "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business" - + "|buzz|bzh|b[abdefghijmnorstvwyz])" - + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" - + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo" - + "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco" - + "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach" - + "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos" - + "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses" - + "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" - + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" - + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount" - + "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" - + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" - + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed" - + "|express|e[cegrstu])" - + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film" - + "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth" - + "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi" - + "|f[ijkmor])" - + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" - + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger" - + "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])" - + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings" - + "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai" - + "|h[kmnrtu])" - + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute" - + "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])" - + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" - + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])" - + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc" - + "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live" - + "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])" - + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" - + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda" - + "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar" - + "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" - + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" - + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" - + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" - + "|otsuka|ovh|om)" - + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" - + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing" - + "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property" - + "|protection|pub|p[aefghklmnrstwy])" - + "|(?:qpon|quebec|qa)" - + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals" - + "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks" - + "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" - + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" - + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security" - + "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski" - + "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting" - + "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies" - + "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])" - + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" - + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools" - + "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])" - + "|(?:ubs|university|uno|uol|u[agksyz])" - + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" - + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" - + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill" - + "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" - + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434" - + "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d" - + "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431" - + "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648" - + "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" - + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646" - + "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" - + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629" - + "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646" - + "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627" - + "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" - + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4" - + "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" - + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22" - + "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c" - + "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71" - + "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063" - + "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c" - + "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c" - + "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f" - + "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" - + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" - + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox" - + "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g" - + "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" - + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" - + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a" - + "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd" - + "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h" - + "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" - + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" - + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" - + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d" - + "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt" - + "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e" - + "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab" - + "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema" - + "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" - + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c" - + "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb" - + "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a" - + "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o" - + "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)" - + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" - + "|(?:zara|zip|zone|zuerich|z[amw]))"; + static final String IANA_TOP_LEVEL_DOMAINS = "(?:" + + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" + + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica" + + "|amsterdam|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia" + + "|associates|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" + + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" + + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz" + + "|black|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots" + + "|boutique|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build" + + "|builders|business|buzz|bzh|b[abdefghijmnorstvwyz])" + + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" + + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center" + + "|ceo|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani" + + "|cisco|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed" + + "|coach|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec" + + "|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country" + + "|coupons|courses|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc" + + "|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" + + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" + + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory" + + "|discount|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" + + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" + + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert" + + "|exposed|express|e[cegrstu])" + + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm" + + "|fashion|feedback|ferrero|film|final|finance|financial|firmdale|fish|fishing|fit" + + "|fitness|flights|florist|flowers|flsmidth|fly|foo|football|forex|forsale|forum" + + "|foundation|frl|frogans|fund|furniture|futbol|fyi|f[ijkmor])" + + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" + + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov" + + "|grainger|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru" + + "|g[abdefghilmnpqrstuwy])" + + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey" + + "|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house" + + "|how|hsbc|hyundai|h[kmnrtu])" + + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink" + + "|institute|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau" + + "|iwc|i[delmnoqrst])" + + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" + + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto" + + "|k[eghimnprwyz])" + + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease" + + "|leclerc|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde" + + "|link|live|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury" + + "|l[abcikrstuvy])" + + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" + + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi" + + "|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov" + + "|movie|movistar|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" + + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" + + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" + + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" + + "|otsuka|ovh|om)" + + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" + + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation" + + "|plumbing|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties" + + "|property|protection|pub|p[aefghklmnrstwy])" + + "|(?:qpon|quebec|qa)" + + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent" + + "|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip" + + "|rocher|rocks|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" + + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" + + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat" + + "|security|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles" + + "|site|ski|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space" + + "|spiegel|spreadbetting|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study" + + "|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems" + + "|s[abcdeghijklmnortuvxyz])" + + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" + + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo" + + "|tools|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust" + + "|tui|t[cdfghjklmnortvwz])" + + "|(?:ubs|university|uno|uol|u[agksyz])" + + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" + + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" + + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki" + + "|williamhill|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" + + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c" + + "|\u043c\u043a\u0434|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430" + + "|\u043e\u043d\u043b\u0430\u0439\u043d|\u043e\u0440\u0433|\u0440\u0443\u0441" + + "|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431|\u0443\u043a\u0440" + + "|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd" + + "|\u0627\u0631\u0627\u0645\u0643\u0648|\u0627\u0644\u0627\u0631\u062f\u0646" + + "|\u0627\u0644\u062c\u0632\u0627\u0626\u0631" + + "|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" + + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a" + + "|\u0627\u06cc\u0631\u0627\u0646|\u0628\u0627\u0632\u0627\u0631" + + "|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" + + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629" + + "|\u0634\u0628\u0643\u0629|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646" + + "|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0643\u0648\u0645" + + "|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627|\u0645\u0648\u0642\u0639" + + "|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" + + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24" + + "|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe" + + "|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8" + + "|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" + + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21" + + "|\u0e44\u0e17\u0e22|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb" + + "|\u30b3\u30e0|\u4e16\u754c|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51" + + "|\u4f01\u4e1a|\u4f5b\u5c71|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8" + + "|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063|\u5546\u57ce|\u5546\u5e97|\u5546\u6807" + + "|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c|\u5e7f\u4e1c|\u6148\u5584" + + "|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c|\u65b0\u52a0\u5761" + + "|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f|\u70b9\u770b" + + "|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" + + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" + + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox|xerox|xin|xn\\-\\-11b4c3d" + + "|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e" + + "|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" + + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" + + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais" + + "|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g" + + "|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-czr694b|xn\\-\\-czrs0t" + + "|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h|xn\\-\\-estv75g" + + "|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" + + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" + + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" + + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d" + + "|xn\\-\\-kpry57d|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf" + + "|xn\\-\\-mgba3a3ejt|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd" + + "|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar" + + "|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m" + + "|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a" + + "|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" + + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g" + + "|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y" + + "|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv" + + "|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a" + + "|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx" + + "|xn\\-\\-zfr164b|xperia|xxx|xyz)" + + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" + + "|(?:zara|zip|zone|zuerich|z[amw]))"; public static final Pattern IP_ADDRESS = Pattern.compile( @@ -162,25 +179,25 @@ public final class PatternsCompat { /** * Valid UCS characters defined in RFC 3987. Excludes space characters. */ - private static final String UCS_CHAR = "[" + - "\u00A0-\uD7FF" + - "\uF900-\uFDCF" + - "\uFDF0-\uFFEF" + - "\uD800\uDC00-\uD83F\uDFFD" + - "\uD840\uDC00-\uD87F\uDFFD" + - "\uD880\uDC00-\uD8BF\uDFFD" + - "\uD8C0\uDC00-\uD8FF\uDFFD" + - "\uD900\uDC00-\uD93F\uDFFD" + - "\uD940\uDC00-\uD97F\uDFFD" + - "\uD980\uDC00-\uD9BF\uDFFD" + - "\uD9C0\uDC00-\uD9FF\uDFFD" + - "\uDA00\uDC00-\uDA3F\uDFFD" + - "\uDA40\uDC00-\uDA7F\uDFFD" + - "\uDA80\uDC00-\uDABF\uDFFD" + - "\uDAC0\uDC00-\uDAFF\uDFFD" + - "\uDB00\uDC00-\uDB3F\uDFFD" + - "\uDB44\uDC00-\uDB7F\uDFFD" + - "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]"; + private static final String UCS_CHAR = "[" + + "\u00A0-\uD7FF" + + "\uF900-\uFDCF" + + "\uFDF0-\uFFEF" + + "\uD800\uDC00-\uD83F\uDFFD" + + "\uD840\uDC00-\uD87F\uDFFD" + + "\uD880\uDC00-\uD8BF\uDFFD" + + "\uD8C0\uDC00-\uD8FF\uDFFD" + + "\uD900\uDC00-\uD93F\uDFFD" + + "\uD940\uDC00-\uD97F\uDFFD" + + "\uD980\uDC00-\uD9BF\uDFFD" + + "\uD9C0\uDC00-\uD9FF\uDFFD" + + "\uDA00\uDC00-\uDA3F\uDFFD" + + "\uDA40\uDC00-\uDA7F\uDFFD" + + "\uDA80\uDC00-\uDABF\uDFFD" + + "\uDAC0\uDC00-\uDAFF\uDFFD" + + "\uDB00\uDC00-\uDB3F\uDFFD" + + "\uDB44\uDC00-\uDB7F\uDFFD" + + "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]"; /** * Valid characters for IRI label defined in RFC 3987. @@ -195,15 +212,15 @@ public final class PatternsCompat { /** * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. */ - private static final String IRI_LABEL = - "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; + private static final String IRI_LABEL + = "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; /** * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. */ private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w"; - private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")"; + private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")"; private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; @@ -243,29 +260,29 @@ public final class PatternsCompat { + ")"); /** - * Regular expression that matches known TLDs and punycode TLDs + * Regular expression that matches known TLDs and punycode TLDs. */ - private static final String STRICT_TLD = "(?:" + - IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; + private static final String STRICT_TLD = "(?:" + + IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; /** - * Regular expression that matches host names using {@link #STRICT_TLD} + * Regular expression that matches host names using {@link #STRICT_TLD}. */ private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+" + STRICT_TLD + ")"; /** * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or - * {@link #IP_ADDRESS} + * {@link #IP_ADDRESS}. */ private static final Pattern STRICT_DOMAIN_NAME = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); /** - * Regular expression that matches domain names without a TLD + * Regular expression that matches domain names without a TLD. */ - private static final String RELAXED_DOMAIN_NAME = - "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")"; + private static final String RELAXED_DOMAIN_NAME + = "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS + ")"; /** * Regular expression to match strings that do not start with a supported protocol. The TLDs @@ -321,15 +338,15 @@ public final class PatternsCompat { * Regular expression for local part of an email address. RFC5321 section 4.5.3.1.1 limits * the local part to be at most 64 octets. */ - private static final String EMAIL_ADDRESS_LOCAL_PART = - "[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{0,62}[" + EMAIL_CHAR + "])?"; + private static final String EMAIL_ADDRESS_LOCAL_PART + = "[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{0,62}[" + EMAIL_CHAR + "])?"; /** * Regular expression for the domain part of an email address. RFC5321 section 4.5.3.1.2 limits * the domain to be at most 255 octets. */ - private static final String EMAIL_ADDRESS_DOMAIN = - "(?=.{1,255}(?:\\s|$|^))" + HOST_NAME; + private static final String EMAIL_ADDRESS_DOMAIN + = "(?=.{1,255}(?:\\s|$|^))" + HOST_NAME; /** * Regular expression pattern to match email addresses. It excludes double quoted local parts @@ -337,24 +354,24 @@ public final class PatternsCompat { * @hide */ @RestrictTo(LIBRARY_GROUP_PREFIX) - public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY + - "(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" + - WORD_BOUNDARY + ")" + public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY + + "(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" + + WORD_BOUNDARY + ")" ); public static final Pattern EMAIL_ADDRESS = Pattern.compile( - "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + - "\\@" + - "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + - "(" + - "\\." + - "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + - ")+" + "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + + "\\@" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + + "(" + + "\\." + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + + ")+" ); /** * Do not create this static utility class. */ - private PatternsCompat() {} + private PatternsCompat() { } } diff --git a/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java b/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java index 03ab40db5..0fbf6a254 100644 --- a/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java +++ b/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java @@ -1,64 +1,66 @@ package org.schabi.newpipe.views; import android.content.Context; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import android.widget.ProgressBar; -public final class AnimatedProgressBar extends ProgressBar { +import androidx.annotation.Nullable; - @Nullable - private ProgressBarAnimation animation = null; +public final class AnimatedProgressBar extends ProgressBar { + @Nullable + private ProgressBarAnimation animation = null; - public AnimatedProgressBar(Context context) { - super(context); - } + public AnimatedProgressBar(final Context context) { + super(context); + } - public AnimatedProgressBar(Context context, AttributeSet attrs) { - super(context, attrs); - } + public AnimatedProgressBar(final Context context, final AttributeSet attrs) { + super(context, attrs); + } - public AnimatedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } + public AnimatedProgressBar(final Context context, final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr); + } - public synchronized void setProgressAnimated(int progress) { - cancelAnimation(); - animation = new ProgressBarAnimation(this, getProgress(), progress); - startAnimation(animation); - } + public synchronized void setProgressAnimated(final int progress) { + cancelAnimation(); + animation = new ProgressBarAnimation(this, getProgress(), progress); + startAnimation(animation); + } - private void cancelAnimation() { - if (animation != null) { - animation.cancel(); - animation = null; - } - clearAnimation(); - } + private void cancelAnimation() { + if (animation != null) { + animation.cancel(); + animation = null; + } + clearAnimation(); + } - private static class ProgressBarAnimation extends Animation { + private static class ProgressBarAnimation extends Animation { - private final AnimatedProgressBar progressBar; - private final float from; - private final float to; + private final AnimatedProgressBar progressBar; + private final float from; + private final float to; - ProgressBarAnimation(AnimatedProgressBar progressBar, float from, float to) { - super(); - this.progressBar = progressBar; - this.from = from; - this.to = to; - setDuration(500); - setInterpolator(new AccelerateDecelerateInterpolator()); - } + ProgressBarAnimation(final AnimatedProgressBar progressBar, final float from, + final float to) { + super(); + this.progressBar = progressBar; + this.from = from; + this.to = to; + setDuration(500); + setInterpolator(new AccelerateDecelerateInterpolator()); + } - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - super.applyTransformation(interpolatedTime, t); - float value = from + (to - from) * interpolatedTime; - progressBar.setProgress((int) value); - } - } + @Override + protected void applyTransformation(final float interpolatedTime, final Transformation t) { + super.applyTransformation(interpolatedTime, t); + float value = from + (to - from) * interpolatedTime; + progressBar.setProgress((int) value); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java index 38ca58cea..71fc37e93 100644 --- a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java +++ b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java @@ -23,13 +23,14 @@ import android.content.Context; import android.os.Build; import android.os.Parcelable; -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import android.util.AttributeSet; import android.util.Log; import android.widget.LinearLayout; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + import org.schabi.newpipe.util.AnimationUtils; import java.lang.annotation.Retention; @@ -46,43 +47,40 @@ * A view that can be fully collapsed and expanded. */ public class CollapsibleView extends LinearLayout { + public static final int COLLAPSED = 0; + public static final int EXPANDED = 1; private static final String TAG = CollapsibleView.class.getSimpleName(); + private static final int ANIMATION_DURATION = 420; + private final List listeners = new ArrayList<>(); + + @State + @ViewMode + int currentState = COLLAPSED; - public CollapsibleView(Context context) { + /*////////////////////////////////////////////////////////////////////////// + // Collapse/expand logic + //////////////////////////////////////////////////////////////////////////*/ + private boolean readyToChangeState; + private int targetHeight = -1; + private ValueAnimator currentAnimator; + + public CollapsibleView(final Context context) { super(context); } - - public CollapsibleView(Context context, @Nullable AttributeSet attrs) { + public CollapsibleView(final Context context, @Nullable final AttributeSet attrs) { super(context, attrs); } - public CollapsibleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public CollapsibleView(final Context context, @Nullable final AttributeSet attrs, + final int defStyleAttr) { super(context, attrs, defStyleAttr); } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public CollapsibleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public CollapsibleView(final Context context, final AttributeSet attrs, final int defStyleAttr, + final int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } - /*////////////////////////////////////////////////////////////////////////// - // Collapse/expand logic - //////////////////////////////////////////////////////////////////////////*/ - - private static final int ANIMATION_DURATION = 420; - public static final int COLLAPSED = 0, EXPANDED = 1; - - @Retention(SOURCE) - @IntDef({COLLAPSED, EXPANDED}) - public @interface ViewMode {} - - @State @ViewMode int currentState = COLLAPSED; - private boolean readyToChangeState; - - private int targetHeight = -1; - private ValueAnimator currentAnimator; - private final List listeners = new ArrayList<>(); - /** * This method recalculates the height of this view so it must be called when * some child changes (e.g. add new views, change text). @@ -92,7 +90,8 @@ public void ready() { Log.d(TAG, getDebugLogString("ready() called")); } - measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), MeasureSpec.UNSPECIFIED); + measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.UNSPECIFIED); targetHeight = getMeasuredHeight(); getLayoutParams().height = currentState == COLLAPSED ? 0 : targetHeight; @@ -111,7 +110,9 @@ public void collapse() { Log.d(TAG, getDebugLogString("collapse() called")); } - if (!readyToChangeState) return; + if (!readyToChangeState) { + return; + } final int height = getHeight(); if (height == 0) { @@ -119,7 +120,9 @@ public void collapse() { return; } - if (currentAnimator != null && currentAnimator.isRunning()) currentAnimator.cancel(); + if (currentAnimator != null && currentAnimator.isRunning()) { + currentAnimator.cancel(); + } currentAnimator = AnimationUtils.animateHeight(this, ANIMATION_DURATION, 0); setCurrentState(COLLAPSED); @@ -130,7 +133,9 @@ public void expand() { Log.d(TAG, getDebugLogString("expand() called")); } - if (!readyToChangeState) return; + if (!readyToChangeState) { + return; + } final int height = getHeight(); if (height == this.targetHeight) { @@ -138,13 +143,17 @@ public void expand() { return; } - if (currentAnimator != null && currentAnimator.isRunning()) currentAnimator.cancel(); + if (currentAnimator != null && currentAnimator.isRunning()) { + currentAnimator.cancel(); + } currentAnimator = AnimationUtils.animateHeight(this, ANIMATION_DURATION, this.targetHeight); setCurrentState(EXPANDED); } public void switchState() { - if (!readyToChangeState) return; + if (!readyToChangeState) { + return; + } if (currentState == COLLAPSED) { expand(); @@ -158,7 +167,7 @@ public int getCurrentState() { return currentState; } - public void setCurrentState(@ViewMode int currentState) { + public void setCurrentState(@ViewMode final int currentState) { this.currentState = currentState; broadcastState(); } @@ -171,6 +180,7 @@ public void broadcastState() { /** * Add a listener which will be listening for changes in this view (i.e. collapsed or expanded). + * @param listener {@link StateListener} to be added */ public void addListener(final StateListener listener) { if (listeners.contains(listener)) { @@ -182,28 +192,12 @@ public void addListener(final StateListener listener) { /** * Remove a listener so it doesn't receive more state changes. + * @param listener {@link StateListener} to be removed */ public void removeListener(final StateListener listener) { listeners.remove(listener); } - /** - * Simple interface used for listening state changes of the {@link CollapsibleView}. - */ - public interface StateListener { - /** - * Called when the state changes. - * - * @param newState the state that the {@link CollapsibleView} transitioned to,
- * it's an integer being either {@link #COLLAPSED} or {@link #EXPANDED} - */ - void onStateChanged(@ViewMode int newState); - } - - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - @Nullable @Override public Parcelable onSaveInstanceState() { @@ -211,20 +205,44 @@ public Parcelable onSaveInstanceState() { } @Override - public void onRestoreInstanceState(Parcelable state) { + public void onRestoreInstanceState(final Parcelable state) { super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state)); ready(); } /*////////////////////////////////////////////////////////////////////////// - // Internal + // State Saving //////////////////////////////////////////////////////////////////////////*/ - public String getDebugLogString(String description) { + public String getDebugLogString(final String description) { return String.format("%-100s → %s", - description, "readyToChangeState = [" + readyToChangeState + "], currentState = [" + currentState + "], targetHeight = [" + targetHeight + "]," + - " mW x mH = [" + getMeasuredWidth() + "x" + getMeasuredHeight() + "]" + - " W x H = [" + getWidth() + "x" + getHeight() + "]"); + description, "readyToChangeState = [" + readyToChangeState + "], " + + "currentState = [" + currentState + "], " + + "targetHeight = [" + targetHeight + "], " + + "mW x mH = [" + getMeasuredWidth() + "x" + getMeasuredHeight() + "], " + + "W x H = [" + getWidth() + "x" + getHeight() + "]"); + } + + @Retention(SOURCE) + @IntDef({COLLAPSED, EXPANDED}) + public @interface ViewMode { + } + + /*////////////////////////////////////////////////////////////////////////// + // Internal + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Simple interface used for listening state changes of the {@link CollapsibleView}. + */ + public interface StateListener { + /** + * Called when the state changes. + * + * @param newState the state that the {@link CollapsibleView} transitioned to,
+ * it's an integer being either {@link #COLLAPSED} or {@link #EXPANDED} + */ + void onStateChanged(@ViewMode int newState); } } diff --git a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java index 48327220a..48e8ef81c 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java @@ -1,15 +1,12 @@ package org.schabi.newpipe.views; import android.content.Context; -import android.os.Build; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayout.Tab; /** * A TabLayout that is scrollable when tabs exceed its width. @@ -21,34 +18,36 @@ public class ScrollableTabLayout extends TabLayout { private int layoutWidth = 0; private int prevVisibility = View.GONE; - public ScrollableTabLayout(Context context) { + public ScrollableTabLayout(final Context context) { super(context); } - public ScrollableTabLayout(Context context, AttributeSet attrs) { + public ScrollableTabLayout(final Context context, final AttributeSet attrs) { super(context, attrs); } - public ScrollableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { + public ScrollableTabLayout(final Context context, final AttributeSet attrs, + final int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { + protected void onLayout(final boolean changed, final int l, final int t, final int r, + final int b) { super.onLayout(changed, l, t, r, b); remeasureTabs(); } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { + protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { super.onSizeChanged(w, h, oldw, oldh); layoutWidth = w; } @Override - public void addTab(@NonNull Tab tab, int position, boolean setSelected) { + public void addTab(@NonNull final Tab tab, final int position, final boolean setSelected) { super.addTab(tab, position, setSelected); hasMultipleTabs(); @@ -60,22 +59,23 @@ public void addTab(@NonNull Tab tab, int position, boolean setSelected) { } @Override - public void removeTabAt(int position) { + public void removeTabAt(final int position) { super.removeTabAt(position); hasMultipleTabs(); - // Removing a tab won't increase total tabs' width so tabMode won't have to change to SCROLLABLE + // Removing a tab won't increase total tabs' width + // so tabMode won't have to change to SCROLLABLE if (getTabMode() != MODE_FIXED) { remeasureTabs(); } } @Override - protected void onVisibilityChanged(View changedView, int visibility) { + protected void onVisibilityChanged(final View changedView, final int visibility) { super.onVisibilityChanged(changedView, visibility); - // Recheck content width in case some tabs have been added or removed while ScrollableTabLayout was invisible + // Check width if some tabs have been added/removed while ScrollableTabLayout was invisible // We don't have to check if it was GONE because then requestLayout() will be called if (changedView == this) { if (prevVisibility == View.INVISIBLE) { @@ -85,14 +85,16 @@ protected void onVisibilityChanged(View changedView, int visibility) { } } - private void setMode(int mode) { - if (mode == getTabMode()) return; + private void setMode(final int mode) { + if (mode == getTabMode()) { + return; + } setTabMode(mode); } /** - * Make ScrollableTabLayout not visible if there are less than two tabs + * Make ScrollableTabLayout not visible if there are less than two tabs. */ private void hasMultipleTabs() { if (getTabCount() > 1) { @@ -103,11 +105,15 @@ private void hasMultipleTabs() { } /** - * Calculate minimal width required by tabs and set tabMode accordingly + * Calculate minimal width required by tabs and set tabMode accordingly. */ private void remeasureTabs() { - if (prevVisibility != View.VISIBLE) return; - if (layoutWidth == 0) return; + if (prevVisibility != View.VISIBLE) { + return; + } + if (layoutWidth == 0) { + return; + } final int count = getTabCount(); int contentWidth = 0; diff --git a/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java index 3b0e18b0d..ca42a5607 100644 --- a/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/local/subscription/services/ImportExportJsonHelperTest.java @@ -7,6 +7,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -19,9 +20,11 @@ public class ImportExportJsonHelperTest { @Test public void testEmptySource() throws Exception { - String emptySource = "{\"app_version\":\"0.11.6\",\"app_version_int\": 47,\"subscriptions\":[]}"; + String emptySource = + "{\"app_version\":\"0.11.6\",\"app_version_int\": 47,\"subscriptions\":[]}"; - List items = ImportExportJsonHelper.readFrom(new ByteArrayInputStream(emptySource.getBytes("UTF-8")), null); + List items = ImportExportJsonHelper.readFrom(new ByteArrayInputStream( + emptySource.getBytes(StandardCharsets.UTF_8)), null); assertTrue(items.isEmpty()); } @@ -36,7 +39,7 @@ public void testInvalidSource() { for (String invalidContent : invalidList) { try { if (invalidContent != null) { - byte[] bytes = invalidContent.getBytes("UTF-8"); + byte[] bytes = invalidContent.getBytes(StandardCharsets.UTF_8); ImportExportJsonHelper.readFrom((new ByteArrayInputStream(bytes)), null); } else { ImportExportJsonHelper.readFrom(null, null); @@ -44,8 +47,10 @@ public void testInvalidSource() { fail("didn't throw exception"); } catch (Exception e) { - boolean isExpectedException = e instanceof SubscriptionExtractor.InvalidSourceException; - assertTrue("\"" + e.getClass().getSimpleName() + "\" is not the expected exception", isExpectedException); + boolean isExpectedException = e + instanceof SubscriptionExtractor.InvalidSourceException; + assertTrue("\"" + e.getClass().getSimpleName() + + "\" is not the expected exception", isExpectedException); } } } @@ -70,9 +75,9 @@ public void ultimateTest() throws Exception { final SubscriptionItem item1 = itemsFromFile.get(i); final SubscriptionItem item2 = itemsSecondRead.get(i); - final boolean equals = item1.getServiceId() == item2.getServiceId() && - item1.getUrl().equals(item2.getUrl()) && - item1.getName().equals(item2.getName()); + final boolean equals = item1.getServiceId() == item2.getServiceId() + && item1.getUrl().equals(item2.getUrl()) + && item1.getName().equals(item2.getName()); if (!equals) { fail("The list of items were different from each other"); @@ -81,8 +86,10 @@ public void ultimateTest() throws Exception { } private List readFromFile() throws Exception { - final InputStream inputStream = getClass().getClassLoader().getResourceAsStream("import_export_test.json"); - final List itemsFromFile = ImportExportJsonHelper.readFrom(inputStream, null); + final InputStream inputStream = getClass().getClassLoader().getResourceAsStream( + "import_export_test.json"); + final List itemsFromFile = ImportExportJsonHelper.readFrom( + inputStream, null); if (itemsFromFile == null || itemsFromFile.isEmpty()) { fail("ImportExportJsonHelper.readFrom(input) returned a null or empty list"); @@ -91,7 +98,7 @@ private List readFromFile() throws Exception { return itemsFromFile; } - private String testWriteTo(List itemsFromFile) throws Exception { + private String testWriteTo(final List itemsFromFile) throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); ImportExportJsonHelper.writeTo(itemsFromFile, out, null); final String jsonOut = out.toString("UTF-8"); @@ -103,9 +110,11 @@ private String testWriteTo(List itemsFromFile) throws Exceptio return jsonOut; } - private List readFromWriteTo(String jsonOut) throws Exception { - final ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonOut.getBytes("UTF-8")); - final List secondReadItems = ImportExportJsonHelper.readFrom(inputStream, null); + private List readFromWriteTo(final String jsonOut) throws Exception { + final ByteArrayInputStream inputStream = new ByteArrayInputStream( + jsonOut.getBytes(StandardCharsets.UTF_8)); + final List secondReadItems = ImportExportJsonHelper.readFrom( + inputStream, null); if (secondReadItems == null || secondReadItems.isEmpty()) { fail("second call to readFrom returned an empty list"); @@ -113,4 +122,4 @@ private List readFromWriteTo(String jsonOut) throws Exception return secondReadItems; } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/schabi/newpipe/report/ErrorActivityTest.java b/app/src/test/java/org/schabi/newpipe/report/ErrorActivityTest.java index ca6c76ff3..6c40df42d 100644 --- a/app/src/test/java/org/schabi/newpipe/report/ErrorActivityTest.java +++ b/app/src/test/java/org/schabi/newpipe/report/ErrorActivityTest.java @@ -11,7 +11,7 @@ import static org.junit.Assert.assertNull; /** - * Unit tests for {@link ErrorActivity} + * Unit tests for {@link ErrorActivity}. */ public class ErrorActivityTest { @Test @@ -32,7 +32,4 @@ public void getReturnActivity() { returnActivity = ErrorActivity.getReturnActivity(VideoDetailFragment.class); assertEquals(MainActivity.class, returnActivity); } - - - -} \ No newline at end of file +} diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java index 45c7c0fff..61a0daeec 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabTest.java @@ -17,4 +17,4 @@ public void checkIdDuplication() { assertTrue("Id was already used: " + type.getTabId(), added); } } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java index 1f951159f..68cee9b0d 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java @@ -36,10 +36,9 @@ public void testEmptyAndNullRead() throws TabsJsonHelper.InvalidJsonException { @Test public void testInvalidIdRead() throws TabsJsonHelper.InvalidJsonException { final int blankTabId = Tab.Type.BLANK.getTabId(); - final String emptyTabsJson = "{\"" + JSON_TABS_ARRAY_KEY + "\":[" + - "{\"" + JSON_TAB_ID_KEY + "\":" + blankTabId + "}," + - "{\"" + JSON_TAB_ID_KEY + "\":" + 12345678 + "}" + - "]}"; + final String emptyTabsJson = "{\"" + JSON_TABS_ARRAY_KEY + "\":[" + + "{\"" + JSON_TAB_ID_KEY + "\":" + blankTabId + "}," + + "{\"" + JSON_TAB_ID_KEY + "\":" + 12345678 + "}" + "]}"; final List items = TabsJsonHelper.getTabsFromJson(emptyTabsJson); assertEquals("Should ignore the tab with invalid id", 1, items.size()); @@ -61,7 +60,8 @@ public void testInvalidRead() { fail("didn't throw exception"); } catch (Exception e) { boolean isExpectedException = e instanceof TabsJsonHelper.InvalidJsonException; - assertTrue("\"" + e.getClass().getSimpleName() + "\" is not the expected exception", isExpectedException); + assertTrue("\"" + e.getClass().getSimpleName() + + "\" is not the expected exception", isExpectedException); } } } @@ -77,7 +77,7 @@ public void testEmptyAndNullSave() throws JsonParserException { assertTrue(isTabsArrayEmpty(returnedJson)); } - private boolean isTabsArrayEmpty(String returnedJson) throws JsonParserException { + private boolean isTabsArrayEmpty(final String returnedJson) throws JsonParserException { JsonObject jsonObject = JsonParser.object().from(returnedJson); assertTrue(jsonObject.containsKey(JSON_TABS_ARRAY_KEY)); return jsonObject.getArray(JSON_TABS_ARRAY_KEY).size() == 0; @@ -89,10 +89,12 @@ public void testSaveAndReading() throws JsonParserException { final Tab.BlankTab blankTab = new Tab.BlankTab(); final Tab.DefaultKioskTab defaultKioskTab = new Tab.DefaultKioskTab(); final Tab.SubscriptionsTab subscriptionsTab = new Tab.SubscriptionsTab(); - final Tab.ChannelTab channelTab = new Tab.ChannelTab(666, "https://example.org", "testName"); + final Tab.ChannelTab channelTab = new Tab.ChannelTab( + 666, "https://example.org", "testName"); final Tab.KioskTab kioskTab = new Tab.KioskTab(123, "trending_key"); - final List tabs = Arrays.asList(blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab); + final List tabs = Arrays.asList( + blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab); final String returnedJson = TabsJsonHelper.getJsonToSave(tabs); // Reading @@ -102,24 +104,30 @@ public void testSaveAndReading() throws JsonParserException { assertEquals(tabs.size(), tabsFromArray.size()); - final Tab.BlankTab blankTabFromReturnedJson = requireNonNull((Tab.BlankTab) Tab.from(((JsonObject) tabsFromArray.get(0)))); + final Tab.BlankTab blankTabFromReturnedJson = requireNonNull((Tab.BlankTab) Tab.from( + (JsonObject) tabsFromArray.get(0))); assertEquals(blankTab.getTabId(), blankTabFromReturnedJson.getTabId()); - final Tab.DefaultKioskTab defaultKioskTabFromReturnedJson = requireNonNull((Tab.DefaultKioskTab) Tab.from(((JsonObject) tabsFromArray.get(1)))); + final Tab.DefaultKioskTab defaultKioskTabFromReturnedJson = requireNonNull( + (Tab.DefaultKioskTab) Tab.from((JsonObject) tabsFromArray.get(1))); assertEquals(defaultKioskTab.getTabId(), defaultKioskTabFromReturnedJson.getTabId()); - final Tab.SubscriptionsTab subscriptionsTabFromReturnedJson = requireNonNull((Tab.SubscriptionsTab) Tab.from(((JsonObject) tabsFromArray.get(2)))); + final Tab.SubscriptionsTab subscriptionsTabFromReturnedJson = requireNonNull( + (Tab.SubscriptionsTab) Tab.from((JsonObject) tabsFromArray.get(2))); assertEquals(subscriptionsTab.getTabId(), subscriptionsTabFromReturnedJson.getTabId()); - final Tab.ChannelTab channelTabFromReturnedJson = requireNonNull((Tab.ChannelTab) Tab.from(((JsonObject) tabsFromArray.get(3)))); + final Tab.ChannelTab channelTabFromReturnedJson = requireNonNull((Tab.ChannelTab) Tab.from( + (JsonObject) tabsFromArray.get(3))); assertEquals(channelTab.getTabId(), channelTabFromReturnedJson.getTabId()); - assertEquals(channelTab.getChannelServiceId(), channelTabFromReturnedJson.getChannelServiceId()); + assertEquals(channelTab.getChannelServiceId(), + channelTabFromReturnedJson.getChannelServiceId()); assertEquals(channelTab.getChannelUrl(), channelTabFromReturnedJson.getChannelUrl()); assertEquals(channelTab.getChannelName(), channelTabFromReturnedJson.getChannelName()); - final Tab.KioskTab kioskTabFromReturnedJson = requireNonNull((Tab.KioskTab) Tab.from(((JsonObject) tabsFromArray.get(4)))); + final Tab.KioskTab kioskTabFromReturnedJson = requireNonNull((Tab.KioskTab) Tab.from( + (JsonObject) tabsFromArray.get(4))); assertEquals(kioskTab.getTabId(), kioskTabFromReturnedJson.getTabId()); assertEquals(kioskTab.getKioskServiceId(), kioskTabFromReturnedJson.getKioskServiceId()); assertEquals(kioskTab.getKioskId(), kioskTabFromReturnedJson.getKioskId()); } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index a6e7fc2c0..0baa2a167 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -13,7 +13,7 @@ public class ListHelperTest { private static final String BEST_RESOLUTION_KEY = "best_resolution"; - private static final List audioStreamsTestList = Arrays.asList( + private static final List AUDIO_STREAMS_TEST_LIST = Arrays.asList( new AudioStream("", MediaFormat.M4A, /**/ 128), new AudioStream("", MediaFormat.WEBMA, /**/ 192), new AudioStream("", MediaFormat.MP3, /**/ 64), @@ -25,7 +25,7 @@ public class ListHelperTest { new AudioStream("", MediaFormat.MP3, /**/ 192), new AudioStream("", MediaFormat.WEBMA, /**/ 320)); - private static final List videoStreamsTestList = Arrays.asList( + private static final List VIDEO_STREAMS_TEST_LIST = Arrays.asList( new VideoStream("", MediaFormat.MPEG_4, /**/ "720p"), new VideoStream("", MediaFormat.v3GPP, /**/ "240p"), new VideoStream("", MediaFormat.WEBM, /**/ "480p"), @@ -33,7 +33,7 @@ public class ListHelperTest { new VideoStream("", MediaFormat.MPEG_4, /**/ "360p"), new VideoStream("", MediaFormat.WEBM, /**/ "360p")); - private static final List videoOnlyStreamsTestList = Arrays.asList( + private static final List VIDEO_ONLY_STREAMS_TEST_LIST = Arrays.asList( new VideoStream("", MediaFormat.MPEG_4, /**/ "720p", true), new VideoStream("", MediaFormat.MPEG_4, /**/ "720p", true), new VideoStream("", MediaFormat.MPEG_4, /**/ "2160p", true), @@ -46,10 +46,16 @@ public class ListHelperTest { @Test public void getSortedStreamVideosListTest() { - List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, videoStreamsTestList, videoOnlyStreamsTestList, true); + List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, + VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true); - List expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); - //for (VideoStream videoStream : result) System.out.println(videoStream.resolution + " > " + MediaFormat.getSuffixById(videoStream.format) + " > " + videoStream.isVideoOnly); + List expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + "1080p", "1080p60", "1440p60", "2160p", "2160p60"); +// for (VideoStream videoStream : result) { +// System.out.println(videoStream.resolution + " > " +// + MediaFormat.getSuffixById(videoStream.format) + " > " +// + videoStream.isVideoOnly); +// } assertEquals(result.size(), expected.size()); for (int i = 0; i < result.size(); i++) { @@ -60,10 +66,14 @@ public void getSortedStreamVideosListTest() { // Reverse Order // ////////////////// - result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, videoStreamsTestList, videoOnlyStreamsTestList, false); - expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); + result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, + VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false); + expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", + "720p", "480p", "360p", "240p", "144p"); assertEquals(result.size(), expected.size()); - for (int i = 0; i < result.size(); i++) assertEquals(result.get(i).resolution, expected.get(i)); + for (int i = 0; i < result.size(); i++) { + assertEquals(result.get(i).resolution, expected.get(i)); + } } @Test @@ -72,10 +82,14 @@ public void getSortedStreamVideosExceptHighResolutionsTest() { // Don't show Higher resolutions // ////////////////////////////////// - List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, false, videoStreamsTestList, videoOnlyStreamsTestList, false); - List expected = Arrays.asList("1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); + List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, + false, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false); + List expected = Arrays.asList( + "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(result.size(), expected.size()); - for (int i = 0; i < result.size(); i++) assertEquals(result.get(i).resolution, expected.get(i)); + for (int i = 0; i < result.size(); i++) { + assertEquals(result.get(i).resolution, expected.get(i)); + } } @Test @@ -89,57 +103,68 @@ public void getDefaultResolutionTest() { new VideoStream("", MediaFormat.WEBM, /**/ "144p"), new VideoStream("", MediaFormat.MPEG_4, /**/ "360p"), new VideoStream("", MediaFormat.WEBM, /**/ "360p")); - VideoStream result = testList.get(ListHelper.getDefaultResolutionIndex("720p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); + VideoStream result = testList.get(ListHelper.getDefaultResolutionIndex( + "720p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); assertEquals("720p", result.resolution); assertEquals(MediaFormat.MPEG_4, result.getFormat()); // Have resolution and the format - result = testList.get(ListHelper.getDefaultResolutionIndex("480p", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + "480p", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); assertEquals("480p", result.resolution); assertEquals(MediaFormat.WEBM, result.getFormat()); // Have resolution but not the format - result = testList.get(ListHelper.getDefaultResolutionIndex("480p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + "480p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); assertEquals("480p", result.resolution); assertEquals(MediaFormat.WEBM, result.getFormat()); // Have resolution and the format - result = testList.get(ListHelper.getDefaultResolutionIndex("240p", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + "240p", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); assertEquals("240p", result.resolution); assertEquals(MediaFormat.WEBM, result.getFormat()); // The best resolution - result = testList.get(ListHelper.getDefaultResolutionIndex(BEST_RESOLUTION_KEY, BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + BEST_RESOLUTION_KEY, BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); assertEquals("720p", result.resolution); assertEquals(MediaFormat.MPEG_4, result.getFormat()); // Doesn't have the 60fps variant and format - result = testList.get(ListHelper.getDefaultResolutionIndex("720p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + "720p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); assertEquals("720p", result.resolution); assertEquals(MediaFormat.MPEG_4, result.getFormat()); // Doesn't have the 60fps variant - result = testList.get(ListHelper.getDefaultResolutionIndex("480p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + "480p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); assertEquals("480p", result.resolution); assertEquals(MediaFormat.WEBM, result.getFormat()); // Doesn't have the resolution, will return the best one - result = testList.get(ListHelper.getDefaultResolutionIndex("2160p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); + result = testList.get(ListHelper.getDefaultResolutionIndex( + "2160p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList)); assertEquals("720p", result.resolution); assertEquals(MediaFormat.MPEG_4, result.getFormat()); } @Test public void getHighestQualityAudioFormatTest() { - AudioStream stream = audioStreamsTestList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.M4A, audioStreamsTestList)); + AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex( + MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST)); assertEquals(320, stream.average_bitrate); assertEquals(MediaFormat.M4A, stream.getFormat()); - stream = audioStreamsTestList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.WEBMA, audioStreamsTestList)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex( + MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST)); assertEquals(320, stream.average_bitrate); assertEquals(MediaFormat.WEBMA, stream.getFormat()); - stream = audioStreamsTestList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, audioStreamsTestList)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex( + MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST)); assertEquals(192, stream.average_bitrate); assertEquals(MediaFormat.MP3, stream.getFormat()); } @@ -154,8 +179,10 @@ public void getHighestQualityAudioFormatPreferredAbsent() { List testList = Arrays.asList( new AudioStream("", MediaFormat.M4A, /**/ 128), new AudioStream("", MediaFormat.WEBMA, /**/ 192)); - // List doesn't contains this format, it should fallback to the highest bitrate audio no matter what format it is - AudioStream stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList)); + // List doesn't contains this format + // It should fallback to the highest bitrate audio no matter what format it is + AudioStream stream = testList.get(ListHelper.getHighestQualityAudioIndex( + MediaFormat.MP3, testList)); assertEquals(192, stream.average_bitrate); assertEquals(MediaFormat.WEBMA, stream.getFormat()); @@ -193,15 +220,18 @@ public void getHighestQualityAudioNull() { @Test public void getLowestQualityAudioFormatTest() { - AudioStream stream = audioStreamsTestList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.M4A, audioStreamsTestList)); + AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex( + MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST)); assertEquals(128, stream.average_bitrate); assertEquals(MediaFormat.M4A, stream.getFormat()); - stream = audioStreamsTestList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.WEBMA, audioStreamsTestList)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex( + MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST)); assertEquals(64, stream.average_bitrate); assertEquals(MediaFormat.WEBMA, stream.getFormat()); - stream = audioStreamsTestList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, audioStreamsTestList)); + stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex( + MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST)); assertEquals(64, stream.average_bitrate); assertEquals(MediaFormat.MP3, stream.getFormat()); } @@ -216,8 +246,10 @@ public void getLowestQualityAudioFormatPreferredAbsent() { List testList = new ArrayList<>(Arrays.asList( new AudioStream("", MediaFormat.M4A, /**/ 128), new AudioStream("", MediaFormat.WEBMA, /**/ 192))); - // List doesn't contains this format, it should fallback to the most compact audio no matter what format it is. - AudioStream stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList)); + // List doesn't contains this format + // It should fallback to the most compact audio no matter what format it is. + AudioStream stream = testList.get(ListHelper.getMostCompactAudioIndex( + MediaFormat.MP3, testList)); assertEquals(128, stream.average_bitrate); assertEquals(MediaFormat.M4A, stream.getFormat()); @@ -238,7 +270,8 @@ public void getLowestQualityAudioFormatPreferredAbsent() { new AudioStream("", MediaFormat.M4A, /**/ 192), new AudioStream("", MediaFormat.WEBMA, /**/ 192), new AudioStream("", MediaFormat.M4A, /**/ 192))); - // List doesn't contains this format, it should fallback to the most compact audio no matter what format it is. + // List doesn't contain this format + // It should fallback to the most compact audio no matter what format it is. stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList)); assertEquals(192, stream.average_bitrate); assertEquals(MediaFormat.WEBMA, stream.getFormat()); @@ -298,4 +331,4 @@ public void getVideoDefaultStreamIndexCombinations() { // Can't find a match assertEquals(-1, ListHelper.getVideoStreamIndex("100p", null, testList)); } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java b/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java index c652472d1..f5bb0c89a 100644 --- a/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java @@ -1,15 +1,17 @@ package org.schabi.newpipe.util; import org.junit.Test; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class QuadraticSliderStrategyTest { - private final static int STEP = 100; - private final static float DELTA = 1f / (float) STEP; + private static final int STEP = 100; + private static final float DELTA = 1f / (float) STEP; private final SliderStrategy.Quadratic standard = new SliderStrategy.Quadratic(0f, 100f, 50f, STEP); + @Test public void testLeftBound() { assertEquals(standard.progressOf(0), 0); diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml new file mode 100644 index 000000000..54a2cee26 --- /dev/null +++ b/checkstyle-suppressions.xml @@ -0,0 +1,12 @@ + + + + + + From 63bcc04effdd6946a738c70606832a97e429b7a6 Mon Sep 17 00:00:00 2001 From: wb9688 Date: Thu, 2 Apr 2020 13:51:10 +0200 Subject: [PATCH 275/663] Move things back to its original place --- .../newpipe/CheckForNewAppVersionTask.java | 2 +- .../newpipe/download/DownloadDialog.java | 22 +-- .../fragments/detail/VideoDetailFragment.java | 118 +++++++++------- .../fragments/list/BaseListFragment.java | 18 +-- .../list/channel/ChannelFragment.java | 27 ++-- .../list/playlist/PlaylistFragment.java | 2 +- .../fragments/list/search/SearchFragment.java | 68 +++++---- .../list/videos/RelatedVideosFragment.java | 21 +-- .../local/bookmark/BookmarkFragment.java | 41 +++--- .../history/StatisticsPlaylistFragment.java | 49 +++---- .../local/playlist/LocalPlaylistFragment.java | 4 +- .../SubscriptionsImportFragment.java | 10 +- .../services/BaseImportExportService.java | 19 ++- .../services/SubscriptionsImportService.java | 11 +- .../org/schabi/newpipe/player/BasePlayer.java | 108 +++++++------- .../newpipe/player/MainVideoPlayer.java | 3 + .../newpipe/player/ServicePlayerActivity.java | 9 +- .../schabi/newpipe/player/VideoPlayer.java | 77 ++++++---- .../newpipe/player/helper/CacheFactory.java | 9 +- .../helper/PlaybackParameterDialog.java | 96 +++++++------ .../newpipe/player/helper/PlayerHelper.java | 23 +-- .../player/playback/MediaSourceManager.java | 133 ++++++++++-------- .../playqueue/AbstractInfoPlayQueue.java | 25 ++-- .../newpipe/player/playqueue/PlayQueue.java | 11 +- .../player/playqueue/PlayQueueAdapter.java | 4 + .../resolver/VideoPlaybackResolver.java | 1 + .../schabi/newpipe/report/ErrorActivity.java | 38 ++--- .../PeertubeInstanceListFragment.java | 22 +-- .../settings/SelectChannelFragment.java | 54 ++++--- .../newpipe/settings/SelectKioskFragment.java | 19 +-- .../settings/tabs/ChooseTabsFragment.java | 13 +- .../org/schabi/newpipe/settings/tabs/Tab.java | 16 +-- .../newpipe/settings/tabs/TabsManager.java | 16 +-- .../org/schabi/newpipe/util/InfoCache.java | 6 +- .../schabi/newpipe/views/CollapsibleView.java | 32 +++-- 35 files changed, 629 insertions(+), 498 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java index 12797bd8e..1f2808bed 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java @@ -204,7 +204,7 @@ protected void onPostExecute(final String response) { * * @param versionName Name of new version * @param apkLocationUrl Url with the new apk - * @param versionCode V + * @param versionCode Code of new version */ private void compareAppVersionAndShowNotification(final String versionName, final String apkLocationUrl, diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index bdd358eaf..ac6ac0717 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -86,35 +86,41 @@ public class DownloadDialog extends DialogFragment private static final String TAG = "DialogFragment"; private static final boolean DEBUG = MainActivity.DEBUG; private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230; - private final CompositeDisposable disposables = new CompositeDisposable(); + @State - protected StreamInfo currentInfo; + StreamInfo currentInfo; @State - protected StreamSizeWrapper wrappedAudioStreams = StreamSizeWrapper.empty(); + StreamSizeWrapper wrappedAudioStreams = StreamSizeWrapper.empty(); @State - protected StreamSizeWrapper wrappedVideoStreams = StreamSizeWrapper.empty(); + StreamSizeWrapper wrappedVideoStreams = StreamSizeWrapper.empty(); @State - protected StreamSizeWrapper wrappedSubtitleStreams = StreamSizeWrapper.empty(); + StreamSizeWrapper wrappedSubtitleStreams = StreamSizeWrapper.empty(); @State - protected int selectedVideoIndex = 0; + int selectedVideoIndex = 0; @State - protected int selectedAudioIndex = 0; + int selectedAudioIndex = 0; @State - protected int selectedSubtitleIndex = 0; + int selectedSubtitleIndex = 0; + private StoredDirectoryHelper mainStorageAudio = null; private StoredDirectoryHelper mainStorageVideo = null; private DownloadManager downloadManager = null; private ActionMenuItemView okButton = null; private Context context; private boolean askForSavePath; + private StreamItemAdapter audioStreamsAdapter; private StreamItemAdapter videoStreamsAdapter; private StreamItemAdapter subtitleStreamsAdapter; + + private final CompositeDisposable disposables = new CompositeDisposable(); + private EditText nameEditText; private Spinner streamsSpinner; private RadioGroup radioStreamsGroup; private TextView threadsCountTextView; private SeekBar threadsSeekBar; + private SharedPreferences prefs; public static DownloadDialog newInstance(final StreamInfo info) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 43e22d597..9ad734cb5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -109,54 +109,55 @@ public class VideoDetailFragment extends BaseStateFragment implements BackPressable, SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener, View.OnLongClickListener { public static final String AUTO_PLAY = "auto_play"; + + private int updateFlags = 0; private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2; private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; private static final int COMMENTS_UPDATE_FLAG = 0x8; - private static final String COMMENTS_TAB_TAG = "COMMENTS"; - 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"; - /** - * Stack that contains the "navigation history".
- * The peek is the current video. - */ - private final LinkedList stack = new LinkedList<>(); + + private boolean autoPlayEnabled; + private boolean showRelatedStreams; + private boolean showComments; + private String selectedTabTag; + @State protected int serviceId = Constants.NO_SERVICE_ID; @State protected String name; @State protected String url; - private int updateFlags = 0; - private boolean autoPlayEnabled; - private boolean showRelatedStreams; - private boolean showComments; - private String selectedTabTag; - /*////////////////////////////////////////////////////////////////////////// - // Views - //////////////////////////////////////////////////////////////////////////*/ private StreamInfo currentInfo; private Disposable currentWorker; @NonNull private CompositeDisposable disposables = new CompositeDisposable(); @Nullable private Disposable positionSubscriber = null; + private List sortedVideoStreams; private int selectedVideoStreamIndex = -1; + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + private Menu menu; + private Spinner spinnerToolbar; + private LinearLayout contentRootLayoutHiding; + private View thumbnailBackgroundButton; private ImageView thumbnailImageView; private ImageView thumbnailPlayButton; private AnimatedProgressBar positionView; + private View videoTitleRoot; private TextView videoTitleTextView; private ImageView videoTitleToggleArrow; private TextView videoCountView; + private TextView detailControlsBackground; private TextView detailControlsPopup; private TextView detailControlsAddToPlaylist; @@ -164,30 +165,42 @@ public class VideoDetailFragment extends BaseStateFragment private TextView appendControlsDetail; private TextView detailDurationView; private TextView detailPositionView; + private LinearLayout videoDescriptionRootLayout; private TextView videoUploadDateView; private TextView videoDescriptionView; + private View uploaderRootLayout; private TextView uploaderTextView; private ImageView uploaderThumb; + private TextView thumbsUpTextView; private ImageView thumbsUpImageView; private TextView thumbsDownTextView; private ImageView thumbsDownImageView; private TextView thumbsDisabledTextView; + private AppBarLayout appBarLayout; private ViewPager viewPager; - - - /*////////////////////////////////////////////////////////////////////////*/ private TabAdaptor pageAdapter; - - /*////////////////////////////////////////////////////////////////////////// - // Fragment's Lifecycle - //////////////////////////////////////////////////////////////////////////*/ private TabLayout tabLayout; private FrameLayout relatedStreamsLayout; + /*////////////////////////////////////////////////////////////////////////*/ + + private static final String COMMENTS_TAB_TAG = "COMMENTS"; + 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"; + + /** + * Stack that contains the "navigation history".
+ * The peek is the current video. + */ + private final LinkedList stack = new LinkedList<>(); + public static VideoDetailFragment getInstance(final int serviceId, final String videoUrl, final String name) { VideoDetailFragment instance = new VideoDetailFragment(); @@ -195,6 +208,11 @@ public static VideoDetailFragment getInstance(final int serviceId, final String return instance; } + + /*////////////////////////////////////////////////////////////////////////// + // Fragment's Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -285,10 +303,6 @@ public void onDestroy() { disposables = null; } - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onDestroyView() { if (DEBUG) { @@ -336,6 +350,10 @@ public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, } } + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); @@ -351,10 +369,6 @@ public void onSaveInstanceState(final Bundle outState) { outState.putSerializable(STACK_KEY, stack); } - /*////////////////////////////////////////////////////////////////////////// - // OnClick - //////////////////////////////////////////////////////////////////////////*/ - @Override protected void onRestoreInstanceState(@NonNull final Bundle savedState) { super.onRestoreInstanceState(savedState); @@ -371,9 +385,12 @@ protected void onRestoreInstanceState(@NonNull final Bundle savedState) { //noinspection unchecked stack.addAll((Collection) serializable); } - } + /*////////////////////////////////////////////////////////////////////////// + // OnClick + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onClick(final View v) { if (isLoading.get() || currentInfo == null) { @@ -449,10 +466,6 @@ public boolean onLongClick(final View v) { return true; } - /*////////////////////////////////////////////////////////////////////////// - // Init - //////////////////////////////////////////////////////////////////////////*/ - private void toggleTitleAndDescription() { if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) { videoTitleTextView.setMaxLines(1); @@ -465,6 +478,10 @@ private void toggleTitleAndDescription() { } } + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + @Override protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); @@ -553,11 +570,6 @@ private View.OnTouchListener getOnControlsTouchListener() { }; } - - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - private void initThumbnailViews(@NonNull final StreamInfo info) { thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); if (!TextUtils.isEmpty(info.getThumbnailUrl())) { @@ -581,6 +593,10 @@ public void onLoadingFailed(final String imageUri, final View view, } } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreateOptionsMenu(final Menu m, final MenuInflater inflater) { this.menu = m; @@ -654,10 +670,6 @@ private void setupActionBarOnError(final String u) { Log.e("-----", "missing code"); } - /*////////////////////////////////////////////////////////////////////////// - // OwnStack - //////////////////////////////////////////////////////////////////////////*/ - private void setupActionBar(final StreamInfo info) { if (DEBUG) { Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]"); @@ -687,7 +699,11 @@ public void onNothingSelected(final AdapterView parent) { } }); } - public void pushToStack(final int sid, final String videoUrl, final String title) { + /*////////////////////////////////////////////////////////////////////////// + // OwnStack + //////////////////////////////////////////////////////////////////////////*/ + + private void pushToStack(final int sid, final String videoUrl, final String title) { if (DEBUG) { Log.d(TAG, "pushToStack() called with: serviceId = [" + sid + "], videoUrl = [" + videoUrl + "], title = [" + title + "]"); @@ -706,7 +722,7 @@ public void pushToStack(final int sid, final String videoUrl, final String title stack.push(new StackItem(sid, videoUrl, title)); } - public void setTitleToUrl(final int sid, final String videoUrl, final String title) { + private void setTitleToUrl(final int sid, final String videoUrl, final String title) { if (title != null && !title.isEmpty()) { for (StackItem stackItem : stack) { if (stack.peek().getServiceId() == sid @@ -755,7 +771,7 @@ public void selectAndLoadVideo(final int sid, final String videoUrl, final Strin prepareAndLoadInfo(); } - public void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) { + private void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) { if (DEBUG) { Log.d(TAG, "prepareAndHandleInfo() called with: " + "info = [" + info + "], scrollToTop = [" + scrollToTop + "]"); @@ -774,7 +790,7 @@ public void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTo } - protected void prepareAndLoadInfo() { + private void prepareAndLoadInfo() { appBarLayout.setExpanded(true, true); pushToStack(serviceId, url, name); startLoading(false); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 68937f078..55301dd50 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -44,20 +44,22 @@ public abstract class BaseListFragment extends BaseStateFragment implements ListViewContract, 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 //////////////////////////////////////////////////////////////////////////*/ - private static final int LIST_MODE_UPDATE_FLAG = 0x32; protected InfoListAdapter infoListAdapter; protected RecyclerView itemsList; - protected StateSaver.SavedState savedState; /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ - private boolean useDefaultStateSaving = true; - private int updateFlags = 0; @Override public void onAttach(final Context context) { @@ -81,10 +83,6 @@ public void onCreate(final Bundle savedInstanceState) { .registerOnSharedPreferenceChangeListener(this); } - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onDestroy() { super.onDestroy(); @@ -111,6 +109,10 @@ public void onResume() { } } + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + /** * If the default implementation of {@link StateSaver.WriteRead} should be used. * diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 0cd7fe32c..8c93ee293 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -70,6 +70,7 @@ public class ChannelFragment extends BaseListInfoFragment { /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ + private SubscriptionManager subscriptionManager; private View headerRootLayout; private ImageView headerChannelBanner; @@ -83,10 +84,6 @@ public class ChannelFragment extends BaseListInfoFragment { private LinearLayout headerBackgroundButton; private MenuItem menuRssButton; - /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - public static ChannelFragment getInstance(final int serviceId, final String url, final String name) { ChannelFragment instance = new ChannelFragment(); @@ -104,6 +101,10 @@ public void setUserVisibleHint(final boolean isVisibleToUser) { } } + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onAttach(final Context context) { super.onAttach(context); @@ -117,10 +118,6 @@ public View onCreateView(@NonNull final LayoutInflater inflater, return inflater.inflate(R.layout.fragment_channel, container, false); } - /*////////////////////////////////////////////////////////////////////////// - // Init - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onDestroy() { super.onDestroy(); @@ -133,7 +130,7 @@ public void onDestroy() { } /*////////////////////////////////////////////////////////////////////////// - // Menu + // Init //////////////////////////////////////////////////////////////////////////*/ protected View getListHeader() { @@ -154,6 +151,10 @@ protected View getListHeader() { return headerRootLayout; } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -179,10 +180,6 @@ private void openRssFeed() { } } - /*////////////////////////////////////////////////////////////////////////// - // Channel Subscription - //////////////////////////////////////////////////////////////////////////*/ - @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { @@ -208,6 +205,10 @@ public boolean onOptionsItemSelected(final MenuItem item) { return true; } + /*////////////////////////////////////////////////////////////////////////// + // Channel Subscription + //////////////////////////////////////////////////////////////////////////*/ + private void monitorSubscription(final ChannelInfo info) { final Consumer onError = (Throwable throwable) -> { animateView(headerSubscribeButton, false, 100); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 68836bbd0..e3eac27ca 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -294,7 +294,7 @@ public void handleResult(@NonNull final PlaylistInfo result) { } }); } - } else { // Else say we have no uploader + } else { // Otherwise say we have no uploader headerUploaderName.setText(R.string.playlist_no_uploader); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index ce84c1b57..718865f10 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -80,7 +80,6 @@ public class SearchFragment extends BaseListFragment implements BackPressable { - /*////////////////////////////////////////////////////////////////////////// // Search //////////////////////////////////////////////////////////////////////////*/ @@ -97,35 +96,45 @@ public class SearchFragment extends BaseListFragment suggestionPublisher = PublishSubject.create(); - private final CompositeDisposable disposables = new CompositeDisposable(); + @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; + boolean wasSearchFocused = false; + private Map menuItemToFilterName; private StreamingService service; private String currentPageUrl; private String nextPageUrl; private String contentCountry; private boolean isSuggestionsEnabled = true; + private Disposable searchDisposable; private Disposable suggestionDisposable; + private final CompositeDisposable disposables = new CompositeDisposable(); + private SuggestionListAdapter suggestionListAdapter; private HistoryRecordManager historyRecordManager; @@ -141,6 +150,7 @@ public class SearchFragment extends BaseListFragment objectsToSave) { super.writeTo(objectsToSave); @@ -358,10 +368,6 @@ public void readFrom(@NonNull final Queue savedObjects) throws Exception nextPageUrl = (String) savedObjects.poll(); } - /*////////////////////////////////////////////////////////////////////////// - // Init's - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onSaveInstanceState(final Bundle bundle) { searchString = searchEditText != null @@ -371,7 +377,7 @@ public void onSaveInstanceState(final Bundle bundle) { } /*////////////////////////////////////////////////////////////////////////// - // Menu + // Init's //////////////////////////////////////////////////////////////////////////*/ @Override @@ -390,6 +396,10 @@ public void reloadContent() { } } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -430,10 +440,6 @@ public boolean onOptionsItemSelected(final MenuItem item) { return true; } - /*////////////////////////////////////////////////////////////////////////// - // Search - //////////////////////////////////////////////////////////////////////////*/ - private void restoreFilterChecked(final Menu menu, final int itemId) { if (itemId != -1) { MenuItem item = menu.findItem(itemId); @@ -445,6 +451,10 @@ private void restoreFilterChecked(final Menu menu, final int itemId) { } } + /*////////////////////////////////////////////////////////////////////////// + // Search + //////////////////////////////////////////////////////////////////////////*/ + private void showSearchOnStart() { if (DEBUG) { Log.d(TAG, "showSearchOnStart() called, searchQuery → " diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java index 2f660c5b6..5d48afd15 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java @@ -34,16 +34,15 @@ public class RelatedVideosFragment extends BaseListInfoFragment> getPlaylistsSubscriber() { return new Subscriber>() { @Override @@ -229,9 +229,6 @@ public void onError(final Throwable exception) { public void onComplete() { } }; } - /////////////////////////////////////////////////////////////////////////// - // Fragment Error Handling - /////////////////////////////////////////////////////////////////////////// @Override public void handleResult(@NonNull final List result) { @@ -252,6 +249,10 @@ public void handleResult(@NonNull final List result) { hideLoading(); } + /////////////////////////////////////////////////////////////////////////// + // Fragment Error Handling + /////////////////////////////////////////////////////////////////////////// + @Override protected boolean onError(final Throwable exception) { if (super.onError(exception)) { @@ -263,10 +264,6 @@ protected boolean onError(final Throwable exception) { return true; } - /////////////////////////////////////////////////////////////////////////// - // Utils - /////////////////////////////////////////////////////////////////////////// - @Override protected void resetFragment() { super.resetFragment(); @@ -275,6 +272,10 @@ protected void resetFragment() { } } + /////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////// + private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) { showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid())); } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index bf1f776e4..18d832453 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -80,16 +80,16 @@ private List processResult(final List> getHistoryObserver() { return new Subscriber>() { @Override @@ -294,10 +298,6 @@ public void onComplete() { }; } - /////////////////////////////////////////////////////////////////////////// - // Statistics Loader - /////////////////////////////////////////////////////////////////////////// - @Override public void handleResult(@NonNull final List result) { super.handleResult(result); @@ -331,6 +331,10 @@ public void handleResult(@NonNull final List result) { hideLoading(); } + /////////////////////////////////////////////////////////////////////////// + // Fragment Error Handling + /////////////////////////////////////////////////////////////////////////// + @Override protected void resetFragment() { super.resetFragment(); @@ -338,9 +342,6 @@ protected void resetFragment() { databaseSubscription.cancel(); } } - /////////////////////////////////////////////////////////////////////////// - // Fragment Error Handling - /////////////////////////////////////////////////////////////////////////// @Override protected boolean onError(final Throwable exception) { @@ -353,6 +354,10 @@ protected boolean onError(final Throwable exception) { return true; } + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + private void toggleSortMode() { if (sortMode == StatisticSortMode.LAST_PLAYED) { sortMode = StatisticSortMode.MOST_PLAYED; @@ -370,10 +375,6 @@ private void toggleSortMode() { startLoading(true); } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - private PlayQueue getPlayQueueStartingAt(final StreamStatisticsEntry infoItem) { return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index c0b7b0ec2..d430afa5c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -56,12 +56,14 @@ public class LocalPlaylistFragment extends BaseLocalListFragment supportedSources; private String relatedUrl; + @StringRes private int instructionsString; - private TextView infoTextView; - private EditText inputText; /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ + + private TextView infoTextView; + private EditText inputText; private Button inputButton; public static SubscriptionsImportFragment getInstance(final int serviceId) { @@ -67,7 +69,7 @@ public static SubscriptionsImportFragment getInstance(final int serviceId) { return instance; } - public void setInitialData(final int serviceId) { + private void setInitialData(final int serviceId) { this.currentServiceId = serviceId; } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index cdabea2cb..16cd70cf2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -53,9 +53,16 @@ public abstract class BaseImportExportService extends Service { protected final String TAG = this.getClass().getSimpleName(); - private static final int NOTIFICATION_SAMPLING_PERIOD = 2500; + protected final CompositeDisposable disposables = new CompositeDisposable(); protected final PublishProcessor notificationUpdater = PublishProcessor.create(); + + protected NotificationManagerCompat notificationManager; + protected NotificationCompat.Builder notificationBuilder; + protected SubscriptionManager subscriptionManager; + + private static final int NOTIFICATION_SAMPLING_PERIOD = 2500; + protected final AtomicInteger currentProgress = new AtomicInteger(-1); protected final AtomicInteger maxProgress = new AtomicInteger(-1); protected final ImportExportEventListener eventListener = new ImportExportEventListener() { @@ -71,13 +78,7 @@ public void onItemCompleted(final String itemName) { notificationUpdater.onNext(itemName); } }; - protected NotificationManagerCompat notificationManager; - protected NotificationCompat.Builder notificationBuilder; - protected SubscriptionManager subscriptionManager; - /*////////////////////////////////////////////////////////////////////////// - // Notification Impl - //////////////////////////////////////////////////////////////////////////*/ protected Toast toast; @Nullable @@ -103,6 +104,10 @@ protected void disposeAll() { disposables.clear(); } + /*////////////////////////////////////////////////////////////////////////// + // Notification Impl + //////////////////////////////////////////////////////////////////////////*/ + protected abstract int getNotificationId(); @StringRes diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java index 379df7151..70d061d7e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java @@ -67,15 +67,18 @@ public class SubscriptionsImportService extends BaseImportExportService { */ public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription" + ".services.SubscriptionsImportService.IMPORT_COMPLETE"; + /** * How many extractions running in parallel. */ public static final int PARALLEL_EXTRACTIONS = 8; + /** * Number of items to buffer to mass-insert in the subscriptions table, * this leads to a better performance as we can then use db transactions. */ public static final int BUFFER_COUNT_BEFORE_INSERT = 50; + private Subscription subscription; private int currentMode; private int currentServiceId; @@ -131,10 +134,6 @@ protected int getNotificationId() { return 4568; } - /*////////////////////////////////////////////////////////////////////////// - // Imports - //////////////////////////////////////////////////////////////////////////*/ - @Override public int getTitle() { return R.string.import_ongoing; @@ -148,6 +147,10 @@ protected void disposeAll() { } } + /*////////////////////////////////////////////////////////////////////////// + // Imports + //////////////////////////////////////////////////////////////////////////*/ + private void startImport() { showToast(R.string.import_ongoing); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index d4a7e7851..601fd96bf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -99,10 +99,22 @@ @SuppressWarnings({"WeakerAccess"}) public abstract class BasePlayer implements Player.EventListener, PlaybackListener, ImageLoadingListener { - public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); @NonNull public static final String TAG = "BasePlayer"; + + public static final int STATE_PREFLIGHT = -1; + public static final int STATE_BLOCKED = 123; + public static final int STATE_PLAYING = 124; + public static final int STATE_BUFFERING = 125; + public static final int STATE_PAUSED = 126; + public static final int STATE_PAUSED_SEEK = 127; + public static final int STATE_COMPLETED = 128; + + /*////////////////////////////////////////////////////////////////////////// + // Intent + //////////////////////////////////////////////////////////////////////////*/ + @NonNull public static final String REPEAT_MODE = "repeat_mode"; @NonNull @@ -123,26 +135,43 @@ public abstract class BasePlayer implements public static final String START_PAUSED = "start_paused"; @NonNull public static final String SELECT_ON_APPEND = "select_on_append"; - /*////////////////////////////////////////////////////////////////////////// - // Intent - //////////////////////////////////////////////////////////////////////////*/ @NonNull public static final String IS_MUTED = "is_muted"; - public static final int STATE_PREFLIGHT = -1; - public static final int STATE_BLOCKED = 123; - public static final int STATE_PLAYING = 124; - public static final int STATE_BUFFERING = 125; - public static final int STATE_PAUSED = 126; - public static final int STATE_PAUSED_SEEK = 127; - public static final int STATE_COMPLETED = 128; - protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f}; - protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds - protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; /*////////////////////////////////////////////////////////////////////////// // Playback //////////////////////////////////////////////////////////////////////////*/ - protected static final int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds + + protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f}; + + protected PlayQueue playQueue; + protected PlayQueueAdapter playQueueAdapter; + + @Nullable + protected MediaSourceManager playbackManager; + + @Nullable + private PlayQueueItem currentItem; + @Nullable + private MediaSourceTag currentMetadata; + @Nullable + private Bitmap currentThumbnail; + + @Nullable + protected Toast errorToast; + + /*////////////////////////////////////////////////////////////////////////// + // Player + //////////////////////////////////////////////////////////////////////////*/ + + protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds + protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; + + protected SimpleExoPlayer simpleExoPlayer; + protected AudioReactor audioReactor; + protected MediaSessionManager mediaSessionManager; + + @NonNull protected final Context context; @NonNull @@ -158,39 +187,17 @@ public abstract class BasePlayer implements @NonNull private final LoadControl loadControl; - /*////////////////////////////////////////////////////////////////////////// - // Player - //////////////////////////////////////////////////////////////////////////*/ @NonNull private final RenderersFactory renderFactory; @NonNull private final SerialDisposable progressUpdateReactor; @NonNull private final CompositeDisposable databaseUpdateReactor; - protected PlayQueue playQueue; - protected PlayQueueAdapter playQueueAdapter; - @Nullable - protected MediaSourceManager playbackManager; - @Nullable - protected Toast errorToast; - protected SimpleExoPlayer simpleExoPlayer; - //////////////////////////////////////////////////////////////////////////*/ - protected AudioReactor audioReactor; - protected MediaSessionManager mediaSessionManager; - protected int currentState = STATE_PREFLIGHT; - @Nullable - private PlayQueueItem currentItem; - @Nullable - private MediaSourceTag currentMetadata; - @Nullable - private Bitmap currentThumbnail; private boolean isPrepared = false; private Disposable stateLoader; - /*////////////////////////////////////////////////////////////////////////// - // Thumbnail Loading - //////////////////////////////////////////////////////////////////////////*/ + protected int currentState = STATE_PREFLIGHT; public BasePlayer(@NonNull final Context context) { this.context = context; @@ -247,8 +254,7 @@ public void initPlayer(final boolean playOnReady) { registerBroadcastReceiver(); } - public void initListeners() { - } + public void initListeners() { } public void handleIntent(final Intent intent) { if (DEBUG) { @@ -324,10 +330,6 @@ public void handleIntent(final Intent intent) { /*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted); } - /*////////////////////////////////////////////////////////////////////////// - // Broadcast Receiver - //////////////////////////////////////////////////////////////////////////*/ - protected void initPlayback(@NonNull final PlayQueue queue, @Player.RepeatMode final int repeatMode, final float playbackSpeed, @@ -398,9 +400,12 @@ public void destroy() { databaseUpdateReactor.clear(); progressUpdateReactor.set(null); - } + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail Loading + //////////////////////////////////////////////////////////////////////////*/ + private void initThumbnail(final String url) { if (DEBUG) { Log.d(TAG, "Thumbnail - initThumbnail() called"); @@ -413,10 +418,6 @@ private void initThumbnail(final String url) { .loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this); } - /*////////////////////////////////////////////////////////////////////////// - // States Implementation - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onLoadingStarted(final String imageUri, final View view) { if (DEBUG) { @@ -453,6 +454,10 @@ public void onLoadingCancelled(final String imageUri, final View view) { currentThumbnail = null; } + /*////////////////////////////////////////////////////////////////////////// + // Broadcast Receiver + //////////////////////////////////////////////////////////////////////////*/ + /** * Add your action in the intentFilter. * @@ -488,6 +493,10 @@ protected void unregisterBroadcastReceiver() { } } + /*////////////////////////////////////////////////////////////////////////// + // States Implementation + //////////////////////////////////////////////////////////////////////////*/ + public void changeState(final int state) { if (DEBUG) { Log.d(TAG, "changeState() called with: state = [" + state + "]"); @@ -1328,6 +1337,7 @@ private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag metadata) { playQueue.append(autoQueue.getStreams()); } } + /*////////////////////////////////////////////////////////////////////////// // Getters and Setters //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 47ea9f4e3..6656ec29e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -1181,11 +1181,14 @@ public int getMaxGestureLength() { private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { private static final int MOVEMENT_THRESHOLD = 40; + private final boolean isVolumeGestureEnabled = PlayerHelper .isVolumeGestureEnabled(getApplicationContext()); private final boolean isBrightnessGestureEnabled = PlayerHelper .isBrightnessGestureEnabled(getApplicationContext()); + private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume(); + private boolean isMoving; @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 3a33772d6..6841389f4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -54,14 +54,19 @@ public abstract class ServicePlayerActivity extends AppCompatActivity View.OnClickListener, PlaybackParameterDialog.Callback { private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47; private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; + protected BasePlayer player; + private boolean serviceBound; private ServiceConnection serviceConnection; + + private boolean seeking; + private boolean redraw; + //////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////// - private boolean seeking; - private boolean redraw; + private View rootView; private RecyclerView itemsList; diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index b75d39b84..7e74a6ee2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -90,51 +90,69 @@ public abstract class VideoPlayer extends BasePlayer Player.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + public final String TAG; public static final boolean DEBUG = BasePlayer.DEBUG; - public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis /*////////////////////////////////////////////////////////////////////////// // Player //////////////////////////////////////////////////////////////////////////*/ + + public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds protected static final int RENDERER_UNAVAILABLE = -1; - public final String TAG; + @NonNull private final VideoPlaybackResolver resolver; - private final Handler controlsVisibilityHandler = new Handler(); - private final int qualityPopupMenuGroupId = 69; - private final int playbackSpeedPopupMenuGroupId = 79; + + private List availableStreams; + private int selectedStreamIndex; + + protected boolean wasPlaying = false; + /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ - private final int captionPopupMenuGroupId = 89; - protected boolean wasPlaying = false; - boolean isSomePopupMenuVisible = false; - private List availableStreams; - private int selectedStreamIndex; + private View rootView; + private AspectRatioFrameLayout aspectRatioFrameLayout; private SurfaceView surfaceView; private View surfaceForeground; + private View loadingPanel; private ImageView endScreen; private ImageView controlAnimationView; + private View controlsRoot; private TextView currentDisplaySeek; + private View bottomControlsRoot; private SeekBar playbackSeekBar; private TextView playbackCurrentTime; private TextView playbackEndTime; private TextView playbackLiveSync; private TextView playbackSpeedTextView; + private View topControlsRoot; private TextView qualityTextView; + private SubtitleView subtitleView; + private TextView resizeView; private TextView captionTextView; + private ValueAnimator controlViewAnimator; + private final Handler controlsVisibilityHandler = new Handler(); + + boolean isSomePopupMenuVisible = false; + + private final int qualityPopupMenuGroupId = 69; private PopupMenu qualityPopupMenu; + + private final int playbackSpeedPopupMenuGroupId = 79; private PopupMenu playbackSpeedPopupMenu; + + private final int captionPopupMenuGroupId = 89; private PopupMenu captionPopupMenu; /////////////////////////////////////////////////////////////////////////// @@ -238,10 +256,6 @@ public void initPlayer(final boolean playOnReady) { } } - /*////////////////////////////////////////////////////////////////////////// - // UI Builders - //////////////////////////////////////////////////////////////////////////*/ - @Override public void handleIntent(final Intent intent) { if (intent == null) { @@ -255,6 +269,10 @@ public void handleIntent(final Intent intent) { super.handleIntent(intent); } + /*////////////////////////////////////////////////////////////////////////// + // UI Builders + //////////////////////////////////////////////////////////////////////////*/ + public void buildQualityMenu() { if (qualityPopupMenu == null) { return; @@ -354,9 +372,6 @@ private void buildCaptionMenu(final List availableLanguages) { } captionPopupMenu.setOnDismissListener(this); } - /*////////////////////////////////////////////////////////////////////////// - // Playback Listener - //////////////////////////////////////////////////////////////////////////*/ private void updateStreamRelatedViews() { if (getCurrentMetadata() == null) { @@ -413,6 +428,10 @@ private void updateStreamRelatedViews() { playbackSpeedTextView.setVisibility(View.VISIBLE); } + /*////////////////////////////////////////////////////////////////////////// + // Playback Listener + //////////////////////////////////////////////////////////////////////////*/ + protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver(); protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { @@ -420,16 +439,16 @@ protected void onMetadataChanged(@NonNull final MediaSourceTag tag) { updateStreamRelatedViews(); } - /*////////////////////////////////////////////////////////////////////////// - // States Implementation - //////////////////////////////////////////////////////////////////////////*/ - @Override @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { return resolver.resolve(info); } + /*////////////////////////////////////////////////////////////////////////// + // States Implementation + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onBlocked() { super.onBlocked(); @@ -494,10 +513,6 @@ public void onPausedSeek() { showAndAnimateControl(-1, true); } - /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer Video Listener - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onCompleted() { super.onCompleted(); @@ -510,6 +525,10 @@ public void onCompleted() { animateView(surfaceForeground, true, 100); } + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer Video Listener + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onTracksChanged(final TrackGroupArray trackGroups, final TrackSelectionArray trackSelections) { @@ -537,15 +556,15 @@ public void onVideoSizeChanged(final int width, final int height, aspectRatioFrameLayout.setAspectRatio(((float) width) / height); } - /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer Track Updates - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onRenderedFirstFrame() { animateView(surfaceForeground, false, 100); } + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer Track Updates + //////////////////////////////////////////////////////////////////////////*/ + private void onTextTrackUpdate() { final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index dc3d9d269..2ef22f2eb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -20,17 +20,20 @@ /* package-private */ class CacheFactory implements DataSource.Factory { private static final String TAG = "CacheFactory"; + private static final String CACHE_FOLDER_NAME = "exoplayer"; private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR; + + private final DefaultDataSourceFactory dataSourceFactory; + private final File cacheDir; + private final long maxFileSize; + // Creating cache on every instance may cause problems with multiple players when // sources are not ExtractorMediaSource // see: https://stackoverflow.com/questions/28700391/using-cache-in-exoplayer // todo: make this a singleton? private static SimpleCache cache; - private final DefaultDataSourceFactory dataSourceFactory; - private final File cacheDir; - private final long maxFileSize; CacheFactory(@NonNull final Context context, @NonNull final String userAgent, diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 089ea456e..0d511d565 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -23,19 +23,23 @@ public class PlaybackParameterDialog extends DialogFragment { // Minimum allowable range in ExoPlayer - public static final double MINIMUM_PLAYBACK_VALUE = 0.10f; - public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - public static final char STEP_UP_SIGN = '+'; - public static final char STEP_DOWN_SIGN = '-'; - public static final double STEP_ONE_PERCENT_VALUE = 0.01f; - public static final double STEP_FIVE_PERCENT_VALUE = 0.05f; - public static final double STEP_TEN_PERCENT_VALUE = 0.10f; - public static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; - public static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; - public static final double DEFAULT_TEMPO = 1.00f; - public static final double DEFAULT_PITCH = 1.00f; - public static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; - public static final boolean DEFAULT_SKIP_SILENCE = false; + private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; + private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; + + private static final char STEP_UP_SIGN = '+'; + private static final char STEP_DOWN_SIGN = '-'; + + private static final double STEP_ONE_PERCENT_VALUE = 0.01f; + private static final double STEP_FIVE_PERCENT_VALUE = 0.05f; + private static final double STEP_TEN_PERCENT_VALUE = 0.10f; + private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; + private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; + + private static final double DEFAULT_TEMPO = 1.00f; + private static final double DEFAULT_PITCH = 1.00f; + private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; + private static final boolean DEFAULT_SKIP_SILENCE = false; + @NonNull private static final String TAG = "PlaybackParameterDialog"; @NonNull @@ -49,18 +53,22 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String PITCH_KEY = "pitch_key"; @NonNull private static final String STEP_SIZE_KEY = "step_size_key"; + @NonNull private final SliderStrategy strategy = new SliderStrategy.Quadratic( MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, /*centerAt=*/1.00f, /*sliderGranularity=*/10000); + @Nullable private Callback callback; + private double initialTempo = DEFAULT_TEMPO; private double initialPitch = DEFAULT_PITCH; private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; private double tempo = DEFAULT_TEMPO; private double pitch = DEFAULT_PITCH; private double stepSize = DEFAULT_STEP; + @Nullable private SeekBar tempoSlider; @Nullable @@ -96,25 +104,10 @@ public static PlaybackParameterDialog newInstance(final double playbackTempo, return dialog; } - @NonNull - private static String getStepUpPercentString(final double percent) { - return STEP_UP_SIGN + getPercentString(percent); - } - /*////////////////////////////////////////////////////////////////////////// // Lifecycle //////////////////////////////////////////////////////////////////////////*/ - @NonNull - private static String getStepDownPercentString(final double percent) { - return STEP_DOWN_SIGN + getPercentString(percent); - } - - @NonNull - private static String getPercentString(final double percent) { - return PlayerHelper.formatPitch(percent); - } - @Override public void onAttach(final Context context) { super.onAttach(context); @@ -125,10 +118,6 @@ public void onAttach(final Context context) { } } - /*////////////////////////////////////////////////////////////////////////// - // Dialog - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onCreate(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); @@ -143,10 +132,6 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { } } - /*////////////////////////////////////////////////////////////////////////// - // Control Views - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); @@ -158,6 +143,10 @@ public void onSaveInstanceState(final Bundle outState) { outState.putDouble(STEP_SIZE_KEY, getCurrentStepSize()); } + /*////////////////////////////////////////////////////////////////////////// + // Dialog + //////////////////////////////////////////////////////////////////////////*/ + @NonNull @Override public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { @@ -179,6 +168,10 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { return dialogBuilder.create(); } + /*////////////////////////////////////////////////////////////////////////// + // Control Views + //////////////////////////////////////////////////////////////////////////*/ + private void setupControlViews(@NonNull final View rootView) { setupHookingControl(rootView); setupSkipSilenceControl(rootView); @@ -273,10 +266,6 @@ private void setupSkipSilenceControl(@NonNull final View rootView) { } } - /*////////////////////////////////////////////////////////////////////////// - // Sliders - //////////////////////////////////////////////////////////////////////////*/ - private void setupStepSizeSelector(@NonNull final View rootView) { TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); @@ -355,6 +344,10 @@ private void setStepSize(final double stepSize) { } } + /*////////////////////////////////////////////////////////////////////////// + // Sliders + //////////////////////////////////////////////////////////////////////////*/ + private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() { return new SeekBar.OnSeekBarChangeListener() { @Override @@ -430,10 +423,6 @@ private void setSliders(final double newValue) { setPitchSlider(newValue); } - /*////////////////////////////////////////////////////////////////////////// - // Helper - //////////////////////////////////////////////////////////////////////////*/ - private void setTempoSlider(final double newTempo) { if (tempoSlider == null) { return; @@ -448,6 +437,10 @@ private void setPitchSlider(final double newPitch) { pitchSlider.setProgress(strategy.progressOf(newPitch)); } + /*////////////////////////////////////////////////////////////////////////// + // Helper + //////////////////////////////////////////////////////////////////////////*/ + private void setCurrentPlaybackParameters() { setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence()); } @@ -483,6 +476,21 @@ private boolean getCurrentSkipSilence() { return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); } + @NonNull + private static String getStepUpPercentString(final double percent) { + return STEP_UP_SIGN + getPercentString(percent); + } + + @NonNull + private static String getStepDownPercentString(final double percent) { + return STEP_DOWN_SIGN + getPercentString(percent); + } + + @NonNull + private static String getPercentString(final double percent) { + return PlayerHelper.formatPitch(percent); + } + public interface Callback { void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, boolean playbackSkipSilence); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index bc8955e74..db98ee6d3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -58,6 +58,10 @@ public final class PlayerHelper { private PlayerHelper() { } + //////////////////////////////////////////////////////////////////////////// + // Exposed helpers + //////////////////////////////////////////////////////////////////////////// + public static String getTimeString(final int milliSeconds) { int seconds = (milliSeconds % 60000) / 1000; int minutes = (milliSeconds % 3600000) / 60000; @@ -72,9 +76,6 @@ public static String getTimeString(final int milliSeconds) { ? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds).toString() : STRING_FORMATTER.format("%02d:%02d", minutes, seconds).toString(); } - //////////////////////////////////////////////////////////////////////////// - // Exposed helpers - //////////////////////////////////////////////////////////////////////////// public static String formatSpeed(final double speed) { return SPEED_FORMATTER.format(speed); @@ -177,14 +178,14 @@ public static PlayQueue autoQueueOf(@NonNull final StreamInfo info, ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0)); } - public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { - return isResumeAfterAudioFocusGain(context, false); - } - //////////////////////////////////////////////////////////////////////////// // Settings Resolution //////////////////////////////////////////////////////////////////////////// + public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { + return isResumeAfterAudioFocusGain(context, false); + } + public static boolean isVolumeGestureEnabled(@NonNull final Context context) { return isVolumeGestureEnabled(context, true); } @@ -322,15 +323,15 @@ public static void setScreenBrightness(@NonNull final Context context, setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis()); } + //////////////////////////////////////////////////////////////////////////// + // Private helpers + //////////////////////////////////////////////////////////////////////////// + @NonNull private static SharedPreferences getPreferences(@NonNull final Context context) { return PreferenceManager.getDefaultSharedPreferences(context); } - //////////////////////////////////////////////////////////////////////////// - // Private helpers - //////////////////////////////////////////////////////////////////////////// - private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context, final boolean b) { return getPreferences(context) diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 7bc9c34cc..af89d3f3d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -44,16 +44,21 @@ import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG; public class MediaSourceManager { + @NonNull + private final String TAG = "MediaSourceManager@" + hashCode(); + /** * Determines how many streams before and after the current stream should be loaded. * The default value (1) ensures seamless playback under typical network settings. - *

+ *

* The streams after the current will be loaded into the playlist timeline while the * streams before will only be cached for future usage. + *

* * @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource) */ private static final int WINDOW_SIZE = 1; + /** * Determines the maximum number of disposables allowed in the {@link #loaderReactor}. * Once exceeded, new calls to {@link #loadImmediate()} will evict all disposables in the @@ -63,12 +68,12 @@ public class MediaSourceManager { * @see #maybeLoadItem(PlayQueueItem) */ private static final int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1; - @NonNull - private final String TAG = "MediaSourceManager@" + hashCode(); + @NonNull private final PlaybackListener playbackListener; @NonNull private final PlayQueue playQueue; + /** * Determines the gap time between the playback position and the playback duration which * the {@link #getEdgeIntervalSignal()} begins to request loading. @@ -76,35 +81,45 @@ public class MediaSourceManager { * @see #progressUpdateIntervalMillis */ private final long playbackNearEndGapMillis; + /** * Determines the interval which the {@link #getEdgeIntervalSignal()} waits for between * each request for loading, once {@link #playbackNearEndGapMillis} has reached. */ private final long progressUpdateIntervalMillis; + @NonNull private final Observable nearEndIntervalSignal; + /** * Process only the last load order when receiving a stream of load orders (lessens I/O). - *

+ *

* The higher it is, the less loading occurs during rapid noncritical timeline changes. - *

+ *

+ *

* Not recommended to go below 100ms. + *

* * @see #loadDebounced() */ private final long loadDebounceMillis; + @NonNull private final Disposable debouncedLoader; @NonNull private final PublishSubject debouncedSignal; + + @NonNull + private Subscription playQueueReactor; + @NonNull private final CompositeDisposable loaderReactor; @NonNull private final Set loadingItems; + @NonNull private final AtomicBoolean isBlocked; - @NonNull - private Subscription playQueueReactor; + @NonNull private ManagedMediaSourcePlaylist playlist; @@ -160,42 +175,6 @@ private MediaSourceManager(@NonNull final PlaybackListener listener, // Exposed Methods //////////////////////////////////////////////////////////////////////////*/ - /*////////////////////////////////////////////////////////////////////////// - // Manager Helpers - //////////////////////////////////////////////////////////////////////////*/ - @Nullable - private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) { - // The current item has higher priority - final int currentIndex = playQueue.getIndex(); - final PlayQueueItem currentItem = playQueue.getItem(currentIndex); - if (currentItem == null) { - return null; - } - - // The rest are just for seamless playback - // Although timeline is not updated prior to the current index, these sources are still - // loaded into the cache for faster retrieval at a potentially later time. - final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE); - final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1; - final int rightBound = Math.min(playQueue.size(), rightLimit); - final Set neighbors = new ArraySet<>( - playQueue.getStreams().subList(leftBound, rightBound)); - - // Do a round robin - final int excess = rightLimit - playQueue.size(); - if (excess >= 0) { - neighbors.addAll(playQueue.getStreams() - .subList(0, Math.min(playQueue.size(), excess))); - } - neighbors.remove(currentItem); - - return new ItemsToLoad(currentItem, neighbors); - } - - /*////////////////////////////////////////////////////////////////////////// - // Event Reactor - //////////////////////////////////////////////////////////////////////////*/ - /** * Dispose the manager and releases all message buses and loaders. */ @@ -211,6 +190,10 @@ public void dispose() { loaderReactor.dispose(); } + /*////////////////////////////////////////////////////////////////////////// + // Event Reactor + //////////////////////////////////////////////////////////////////////////*/ + private Subscriber getReactor() { return new Subscriber() { @Override @@ -233,10 +216,6 @@ public void onComplete() { } }; } - /*////////////////////////////////////////////////////////////////////////// - // Playback Locking - //////////////////////////////////////////////////////////////////////////*/ - private void onPlayQueueChanged(final PlayQueueEvent event) { if (playQueue.isEmpty() && playQueue.isComplete()) { playbackListener.onPlaybackShutdown(); @@ -298,6 +277,10 @@ private void onPlayQueueChanged(final PlayQueueEvent event) { playQueueReactor.request(1); } + /*////////////////////////////////////////////////////////////////////////// + // Playback Locking + //////////////////////////////////////////////////////////////////////////*/ + private boolean isPlayQueueReady() { final boolean isWindowLoaded = playQueue.size() - playQueue.getIndex() > WINDOW_SIZE; return playQueue.isComplete() || isWindowLoaded; @@ -332,10 +315,6 @@ private void maybeBlock() { isBlocked.set(true); } - /*////////////////////////////////////////////////////////////////////////// - // Metadata Synchronization - //////////////////////////////////////////////////////////////////////////*/ - private void maybeUnblock() { if (DEBUG) { Log.d(TAG, "maybeUnblock() called."); @@ -347,6 +326,10 @@ private void maybeUnblock() { } } + /*////////////////////////////////////////////////////////////////////////// + // Metadata Synchronization + //////////////////////////////////////////////////////////////////////////*/ + private void maybeSync() { if (DEBUG) { Log.d(TAG, "maybeSync() called."); @@ -360,10 +343,6 @@ private void maybeSync() { playbackListener.onPlaybackSynchronize(currentItem); } - /*////////////////////////////////////////////////////////////////////////// - // MediaSource Loading - //////////////////////////////////////////////////////////////////////////*/ - private synchronized void maybeSynchronizePlayer() { if (isPlayQueueReady() && isPlaybackReady()) { maybeUnblock(); @@ -371,6 +350,10 @@ private synchronized void maybeSynchronizePlayer() { } } + /*////////////////////////////////////////////////////////////////////////// + // MediaSource Loading + //////////////////////////////////////////////////////////////////////////*/ + private Observable getEdgeIntervalSignal() { return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) @@ -523,9 +506,6 @@ private void maybeRenewCurrentIndex() { } playlist.invalidate(currentIndex, removeMediaSourceHandler, this::loadImmediate); } - /*////////////////////////////////////////////////////////////////////////// - // MediaSource Playlist Helpers - //////////////////////////////////////////////////////////////////////////*/ private void maybeClearLoaders() { if (DEBUG) { @@ -538,6 +518,10 @@ private void maybeClearLoaders() { } } + /*////////////////////////////////////////////////////////////////////////// + // MediaSource Playlist Helpers + //////////////////////////////////////////////////////////////////////////*/ + private void resetSources() { if (DEBUG) { Log.d(TAG, "resetSources() called."); @@ -554,6 +538,39 @@ private void populateSources() { } } + /*////////////////////////////////////////////////////////////////////////// + // Manager Helpers + //////////////////////////////////////////////////////////////////////////*/ + + @Nullable + private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) { + // The current item has higher priority + final int currentIndex = playQueue.getIndex(); + final PlayQueueItem currentItem = playQueue.getItem(currentIndex); + if (currentItem == null) { + return null; + } + + // The rest are just for seamless playback + // Although timeline is not updated prior to the current index, these sources are still + // loaded into the cache for faster retrieval at a potentially later time. + final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE); + final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1; + final int rightBound = Math.min(playQueue.size(), rightLimit); + final Set neighbors = new ArraySet<>( + playQueue.getStreams().subList(leftBound, rightBound)); + + // Do a round robin + final int excess = rightLimit - playQueue.size(); + if (excess >= 0) { + neighbors.addAll(playQueue.getStreams() + .subList(0, Math.min(playQueue.size(), excess))); + } + neighbors.remove(currentItem); + + return new ItemsToLoad(currentItem, neighbors); + } + private static class ItemsToLoad { @NonNull private final PlayQueueItem center; diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 9c77a6ef5..f0d6dc6ec 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -16,10 +16,11 @@ import io.reactivex.disposables.Disposable; abstract class AbstractInfoPlayQueue extends PlayQueue { - final int serviceId; - final String baseUrl; boolean isInitial; private boolean isComplete; + + final int serviceId; + final String baseUrl; String nextUrl; private transient Disposable fetchReactor; @@ -40,16 +41,6 @@ abstract class AbstractInfoPlayQueue ext this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); } - private static List extractListItems(final List infos) { - List result = new ArrayList<>(); - for (final InfoItem stream : infos) { - if (stream instanceof StreamInfoItem) { - result.add(new PlayQueueItem((StreamInfoItem) stream)); - } - } - return result; - } - protected abstract String getTag(); @Override @@ -134,4 +125,14 @@ public void dispose() { } fetchReactor = null; } + + private static List extractListItems(final List infos) { + List result = new ArrayList<>(); + for (final InfoItem stream : infos) { + if (stream instanceof StreamInfoItem) { + result.add(new PlayQueueItem((StreamInfoItem) stream)); + } + } + return result; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index 84ef45242..7de1d6422 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -36,17 +36,22 @@ *

* This class contains basic manipulation of a playlist while also functions as a * message bus, providing all listeners with new updates to the play queue. + *

*

* This class can be serialized for passing intents, but in order to start the * message bus, it must be initialized. + *

*/ public abstract class PlayQueue implements Serializable { - public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); - @NonNull - private final AtomicInteger queueIndex; + public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); + private ArrayList backup; private ArrayList streams; + + @NonNull + private final AtomicInteger queueIndex; + private transient BehaviorSubject eventBroadcast; private transient Flowable broadcastReceiver; private transient Subscription reportingReactor; diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java index 8028a5a9d..bf1361fc5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueAdapter.java @@ -28,19 +28,23 @@ *

* Copyright (C) Christian Schabesberger 2016 * InfoListAdapter.java is part of NewPipe. + *

*

* 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. + *

*

* 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. + *

*

* You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . + *

*/ public class PlayQueueAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index 3c131454d..2eb766769 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -32,6 +32,7 @@ public class VideoPlaybackResolver implements PlaybackResolver { private final PlayerDataSource dataSource; @NonNull private final QualityResolver qualityResolver; + @Nullable private String playbackQuality; diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index 19bf9d14d..46a816029 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -181,25 +181,6 @@ private static String[] elToSl(final List stackTraces) { return out; } - /** - * Get the checked activity. - * - * @param returnActivity the activity to return to - * @return the casted return activity or null - */ - @Nullable - static Class getReturnActivity(final Class returnActivity) { - Class checkedReturnActivity = null; - if (returnActivity != null) { - if (Activity.class.isAssignableFrom(returnActivity)) { - checkedReturnActivity = returnActivity.asSubclass(Activity.class); - } else { - checkedReturnActivity = MainActivity.class; - } - } - return checkedReturnActivity; - } - @Override protected void onCreate(final Bundle savedInstanceState) { assureCorrectAppLanguage(this); @@ -315,6 +296,25 @@ private String formErrorText(final String[] el) { return text.toString(); } + /** + * Get the checked activity. + * + * @param returnActivity the activity to return to + * @return the casted return activity or null + */ + @Nullable + static Class getReturnActivity(final Class returnActivity) { + Class checkedReturnActivity = null; + if (returnActivity != null) { + if (Activity.class.isAssignableFrom(returnActivity)) { + checkedReturnActivity = returnActivity.asSubclass(Activity.class); + } else { + checkedReturnActivity = MainActivity.class; + } + } + return checkedReturnActivity; + } + private void goToReturnActivity() { Class checkedReturnActivity = getReturnActivity(returnActivity); if (checkedReturnActivity == null) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 1242c39e8..03e246533 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -54,17 +54,20 @@ public class PeertubeInstanceListFragment extends Fragment { private static final int MENU_ITEM_RESTORE_ID = 123456; - public InstanceListAdapter instanceListAdapter; + private List instanceList = new ArrayList<>(); private PeertubeInstance selectedInstance; private String savedInstanceListKey; + private InstanceListAdapter instanceListAdapter; + private ProgressBar progressBar; private SharedPreferences sharedPreferences; + private CompositeDisposable disposables = new CompositeDisposable(); + /*////////////////////////////////////////////////////////////////////////// // Lifecycle //////////////////////////////////////////////////////////////////////////*/ - private CompositeDisposable disposables = new CompositeDisposable(); @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -122,9 +125,6 @@ public void onPause() { super.onPause(); saveChanges(); } - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ @Override public void onDestroy() { @@ -135,6 +135,10 @@ public void onDestroy() { disposables = null; } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -284,10 +288,6 @@ private void add(final PeertubeInstance instance) { instanceListAdapter.notifyDataSetChanged(); } - /*////////////////////////////////////////////////////////////////////////// - // List Handling - //////////////////////////////////////////////////////////////////////////*/ - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.START | ItemTouchHelper.END) { @@ -348,6 +348,10 @@ public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDi }; } + /*////////////////////////////////////////////////////////////////////////// + // List Handling + //////////////////////////////////////////////////////////////////////////*/ + private class InstanceListAdapter extends RecyclerView.Adapter { private final LayoutInflater inflater; diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index 2621a38e5..9ac3e2eda 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -57,18 +57,18 @@ public class SelectChannelFragment extends DialogFragment { /** * This contains the base display options for images. */ - public static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS + private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS = new DisplayImageOptions.Builder().cacheInMemory(true).build(); + private final ImageLoader imageLoader = ImageLoader.getInstance(); - OnSelectedLisener onSelectedLisener = null; - OnCancelListener onCancelListener = null; - private ProgressBar progressBar; - /*////////////////////////////////////////////////////////////////////////// - // Interfaces - //////////////////////////////////////////////////////////////////////////*/ + private OnSelectedLisener onSelectedLisener = null; + private OnCancelListener onCancelListener = null; + + private ProgressBar progressBar; private TextView emptyView; private RecyclerView recyclerView; + private List subscriptions = new Vector<>(); public void setOnSelectedLisener(final OnSelectedLisener listener) { @@ -79,6 +79,10 @@ public void setOnCancelListener(final OnCancelListener listener) { onCancelListener = listener; } + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { @@ -105,7 +109,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup } /*////////////////////////////////////////////////////////////////////////// - // Init + // Handle actions //////////////////////////////////////////////////////////////////////////*/ @Override @@ -116,11 +120,6 @@ public void onCancel(final DialogInterface dialogInterface) { } } - - /*////////////////////////////////////////////////////////////////////////// - // Handle actions - //////////////////////////////////////////////////////////////////////////*/ - private void clickedItem(final int position) { if (onSelectedLisener != null) { SubscriptionEntity entry = subscriptions.get(position); @@ -130,6 +129,10 @@ private void clickedItem(final int position) { dismiss(); } + /*////////////////////////////////////////////////////////////////////////// + // Item handling + //////////////////////////////////////////////////////////////////////////*/ + private void displayChannels(final List newSubscriptions) { this.subscriptions = newSubscriptions; progressBar.setVisibility(View.GONE); @@ -141,10 +144,6 @@ private void displayChannels(final List newSubscriptions) { } - /*////////////////////////////////////////////////////////////////////////// - // Item handling - //////////////////////////////////////////////////////////////////////////*/ - private Observer> getSubscriptionObserver() { return new Observer>() { @Override @@ -165,29 +164,28 @@ public void onComplete() { } }; } + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + protected void onError(final Throwable e) { final Activity activity = getActivity(); ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); } + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + public interface OnSelectedLisener { void onChannelSelected(int serviceId, String url, String name); } - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - public interface OnCancelListener { void onCancel(); } - - /*////////////////////////////////////////////////////////////////////////// - // ImageLoaderOptions - //////////////////////////////////////////////////////////////////////////*/ - private class SelectChannelAdapter extends RecyclerView.Adapter { @Override @@ -219,8 +217,8 @@ public int getItemCount() { public class SelectChannelItemHolder extends RecyclerView.ViewHolder { public final View view; - public final CircleImageView thumbnailView; - public final TextView titleView; + final CircleImageView thumbnailView; + final TextView titleView; SelectChannelItemHolder(final View v) { super(v); this.view = v; diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index 014e108f9..cb148c843 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -50,9 +50,6 @@ public class SelectKioskFragment extends DialogFragment { private RecyclerView recyclerView = null; private SelectKioskAdapter selectKioskAdapter = null; - /*////////////////////////////////////////////////////////////////////////// - // Interfaces - //////////////////////////////////////////////////////////////////////////*/ private OnSelectedLisener onSelectedLisener = null; private OnCancelListener onCancelListener = null; @@ -80,6 +77,10 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe return v; } + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCancel(final DialogInterface dialogInterface) { super.onCancel(dialogInterface); @@ -95,8 +96,8 @@ private void clickedItem(final SelectKioskAdapter.Entry entry) { dismiss(); } - /*////////////////////////////////////////////////////////////////////////// - // Handle actions + /*////////////////////////////////////////////////////////////////////////// + // Error //////////////////////////////////////////////////////////////////////////*/ protected void onError(final Throwable e) { @@ -105,6 +106,10 @@ protected void onError(final Throwable e) { .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); } + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + public interface OnSelectedLisener { void onKioskSelected(int serviceId, String kioskId, String kioskName); } @@ -113,10 +118,6 @@ public interface OnCancelListener { void onCancel(); } - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - private class SelectKioskAdapter extends RecyclerView.Adapter { private final List kioskList = new Vector<>(); diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index aafc05240..8a3a7f67e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -45,10 +45,11 @@ public class ChooseTabsFragment extends Fragment { private static final int MENU_ITEM_RESTORE_ID = 123456; - private ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; + private TabsManager tabsManager; private List tabList = new ArrayList<>(); + private ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; /*////////////////////////////////////////////////////////////////////////// // Lifecycle @@ -93,16 +94,16 @@ public void onResume() { updateTitle(); } - /*////////////////////////////////////////////////////////////////////////// - // Menu - //////////////////////////////////////////////////////////////////////////*/ - @Override public void onPause() { super.onPause(); saveChanges(); } + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); @@ -216,7 +217,7 @@ private void addTab(final int tabId) { } } - public ChooseTabListItem[] getAvailableTabs(final Context context) { + private ChooseTabListItem[] getAvailableTabs(final Context context) { final ArrayList returnList = new ArrayList<>(); for (Tab.Type type : Tab.Type.values()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index ef4e35aef..07e1c1cc3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -39,6 +39,10 @@ public abstract class Tab { readDataFromJson(jsonObject); } + /*////////////////////////////////////////////////////////////////////////// + // Tab Handling + //////////////////////////////////////////////////////////////////////////*/ + @Nullable public static Tab from(@NonNull final JsonObject jsonObject) { final int tabId = jsonObject.getInt(Tab.JSON_TAB_ID_KEY, -1); @@ -85,10 +89,6 @@ private static Tab from(final int tabId, @Nullable final JsonObject jsonObject) return type.getTab(); } - /*////////////////////////////////////////////////////////////////////////// - // JSON Handling - //////////////////////////////////////////////////////////////////////////*/ - public abstract int getTabId(); public abstract String getTabName(Context context); @@ -104,10 +104,6 @@ private static Tab from(final int tabId, @Nullable final JsonObject jsonObject) */ public abstract Fragment getFragment(Context context) throws ExtractionException; - /*////////////////////////////////////////////////////////////////////////// - // Tab Handling - //////////////////////////////////////////////////////////////////////////*/ - @Override public boolean equals(final Object obj) { if (obj == this) { @@ -118,6 +114,10 @@ public boolean equals(final Object obj) { && ((Tab) obj).getTabId() == this.getTabId(); } + /*////////////////////////////////////////////////////////////////////////// + // JSON Handling + //////////////////////////////////////////////////////////////////////////*/ + public void writeJsonOn(final JsonSink jsonSink) { jsonSink.object(); diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java index 4c8e0c06b..c76df7047 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java @@ -41,10 +41,6 @@ public void saveTabs(final List tabList) { sharedPreferences.edit().putString(savedTabsKey, jsonToSave).apply(); } - /*////////////////////////////////////////////////////////////////////////// - // Listener - //////////////////////////////////////////////////////////////////////////*/ - public void resetTabs() { sharedPreferences.edit().remove(savedTabsKey).apply(); } @@ -53,6 +49,10 @@ public List getDefaultTabs() { return TabsJsonHelper.getDefaultTabs(); } + /*////////////////////////////////////////////////////////////////////////// + // Listener + //////////////////////////////////////////////////////////////////////////*/ + public void setSavedTabsListener(final SavedTabsChangeListener listener) { if (preferenceChangeListener != null) { sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener); @@ -83,12 +83,4 @@ private SharedPreferences.OnSharedPreferenceChangeListener getPreferenceChangeLi public interface SavedTabsChangeListener { void onTabsChanged(); } - } - - - - - - - diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java index 03eae344a..035416dcd 100644 --- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java @@ -32,18 +32,20 @@ import java.util.Map; public final class InfoCache { + private final String TAG = getClass().getSimpleName(); private static final boolean DEBUG = MainActivity.DEBUG; + private static final InfoCache INSTANCE = new InfoCache(); private static final int MAX_ITEMS_ON_CACHE = 60; /** * Trim the cache to this size. */ private static final int TRIM_CACHE_TO = 30; + private static final LruCache LRU_CACHE = new LruCache<>(MAX_ITEMS_ON_CACHE); - private final String TAG = getClass().getSimpleName(); private InfoCache() { - //no instance + // no instance } public static InfoCache getInstance() { diff --git a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java index 71fc37e93..028e9b674 100644 --- a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java +++ b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java @@ -47,26 +47,26 @@ * A view that can be fully collapsed and expanded. */ public class CollapsibleView extends LinearLayout { - public static final int COLLAPSED = 0; - public static final int EXPANDED = 1; private static final String TAG = CollapsibleView.class.getSimpleName(); + private static final int ANIMATION_DURATION = 420; - private final List listeners = new ArrayList<>(); + + public static final int COLLAPSED = 0; + public static final int EXPANDED = 1; @State @ViewMode int currentState = COLLAPSED; - - /*////////////////////////////////////////////////////////////////////////// - // Collapse/expand logic - //////////////////////////////////////////////////////////////////////////*/ private boolean readyToChangeState; + private int targetHeight = -1; private ValueAnimator currentAnimator; + private final List listeners = new ArrayList<>(); public CollapsibleView(final Context context) { super(context); } + public CollapsibleView(final Context context, @Nullable final AttributeSet attrs) { super(context, attrs); } @@ -75,12 +75,17 @@ public CollapsibleView(final Context context, @Nullable final AttributeSet attrs final int defStyleAttr) { super(context, attrs, defStyleAttr); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public CollapsibleView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } + /*////////////////////////////////////////////////////////////////////////// + // Collapse/expand logic + //////////////////////////////////////////////////////////////////////////*/ + /** * This method recalculates the height of this view so it must be called when * some child changes (e.g. add new views, change text). @@ -198,6 +203,10 @@ public void removeListener(final StateListener listener) { listeners.remove(listener); } + /*////////////////////////////////////////////////////////////////////////// + // State Saving + //////////////////////////////////////////////////////////////////////////*/ + @Nullable @Override public Parcelable onSaveInstanceState() { @@ -212,7 +221,7 @@ public void onRestoreInstanceState(final Parcelable state) { } /*////////////////////////////////////////////////////////////////////////// - // State Saving + // Internal //////////////////////////////////////////////////////////////////////////*/ public String getDebugLogString(final String description) { @@ -226,12 +235,7 @@ public String getDebugLogString(final String description) { @Retention(SOURCE) @IntDef({COLLAPSED, EXPANDED}) - public @interface ViewMode { - } - - /*////////////////////////////////////////////////////////////////////////// - // Internal - //////////////////////////////////////////////////////////////////////////*/ + public @interface ViewMode { } /** * Simple interface used for listening state changes of the {@link CollapsibleView}. From 55480c8290d742533edc6c610022a099393d5a2b Mon Sep 17 00:00:00 2001 From: wb9688 Date: Thu, 2 Apr 2020 14:25:13 +0200 Subject: [PATCH 276/663] Disable VisibilityModifier in checkstyle.xml --- checkstyle.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index 8968d57c6..c1efe1dc1 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -159,10 +159,10 @@ - + From 45194061b3d5f72175734e2e7035c75e53012ad2 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Thu, 2 Apr 2020 11:05:19 -0300 Subject: [PATCH 277/663] Make the drawer layout adapt to any status bar size This improves the drawer specifically for phones that have a notch. --- app/src/main/res/layout/drawer_header.xml | 30 ++++++++++++++--------- app/src/main/res/values-v21/dimens.xml | 6 ----- app/src/main/res/values/dimens.xml | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) delete mode 100644 app/src/main/res/values-v21/dimens.xml diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml index f2a776659..4abf20c44 100644 --- a/app/src/main/res/layout/drawer_header.xml +++ b/app/src/main/res/layout/drawer_header.xml @@ -1,24 +1,30 @@ - + app:layout_constraintTop_toTopOf="parent"> + app:autoSizeTextType="uniform" + tools:ignore="UnusedAttribute" /> @@ -108,7 +115,8 @@