diff --git a/.github/workflows/image-minimizer.js b/.github/workflows/image-minimizer.js
index 8d10a1e77ae..80cc5294cba 100644
--- a/.github/workflows/image-minimizer.js
+++ b/.github/workflows/image-minimizer.js
@@ -4,7 +4,12 @@
module.exports = async ({github, context}) => {
const IGNORE_KEY = '';
const IGNORE_ALT_NAME_END = 'ignoreImageMinify';
+ // Targeted maximum height
const IMG_MAX_HEIGHT_PX = 600;
+ // maximum width of GitHub issues/comments
+ const IMG_MAX_WIDTH_PX = 800;
+ // all images that have a lower aspect ratio (-> have a smaller width) than this will be minimized
+ const MIN_ASPECT_RATIO = IMG_MAX_WIDTH_PX / IMG_MAX_HEIGHT_PX
// Get the body of the image
let initialBody = null;
@@ -38,6 +43,8 @@ module.exports = async ({github, context}) => {
// Require the probe lib for getting the image dimensions
const probe = require('probe-image-size');
+
+ var wasMatchModified = false;
// Try to find and replace the images with minimized ones
let newBody = await replaceAsync(initialBody, REGEX_IMAGE_LOOKUP, async (match, g1, g2) => {
@@ -48,7 +55,7 @@ module.exports = async ({github, context}) => {
return match;
}
- let shouldModifiy = false;
+ let shouldModify = false;
try {
console.log(`Probing ${g2}`);
let probeResult = await probe(g2);
@@ -58,15 +65,26 @@ module.exports = async ({github, context}) => {
if (probeResult.hUnits != 'px') {
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
}
+ if (probeResult.height <= 0) {
+ throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
+ }
+ if (probeResult.wUnits != 'px') {
+ throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
+ }
+ if (probeResult.width <= 0) {
+ throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
+ }
+ console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
- shouldModifiy = probeResult.height > IMG_MAX_HEIGHT_PX;
+ shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && (probeResult.width / probeResult.height) < MIN_ASPECT_RATIO;
} catch(e) {
console.log('Probing failed:', e);
// Immediately abort
return match;
}
- if (shouldModifiy) {
+ if (shouldModify) {
+ wasMatchModified = true;
console.log(`Modifying match '${match}'`);
return ``;
}
@@ -74,6 +92,11 @@ module.exports = async ({github, context}) => {
console.log(`Match '${match}' is ok/will not be modified`);
return match;
});
+
+ if (!wasMatchModified) {
+ console.log('Nothing was modified. Skipping update');
+ return;
+ }
// Update the corresponding element
if (context.eventName == 'issue_comment') {
diff --git a/app/build.gradle b/app/build.gradle
index bbd8cf4152e..633da37bdd0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ plugins {
}
android {
- compileSdk 30
+ compileSdk 31
buildToolsVersion '30.0.3'
defaultConfig {
@@ -16,8 +16,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdk 19
targetSdk 29
- versionCode 983
- versionName "0.22.0"
+ versionCode 984
+ versionName "0.22.1"
multiDexEnabled true
@@ -260,7 +260,7 @@ dependencies {
implementation "com.nononsenseapps:filepicker:4.2.1"
// Crash reporting
- implementation "ch.acra:acra-core:5.7.0"
+ implementation "ch.acra:acra-core:5.8.4"
// Properly restarting
implementation 'com.jakewharton:process-phoenix:2.1.2'
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index 6c02b6f5750..54e0af8c65f 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -13,13 +13,8 @@
import com.jakewharton.processphoenix.ProcessPhoenix;
import org.acra.ACRA;
-import org.acra.config.ACRAConfigurationException;
-import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder;
-import org.schabi.newpipe.error.ErrorInfo;
-import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
-import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.ktx.ExceptionUtils;
@@ -210,16 +205,9 @@ protected void initACRA() {
return;
}
- try {
- final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
- .setBuildConfigClass(BuildConfig.class)
- .build();
- ACRA.init(this, acraConfig);
- } catch (final ACRAConfigurationException exception) {
- exception.printStackTrace();
- ErrorUtil.openActivity(this, new ErrorInfo(exception,
- UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report"));
- }
+ final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder(this)
+ .withBuildConfigClass(BuildConfig.class);
+ ACRA.init(this, acraConfig);
}
private void initNotificationChannels() {
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 2d9abc6dc67..78f0bfffbd9 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
@@ -1994,9 +1994,7 @@ private void showSystemUi() {
// Prevent jumping of the player on devices with cutout
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
- isMultiWindowOrFullscreen()
- ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
- : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
}
activity.getWindow().getDecorView().setSystemUiVisibility(0);
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -2018,9 +2016,7 @@ private void hideSystemUi() {
// Prevent jumping of the player on devices with cutout
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
- isMultiWindowOrFullscreen()
- ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
- : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -2037,7 +2033,7 @@ private void hideSystemUi() {
activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
- && isMultiWindowOrFullscreen()) {
+ && (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) {
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
@@ -2053,11 +2049,6 @@ public void hideSystemUiIfNeeded() {
}
}
- private boolean isMultiWindowOrFullscreen() {
- return DeviceUtils.isInMultiWindow(activity)
- || (isPlayerAvailable() && player.isFullscreen());
- }
-
private boolean playerIsNotStopped() {
return isPlayerAvailable() && !player.isStopped();
}
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 3c2e65bb7da..6ea0a8a0d5b 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
@@ -17,10 +17,8 @@
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
@@ -44,6 +42,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
+import java.util.function.Supplier;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
@@ -79,11 +78,6 @@ public void onAttach(@NonNull final Context context) {
}
}
- @Override
- public void onDetach() {
- super.onDetach();
- }
-
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -220,14 +214,10 @@ public void onStart() {
//////////////////////////////////////////////////////////////////////////*/
@Nullable
- protected ViewBinding getListHeader() {
+ protected Supplier getListHeaderSupplier() {
return null;
}
- protected ViewBinding getListFooter() {
- return PignateFooterBinding.inflate(activity.getLayoutInflater(), itemsList, false);
- }
-
protected RecyclerView.LayoutManager getListLayoutManager() {
return new SuperScrollLayoutManager(activity);
}
@@ -252,11 +242,10 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
- infoListAdapter.setFooter(getListFooter().getRoot());
- final ViewBinding listHeader = getListHeader();
- if (listHeader != null) {
- infoListAdapter.setHeader(listHeader.getRoot());
+ final Supplier listHeaderSupplier = getListHeaderSupplier();
+ if (listHeaderSupplier != null) {
+ infoListAdapter.setHeaderSupplier(listHeaderSupplier);
}
itemsList.setAdapter(infoListAdapter);
@@ -271,7 +260,7 @@ protected void onItemSelected(final InfoItem selectedItem) {
@Override
protected void initListeners() {
super.initListeners();
- infoListAdapter.setOnStreamSelectedListener(new OnClickGesture() {
+ infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<>() {
@Override
public void selected(final StreamInfoItem selectedItem) {
onStreamSelected(selectedItem);
@@ -315,22 +304,98 @@ public void selected(final PlaylistInfoItem selectedItem) {
}
});
- infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture() {
+ infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<>() {
@Override
public void selected(final CommentsInfoItem selectedItem) {
onItemSelected(selectedItem);
}
});
+ // Ensure that there is always a scroll listener (e.g. when rotating the device)
+ useNormalItemListScrollListener();
+ }
+
+ /**
+ * Removes all listeners and adds the normal scroll listener to the {@link #itemsList}.
+ */
+ protected void useNormalItemListScrollListener() {
+ if (DEBUG) {
+ Log.d(TAG, "useNormalItemListScrollListener called");
+ }
+ itemsList.clearOnScrollListeners();
+ itemsList.addOnScrollListener(new DefaultItemListOnScrolledDownListener());
+ }
+
+ /**
+ * Removes all listeners and adds the initial scroll listener to the {@link #itemsList}.
+ *
+ * Which tries to load more items when not enough are in the view (not scrollable)
+ * and more are available.
+ *
+ * Note: This method only works because "This callback will also be called if visible
+ * item range changes after a layout calculation. In that case, dx and dy will be 0."
+ * - which might be unexpected because no actual scrolling occurs...
+ *
+ * This listener will be replaced by DefaultItemListOnScrolledDownListener when
+ *
+ * - the view was actually scrolled
+ * - the view is scrollable
+ * - no more items can be loaded
+ *
+ */
+ protected void useInitialItemListLoadScrollListener() {
+ if (DEBUG) {
+ Log.d(TAG, "useInitialItemListLoadScrollListener called");
+ }
itemsList.clearOnScrollListeners();
- itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
+ itemsList.addOnScrollListener(new DefaultItemListOnScrolledDownListener() {
@Override
- public void onScrolledDown(final RecyclerView recyclerView) {
- onScrollToBottom();
+ public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+
+ if (dy != 0) {
+ log("Vertical scroll occurred");
+
+ useNormalItemListScrollListener();
+ return;
+ }
+ if (isLoading.get()) {
+ log("Still loading data -> Skipping");
+ return;
+ }
+ if (!hasMoreItems()) {
+ log("No more items to load");
+
+ useNormalItemListScrollListener();
+ return;
+ }
+ if (itemsList.canScrollVertically(1)
+ || itemsList.canScrollVertically(-1)) {
+ log("View is scrollable");
+
+ useNormalItemListScrollListener();
+ return;
+ }
+
+ log("Loading more data");
+ loadMoreItems();
+ }
+
+ private void log(final String msg) {
+ if (DEBUG) {
+ Log.d(TAG, "initItemListLoadScrollListener - " + msg);
+ }
}
});
}
+ class DefaultItemListOnScrolledDownListener extends OnScrollBelowItemsListener {
+ @Override
+ public void onScrolledDown(final RecyclerView recyclerView) {
+ onScrollToBottom();
+ }
+ }
+
private void onStreamSelected(final StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
@@ -418,6 +483,12 @@ public void onCreateOptionsMenu(@NonNull final Menu menu,
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
+ @Override
+ protected void startLoading(final boolean forceLoad) {
+ useInitialItemListLoadScrollListener();
+ super.startLoading(forceLoad);
+ }
+
protected abstract void loadMoreItems();
protected abstract boolean hasMoreItems();
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 e98dc9fdadf..ebd586e354f 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
@@ -65,7 +65,7 @@ public void onResume() {
super.onResume();
// Check if it was loading when the fragment was stopped/paused,
if (wasLoading.getAndSet(false)) {
- if (hasMoreItems() && infoListAdapter.getItemsList().size() > 0) {
+ if (hasMoreItems() && !infoListAdapter.getItemsList().isEmpty()) {
loadMoreItems();
} else {
doInitialLoadLogic();
@@ -105,6 +105,7 @@ public void readFrom(@NonNull final Queue