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 eddefaa8c..6ab9d8995 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 @@ -11,11 +11,13 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.ContentObserver; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import androidx.core.app.ActivityCompat; import androidx.core.text.HtmlCompat; import androidx.preference.PreferenceManager; import android.provider.Settings; @@ -2002,8 +2004,8 @@ public class VideoDetailFragment WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; } activity.getWindow().getDecorView().setSystemUiVisibility(0); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - if (Build.VERSION.SDK_INT >= 30 /*Android 11*/) { + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.getWindow().setStatusBarColor(ThemeHelper.resolveColorFromAttr( requireContext(), android.R.attr.colorPrimary)); } @@ -2021,18 +2023,27 @@ public class VideoDetailFragment // Prevent jumping of the player on devices with cutout if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { activity.getWindow().getAttributes().layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + // In multiWindow mode status bar is not transparent for devices with cutout + // if I include this flag. So without it is better in this case + if (!isInMultiWindow()) { + visibility |= View.SYSTEM_UI_FLAG_FULLSCREEN; + } activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - activity.getWindow().setFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && (isInMultiWindow() || (player != null && player.isFullscreen()))) { + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + activity.getWindow().setNavigationBarColor( + ActivityCompat.getColor(activity, R.color.video_overlay_color)); + } + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } // Listener implementation diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java index ede476653..40d4a9cd4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -27,23 +27,22 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.PixelFormat; -import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Handler; +import android.view.DisplayCutout; +import androidx.annotation.ColorInt; +import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceManager; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import android.view.Display; import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -103,7 +102,6 @@ import org.schabi.newpipe.util.ShareUtils; import java.util.List; import static android.content.Context.WINDOW_SERVICE; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; @@ -339,7 +337,6 @@ public class VideoPlayerImpl extends VideoPlayer * This method ensures that popup and main players have different look. * We use one layout for both players and need to decide what to show and what to hide. * Additional measuring should be done inside {@link #setupElementsSize}. - * {@link #setControlsSize} is used to adapt the UI to fullscreen mode, multiWindow, navBar, etc */ private void setupElementsVisibility() { if (popupPlayerSelected()) { @@ -474,6 +471,17 @@ public class VideoPlayerImpl extends VideoPlayer Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, settingsContentObserver); getRootView().addOnLayoutChangeListener(this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + queueLayout.setOnApplyWindowInsetsListener((view, windowInsets) -> { + final DisplayCutout cutout = windowInsets.getDisplayCutout(); + if (cutout != null) { + view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), + cutout.getSafeInsetRight(), cutout.getSafeInsetBottom()); + } + return windowInsets; + }); + } } public boolean onKeyDown(final int keyCode) { @@ -746,7 +754,8 @@ public class VideoPlayerImpl extends VideoPlayer } isFullscreen = !isFullscreen; - setControlsSize(); + // Prevent applying windows insets twice (open vertical video to reproduce) + getRootView().findViewById(R.id.playbackControlRoot).setPadding(0, 0, 0, 0); fragmentListener.onFullscreenStateChanged(isFullscreen()); } @@ -1264,20 +1273,6 @@ public class VideoPlayerImpl extends VideoPlayer updatePopupSize(getPopupLayoutParams().width, -1); checkPopupPositionBounds(); } - - // The only situation I need to re-calculate elements sizes is - // when a user rotates a device from landscape to landscape - // because in that case the controls should be aligned to another side of a screen. - // The problem is when user leaves the app and returns back - // (while the app in landscape) Android reports via DisplayMetrics that orientation - // is portrait and it gives wrong sizes calculations. - // Let's skip re-calculation in every case but landscape - final boolean reportedOrientationIsLandscape = service.isLandscape(); - final boolean actualOrientationIsLandscape = context.getResources() - .getConfiguration().orientation == ORIENTATION_LANDSCAPE; - if (reportedOrientationIsLandscape && actualOrientationIsLandscape) { - setControlsSize(); - } // Close it because when changing orientation from portrait // (in fullscreen mode) the size of queue layout can be larger than the screen size onQueueClosed(); @@ -1471,14 +1466,19 @@ public class VideoPlayerImpl extends VideoPlayer } private void showSystemUIPartially() { - if (isFullscreen() && getParentActivity() != null) { + final AppCompatActivity activity = getParentActivity(); + if (isFullscreen() && activity != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + @ColorInt final int systemUiColor = + ActivityCompat.getColor(service, R.color.video_overlay_color); + activity.getWindow().setStatusBarColor(systemUiColor); + activity.getWindow().setNavigationBarColor(systemUiColor); + } final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - getParentActivity().getWindow().getDecorView().setSystemUiVisibility(visibility); - if (Build.VERSION.SDK_INT >= 30 /*Android 11*/) { - getParentActivity().getWindow().setStatusBarColor(Color.TRANSPARENT); - } + activity.getWindow().getDecorView().setSystemUiVisibility(visibility); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } @@ -1489,92 +1489,6 @@ public class VideoPlayerImpl extends VideoPlayer } } - /** - * Measures width and height of controls visible on screen. - * It ensures that controls will be side-by-side with NavigationBar and notches - * but not under them. Tablets have only bottom NavigationBar - */ - public void setControlsSize() { - final Point size = new Point(); - final Display display = getRootView().getDisplay(); - if (display == null || !videoPlayerSelected()) { - return; - } - // This method will give a correct size of a usable area of a window. - // It doesn't include NavigationBar, notches, etc. - display.getSize(size); - - final boolean isLandscape = service.isLandscape(); - final int width = isFullscreen - ? (isLandscape ? size.x : size.y) - : ViewGroup.LayoutParams.MATCH_PARENT; - final int gravity = isFullscreen - ? (display.getRotation() == Surface.ROTATION_90 - ? Gravity.START : Gravity.END) - : Gravity.TOP; - - getTopControlsRoot().getLayoutParams().width = width; - final RelativeLayout.LayoutParams topParams = - ((RelativeLayout.LayoutParams) getTopControlsRoot().getLayoutParams()); - topParams.removeRule(RelativeLayout.ALIGN_PARENT_START); - topParams.removeRule(RelativeLayout.ALIGN_PARENT_END); - topParams.addRule(gravity == Gravity.END - ? RelativeLayout.ALIGN_PARENT_END - : RelativeLayout.ALIGN_PARENT_START); - getTopControlsRoot().requestLayout(); - - getBottomControlsRoot().getLayoutParams().width = width; - final RelativeLayout.LayoutParams bottomParams = - ((RelativeLayout.LayoutParams) getBottomControlsRoot().getLayoutParams()); - bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_START); - bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_END); - bottomParams.addRule(gravity == Gravity.END - ? RelativeLayout.ALIGN_PARENT_END - : RelativeLayout.ALIGN_PARENT_START); - getBottomControlsRoot().requestLayout(); - - final ViewGroup controlsRoot = getRootView().findViewById(R.id.playbackWindowRoot); - // In tablet navigationBar located at the bottom of the screen. - // And the situations when we need to set custom height is - // in fullscreen mode in tablet in non-multiWindow mode or with vertical video. - // Other than that MATCH_PARENT is good - final boolean navBarAtTheBottom = DeviceUtils.isTablet(service) || !isLandscape; - controlsRoot.getLayoutParams().height = isFullscreen && !isInMultiWindow() - && navBarAtTheBottom ? size.y : ViewGroup.LayoutParams.MATCH_PARENT; - controlsRoot.requestLayout(); - - final DisplayMetrics metrics = getRootView().getResources().getDisplayMetrics(); - int topPadding = isFullscreen && !isInMultiWindow() ? getStatusBarHeight() : 0; - topPadding = !isLandscape && DeviceUtils.hasCutout(topPadding, metrics) ? 0 : topPadding; - getRootView().findViewById(R.id.playbackWindowRoot).setTranslationY(topPadding); - getBottomControlsRoot().setTranslationY(-topPadding); - } - - /** - * @return statusBar height that was found inside system resources - * or default value if no value was provided inside resources - */ - private int getStatusBarHeight() { - int statusBarHeight = 0; - final int resourceId = service.isLandscape() - ? service.getResources().getIdentifier( - "status_bar_height_landscape", "dimen", "android") - : service.getResources().getIdentifier( - "status_bar_height", "dimen", "android"); - - if (resourceId > 0) { - statusBarHeight = service.getResources().getDimensionPixelSize(resourceId); - } - if (statusBarHeight == 0) { - // Some devices provide wrong value for status bar height in landscape mode, - // this is workaround - final DisplayMetrics metrics = getRootView().getResources().getDisplayMetrics(); - statusBarHeight = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 24, metrics); - } - return statusBarHeight; - } - protected void setMuteButton(final ImageButton button, final boolean isMuted) { button.setImageDrawable(AppCompatResources.getDrawable(service, isMuted ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp)); @@ -1617,8 +1531,6 @@ public class VideoPlayerImpl extends VideoPlayer && !DeviceUtils.isTablet(service)) { toggleFullscreen(); } - - setControlsSize(); } private void buildQueue() { @@ -2015,6 +1927,9 @@ public class VideoPlayerImpl extends VideoPlayer public void setFragmentListener(final PlayerServiceEventListener listener) { fragmentListener = listener; fragmentIsVisible = true; + // Prevent applying windows insets twice + getRootView().findViewById(R.id.playbackControlRoot).setPadding(0, 0, 0, 0); + queueLayout.setPadding(0, 0, 0, 0); updateMetadata(); updatePlayback(); triggerProgressUpdate(); diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 7592d2f35..d852c2296 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -6,8 +6,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.BatteryManager; import android.os.Build; -import android.util.DisplayMetrics; -import android.util.TypedValue; import android.view.KeyEvent; import androidx.annotation.NonNull; @@ -74,17 +72,4 @@ public final class DeviceUtils { return false; } } - - /* - * Compares current status bar height with default status bar height in Android and decides, - * does the device has cutout or not - * */ - public static boolean hasCutout(final float statusBarHeight, final DisplayMetrics metrics) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - final float defaultStatusBarHeight = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 25, metrics); - return statusBarHeight > defaultStatusBarHeight; - } - return false; - } } diff --git a/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java new file mode 100644 index 000000000..fcbdabc8b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java @@ -0,0 +1,36 @@ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import com.google.android.material.appbar.CollapsingToolbarLayout; + +public class CustomCollapsingToolbarLayout extends CollapsingToolbarLayout { + public CustomCollapsingToolbarLayout(@NonNull final Context context) { + super(context); + overrideListener(); + } + + public CustomCollapsingToolbarLayout(@NonNull final Context context, + @Nullable final AttributeSet attrs) { + super(context, attrs); + overrideListener(); + } + + public CustomCollapsingToolbarLayout(@NonNull final Context context, + @Nullable final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr); + overrideListener(); + } + + /** + * CollapsingToolbarLayout overrides our logic with fitsSystemWindows and ruins the layout. + * Override Google's method + * */ + public void overrideListener() { + ViewCompat.setOnApplyWindowInsetsListener(this, (v, insets) -> insets); + } +} 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 3c64be3b5..3a17104e4 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 @@ -32,7 +32,7 @@ app:elevation="0dp" app:layout_behavior="com.google.android.material.appbar.FlingBehavior"> - @@ -161,7 +161,7 @@ - + diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index bec810e6e..e6c78585e 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -21,7 +21,7 @@ app:elevation="0dp" app:layout_behavior="com.google.android.material.appbar.FlingBehavior"> - @@ -147,8 +147,7 @@ /> - - +