Browse Source

Improve the video layout for foldable devices

merge-requests/1454/head
Nicolas Pomepuy 4 years ago
parent
commit
b3f6ee97fe
  1. 10
      application/resources/src/main/res/drawable/ic_arrow_left.xml
  2. 10
      application/resources/src/main/res/drawable/ic_arrow_right.xml
  3. 4
      application/resources/src/main/res/values/strings.xml
  4. 2
      application/tools/src/main/java/org/videolan/tools/Settings.kt
  5. 1
      application/vlc-android/build.gradle
  6. 24
      application/vlc-android/res/layout/player.xml
  7. 28
      application/vlc-android/res/layout/player_resize.xml
  8. 19
      application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt
  9. 121
      application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerOverlayDelegate.kt
  10. 21
      application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerResizeDelegate.kt

10
application/resources/src/main/res/drawable/ic_arrow_left.xml

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="42dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/font_default"
android:pathData="M15.41,16.59L10.83,12l4.58,-4.59L14,6l-6,6 6,6 1.41,-1.41z" />
</vector>

10
application/resources/src/main/res/drawable/ic_arrow_right.xml

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="42dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/font_default"
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z" />
</vector>

4
application/resources/src/main/res/values/strings.xml

@ -939,5 +939,9 @@
<string name="touch_only_description">Switching to the mobile interface can only be done with a tactile screen or a mouse.\nPlease slide below to verify that this feature is safe to change.</string>
<string name="no_web_browser">No web browser installed</string>
<string name="no_web_browser_message">You can scan this on your smartphone.\nThe URL is %s</string>
<string name="fold_optimized">Optimized for your folding screen</string>
<string name="fold_optimize">Optimize for folding screens</string>
<string name="foldable">Foldable screen</string>
<string name="undo">Undo</string>
</resources>

2
application/tools/src/main/java/org/videolan/tools/Settings.kt

@ -130,6 +130,8 @@ const val POPUP_KEEPSCREEN = "popup_keepscreen"
const val POPUP_FORCE_LEGACY = "popup_force_legacy"
const val LOCK_USE_SENSOR = "lock_use_sensor"
const val DISPLAY_UNDER_NOTCH = "display_under_notch"
const val ALLOW_FOLD_AUTO_LAYOUT = "allow_fold_auto_layout"
const val HINGE_ON_RIGHT = "hinge_on_right"
const val VIDEO_PAUSED = "VideoPaused"
const val VIDEO_SPEED = "VideoSpeed"

1
application/vlc-android/build.gradle

@ -175,6 +175,7 @@ dependencies {
api "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.ext.lifecycleVersion"
api "androidx.lifecycle:lifecycle-common-java8:$rootProject.ext.lifecycleVersion"
api "androidx.room:room-runtime:$rootProject.ext.roomVersion"
api "androidx.window:window:1.0.0"
kapt ('org.xerial:sqlite-jdbc:3.36.0')
kapt("androidx.room:room-compiler:$rootProject.ext.roomVersion")

24
application/vlc-android/res/layout/player.xml

@ -114,6 +114,30 @@
android:layout="@layout/player_hud"
android:layout_alignParentBottom="true" />
<ImageView
android:id="@+id/hinge_go_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:visibility="gone"
android:foreground="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_arrow_left" />
<ImageView
android:id="@+id/hinge_go_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:visibility="gone"
android:foreground="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_arrow_right" />
</RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout

28
application/vlc-android/res/layout/player_resize.xml

@ -23,6 +23,32 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/foldable_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:fontFamily="sans-serif-medium"
android:text="@string/foldable"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<CheckBox
android:id="@+id/foldable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/fold_optimize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/foldable_title" />
<TextView
android:id="@+id/notch_title"
android:layout_width="0dp"
@ -35,7 +61,7 @@
android:text="@string/notch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/foldable" />
<CheckBox
android:id="@+id/notch"

19
application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt

@ -62,11 +62,15 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.databinding.BindingAdapter
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -121,7 +125,7 @@ open class VideoPlayerActivity : AppCompatActivity(), PlaybackService.Callback,
private lateinit var startedScope: CoroutineScope
var service: PlaybackService? = null
lateinit var medialibrary: Medialibrary
private var videoLayout: VLCVideoLayout? = null
var videoLayout: VLCVideoLayout? = null
lateinit var displayManager: DisplayManager
var rootView: View? = null
var videoUri: Uri? = null
@ -404,6 +408,8 @@ open class VideoPlayerActivity : AppCompatActivity(), PlaybackService.Callback,
overlayDelegate.playlistSearchText = findViewById(R.id.playlist_search_text)
overlayDelegate.playlistContainer = findViewById(R.id.video_playlist_container)
overlayDelegate.closeButton = findViewById(R.id.close_button)
overlayDelegate.hingeArrowRight = findViewById(R.id.hinge_go_right)
overlayDelegate.hingeArrowLeft = findViewById(R.id.hinge_go_left)
overlayDelegate.playlistSearchText.editText?.addTextChangedListener(this)
overlayDelegate.playerUiContainer = findViewById(R.id.player_ui_container)
@ -490,6 +496,17 @@ open class VideoPlayerActivity : AppCompatActivity(), PlaybackService.Callback,
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE
}
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@VideoPlayerActivity)
.windowLayoutInfo(this@VideoPlayerActivity)
.collect { layoutInfo ->
overlayDelegate.foldingFeature = layoutInfo.displayFeatures
.firstOrNull() as? FoldingFeature
}
}
}
}
override fun onAttachedToWindow() {

121
application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerOverlayDelegate.kt

@ -34,9 +34,7 @@ import android.os.Build
import android.view.View
import android.view.WindowManager
import android.view.animation.AnimationUtils
import android.widget.Button
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.*
import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.ViewStubCompat
@ -44,12 +42,14 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.children
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import androidx.window.layout.FoldingFeature
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
@ -75,10 +75,8 @@ import org.videolan.vlc.gui.helpers.UiTools.showVideoTrack
import org.videolan.vlc.gui.view.PlayerProgress
import org.videolan.vlc.manageAbRepeatStep
import org.videolan.vlc.media.MediaUtils
import org.videolan.vlc.util.*
import org.videolan.vlc.util.FileUtils
import org.videolan.vlc.util.getScreenHeight
import org.videolan.vlc.util.isSchemeFile
import org.videolan.vlc.util.isSchemeNetwork
import org.videolan.vlc.viewmodels.PlaylistModel
import java.text.DateFormat
import java.util.*
@ -116,6 +114,7 @@ class VideoPlayerOverlayDelegate (private val player: VideoPlayerActivity) {
var seekButtons: Boolean = false
var hasPlaylist: Boolean = false
private var hingeSnackShown: Boolean = false
var enableSubs = true
private lateinit var bookmarkListDelegate: BookmarkListDelegate
@ -127,9 +126,103 @@ class VideoPlayerOverlayDelegate (private val player: VideoPlayerActivity) {
private var orientationLockedBeforeLock: Boolean = false
lateinit var closeButton: View
lateinit var playlistContainer: View
lateinit var hingeArrowRight: ImageView
lateinit var hingeArrowLeft: ImageView
lateinit var playlist: RecyclerView
lateinit var playlistSearchText: TextInputLayout
lateinit var playlistAdapter: PlaylistAdapter
var foldingFeature: FoldingFeature? = null
set(value) {
field = value
manageHinge()
}
/**
* Changes the device layout depending on the scree foldable status and features
*/
fun manageHinge() {
resetHingeLayout()
if (foldingFeature == null || !Settings.getInstance(player).getBoolean(ALLOW_FOLD_AUTO_LAYOUT, true)) return
val foldingFeature = foldingFeature!!
//device is fully occluded and split vertically. We display the controls on the half left or right side
if (foldingFeature.occlusionType == FoldingFeature.OcclusionType.FULL && foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL) {
val onRight = Settings.getInstance(player).getBoolean(HINGE_ON_RIGHT, true)
hingeArrowLeft.visibility = if (onRight && ::hudBinding.isInitialized) View.VISIBLE else View.GONE
hingeArrowRight.visibility = if (!onRight && ::hudBinding.isInitialized) View.VISIBLE else View.GONE
val halfScreenSize = player.getScreenWidth() - foldingFeature.bounds.right
arrayOf(playerUiContainer, hudBackground, hudRightBackground, playlistContainer).forEach {
it?.let { view ->
val lp = (view.layoutParams as RelativeLayout.LayoutParams)
lp.width = halfScreenSize
lp.addRule(if (onRight) RelativeLayout.ALIGN_PARENT_RIGHT else RelativeLayout.ALIGN_PARENT_LEFT)
lp.removeRule(if (onRight) RelativeLayout.ALIGN_PARENT_LEFT else RelativeLayout.ALIGN_PARENT_RIGHT)
view.layoutParams = lp
}
}
showHingeSnackIfNeeded()
} else {
//device is separated and half opened. We display the controls on the bottom half and the video on the top half
if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
val videoLayoutLP = (player.videoLayout!!.layoutParams as RelativeLayout.LayoutParams)
val halfScreenSize = foldingFeature.bounds.top
videoLayoutLP.height = halfScreenSize
player.videoLayout!!.layoutParams = videoLayoutLP
player.findViewById<FrameLayout>(R.id.player_surface_frame).children.forEach { it.requestLayout() }
arrayOf(playerUiContainer, playlistContainer).forEach {
val lp = (it.layoutParams as RelativeLayout.LayoutParams)
lp.height = halfScreenSize
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
it.layoutParams = lp
}
arrayOf(hudBackground, hudRightBackground).forEach {
it?.setGone()
}
showHingeSnackIfNeeded()
}
}
}
/**
* Shows the fold layout snackbar if needed
*/
private fun showHingeSnackIfNeeded() {
if (!hingeSnackShown) {
UiTools.snackerConfirm(player, player.getString(R.string.fold_optimized), confirmMessage = R.string.undo) {
player.resizeDelegate.showResizeOverlay()
}
hingeSnackShown = true
}
}
/**
* Resets the layout to normal after a fold/hinge status change
*/
private fun resetHingeLayout() {
arrayOf(playerUiContainer, hudBackground, hudRightBackground, playlistContainer).forEach {
it?.let { view ->
val lp = (view.layoutParams as RelativeLayout.LayoutParams)
lp.width = RelativeLayout.LayoutParams.MATCH_PARENT
view.layoutParams = lp
}
}
arrayOf(playerUiContainer, playlistContainer).forEach {
val lp = (it.layoutParams as RelativeLayout.LayoutParams)
lp.height = RelativeLayout.LayoutParams.MATCH_PARENT
lp.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
it.layoutParams = lp
}
if (::hudBinding.isInitialized) arrayOf(hudBackground, hudRightBackground).forEach {
it?.setVisible()
}
hingeArrowLeft.visibility = View.GONE
hingeArrowRight.visibility = View.GONE
val lp = (player.videoLayout!!.layoutParams as RelativeLayout.LayoutParams)
lp.height = RelativeLayout.LayoutParams.MATCH_PARENT
player.videoLayout!!.layoutParams = lp
player.findViewById<FrameLayout>(R.id.player_surface_frame).children.forEach { it.requestLayout() }
}
fun showTracks() {
player.showVideoTrack(
@ -324,6 +417,9 @@ class VideoPlayerOverlayDelegate (private val player: VideoPlayerActivity) {
enterAnimate(arrayOf(hudBinding.progressOverlay, hudBackground), 100.dp.toFloat())
enterAnimate(arrayOf(hudRightBinding.hudRightOverlay, hudRightBackground), -100.dp.toFloat())
hingeArrowLeft.animate().alpha(1F)
hingeArrowRight.animate().alpha(1F)
if (!player.displayManager.isPrimary)
overlayBackground.setVisible()
updateOverlayPausePlay(true)
@ -452,6 +548,7 @@ class VideoPlayerOverlayDelegate (private val player: VideoPlayerActivity) {
player.updateNavStatus()
setListeners(true)
initPlaylistUi()
if (foldingFeature != null) manageHinge()
} else if (::hudBinding.isInitialized) {
hudBinding.progress = service.playlistManager.player.progress
hudBinding.lifecycleOwner = player
@ -658,6 +755,16 @@ class VideoPlayerOverlayDelegate (private val player: VideoPlayerActivity) {
}
hudRightBinding.playlistToggle.setOnClickListener(player)
closeButton.setOnClickListener { togglePlaylist() }
hingeArrowLeft.setOnClickListener {
Settings.getInstance(player).putSingle(HINGE_ON_RIGHT, false)
manageHinge()
showOverlay()
}
hingeArrowRight.setOnClickListener {
Settings.getInstance(player).putSingle(HINGE_ON_RIGHT, true)
manageHinge()
showOverlay()
}
val callback = SwipeDragItemTouchHelperCallback(playlistAdapter, true)
val touchHelper = ItemTouchHelper(callback)
@ -735,6 +842,8 @@ class VideoPlayerOverlayDelegate (private val player: VideoPlayerActivity) {
exitAnimate(arrayOf(hudBinding.progressOverlay, hudBackground),100.dp.toFloat())
exitAnimate(arrayOf(hudRightBinding.hudRightOverlay, hudRightBackground),-100.dp.toFloat())
hingeArrowLeft.animate().alpha(0F)
hingeArrowRight.animate().alpha(0F)
showControls(false)
player.isShowing = false

21
application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerResizeDelegate.kt

@ -54,6 +54,7 @@ class VideoPlayerResizeDelegate(private val player: VideoPlayerActivity) {
get() = player.overlayDelegate
lateinit var resizeMainView: View
lateinit var notchCheckbox: CheckBox
private lateinit var foldCheckbox: CheckBox
private lateinit var scrollView: NestedScrollView
private lateinit var sizeList: RecyclerView
private lateinit var sizeAdapter: SizeAdapter
@ -67,7 +68,7 @@ class VideoPlayerResizeDelegate(private val player: VideoPlayerActivity) {
/**
* Show the resize overlay. Inflate it if it's not yet
*/
private fun showResizeOverlay() {
fun showResizeOverlay() {
player.findViewById<ViewStubCompat>(R.id.player_resize_stub)?.let {
resizeMainView = it.inflate() as FrameLayout
val browseFrameLayout = resizeMainView.findViewById<BrowseFrameLayout>(R.id.resize_background)
@ -80,6 +81,9 @@ class VideoPlayerResizeDelegate(private val player: VideoPlayerActivity) {
sizeList = resizeMainView.findViewById(R.id.size_list)
scrollView = resizeMainView.findViewById(R.id.resize_scrollview)
foldCheckbox = resizeMainView.findViewById(R.id.foldable)
val foldTitle = resizeMainView.findViewById<View>(R.id.foldable_title)
sizeList.layoutManager = LinearLayoutManager(player)
sizeAdapter = SizeAdapter()
sizeAdapter.setOnSizeSelectedListener { scale ->
@ -87,8 +91,21 @@ class VideoPlayerResizeDelegate(private val player: VideoPlayerActivity) {
}
sizeList.adapter = sizeAdapter
val settings = Settings.getInstance(player)
if (player.overlayDelegate.foldingFeature != null) {
foldCheckbox.setVisible()
foldTitle.setVisible()
foldCheckbox.isChecked = settings.getBoolean(ALLOW_FOLD_AUTO_LAYOUT, true)
foldCheckbox.setOnCheckedChangeListener { _, isChecked ->
settings.edit { putBoolean(ALLOW_FOLD_AUTO_LAYOUT, isChecked) }
player.overlayDelegate.manageHinge()
}
} else {
foldCheckbox.setGone()
foldTitle.setGone()
}
if (player.hasPhysicalNotch) {
val settings = Settings.getInstance(player)
notchCheckbox.setVisible()
notchTitle.setVisible()
notchCheckbox.isChecked = settings.getInt(DISPLAY_UNDER_NOTCH, WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

Loading…
Cancel
Save