Reimagined player positioning

This commit is contained in:
Avently 2020-09-15 14:43:43 +03:00
parent 6d38615ea8
commit 150e156d26
8 changed files with 90 additions and 142 deletions

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -32,7 +32,7 @@
app:elevation="0dp"
app:layout_behavior="com.google.android.material.appbar.FlingBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
<org.schabi.newpipe.views.CustomCollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
@ -161,7 +161,7 @@
</FrameLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</org.schabi.newpipe.views.CustomCollapsingToolbarLayout>
<!-- CONTENT -->
<RelativeLayout

View File

@ -42,6 +42,7 @@
android:id="@+id/playbackControlRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="@color/video_overlay_color"
android:visibility="gone"
tools:visibility="visible">

View File

@ -21,7 +21,7 @@
app:elevation="0dp"
app:layout_behavior="com.google.android.material.appbar.FlingBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
<org.schabi.newpipe.views.CustomCollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
@ -147,8 +147,7 @@
/>
</FrameLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</org.schabi.newpipe.views.CustomCollapsingToolbarLayout>
<!-- CONTENT -->
<RelativeLayout

View File

@ -42,6 +42,7 @@
android:id="@+id/playbackControlRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="@color/video_overlay_color"
android:visibility="gone"
tools:visibility="visible">