diff --git a/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt b/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt index 751f465c34c3..6c1e6efe0979 100644 --- a/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt +++ b/app/src/main/java/com/nextcloud/client/player/ui/PlayerActivity.kt @@ -136,17 +136,6 @@ class PlayerActivity : } } - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - - val videoPlayerView = playerView as? VideoPlayerView ?: return - if (isInPictureInPictureMode) { - videoPlayerView.hideControls() - } else { - videoPlayerView.showControls() - } - } - override fun onUserLeaveHint() { super.onUserLeaveHint() if (canUsePictureInPictureMode()) { @@ -166,14 +155,11 @@ class PlayerActivity : null } + // Finish the activity if the user closes the PIP window if (!isInPictureInPictureMode && lifecycle.currentState == Lifecycle.State.CREATED) { - finish() // Finish the activity if the user closes the PIP window + finish() return } - - if (!isInPictureInPictureMode) { - (playerView as? VideoPlayerView)?.showControls() - } } private fun canUsePictureInPictureMode(): Boolean = diff --git a/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt b/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt index 125e3e9890ba..5bf1f4c34ee4 100644 --- a/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt +++ b/app/src/main/java/com/nextcloud/client/player/ui/video/VideoPlayerView.kt @@ -8,27 +8,16 @@ package com.nextcloud.client.player.ui.video import android.content.Context -import android.view.MotionEvent import android.view.WindowInsets import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat.Type -import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import com.nextcloud.client.player.model.file.PlaybackFile import com.nextcloud.client.player.ui.PlayerView import com.owncloud.android.R import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.milliseconds class VideoPlayerView(context: Context) : PlayerView(context) { - - companion object { - private const val HIDE_CONTROLS_DELAY = 5000L - } - override val layoutRes get() = R.layout.player_video_view override val createFragment: (PlaybackFile) -> Fragment get() = { VideoFileFragment.createInstance(it) } @@ -39,13 +28,6 @@ class VideoPlayerView(context: Context) : PlayerView(context) { topBar.setBackgroundResource(R.drawable.player_video_top_scrim) } - override fun onStart() { - super.onStart() - if (!activity.isInPictureInPictureMode) { - showControls() - } - } - override fun onStop() { super.onStop() cancelHideControlsTimer() @@ -65,45 +47,6 @@ class VideoPlayerView(context: Context) : PlayerView(context) { return WindowInsetsCompat.CONSUMED.toWindowInsets() } - override fun dispatchTouchEvent(event: MotionEvent): Boolean { - if (event.action == MotionEvent.ACTION_DOWN) { - val isTouchOutsideControls = event.y < playerControlView.y && event.y > topBar.height - when { - !playerControlView.isVisible -> showControls() - isTouchOutsideControls -> hideControls() - else -> restartHideControlsTimer() - } - } - return super.dispatchTouchEvent(event) - } - - fun showControls() { - windowWrapper.showSystemBars() - topBar.visibility = VISIBLE - playerControlView.visibility = VISIBLE - restartHideControlsTimer() - } - - fun hideControls() { - windowWrapper.hideSystemBars() - topBar.visibility = GONE - playerControlView.visibility = GONE - cancelHideControlsTimer() - } - - private fun restartHideControlsTimer() { - hideControlsTimerJob?.cancel() - - if (activity.isInPictureInPictureMode) { - return - } - - hideControlsTimerJob = activity.lifecycleScope.launch { - delay(HIDE_CONTROLS_DELAY.milliseconds) - hideControls() - } - } - private fun cancelHideControlsTimer() { hideControlsTimerJob?.cancel() hideControlsTimerJob = null diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ActionBarExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ActionBarExtensions.kt deleted file mode 100644 index 3bafc9b850e9..000000000000 --- a/app/src/main/java/com/nextcloud/utils/extensions/ActionBarExtensions.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2024 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.nextcloud.utils.extensions - -import android.text.Spannable -import android.text.SpannableString -import android.text.style.ForegroundColorSpan -import androidx.appcompat.app.ActionBar - -fun ActionBar.setTitleColor(color: Int) { - val text = SpannableString(title ?: "") - text.setSpan(ForegroundColorSpan(color), 0, text.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE) - title = text -} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt new file mode 100644 index 000000000000..4f9ba65373fd --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/WindowExtensions.kt @@ -0,0 +1,47 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import android.view.View +import android.view.Window +import androidx.appcompat.app.ActionBar +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat + +fun ActionBar.toggle(show: Boolean) { + if (show) { + show() + } else { + hide() + } +} + +fun Window.showSystemBar(show: Boolean, view: View) { + WindowCompat.getInsetsController(this, view).run { + val types = WindowInsetsCompat.Type.systemBars() + if (show) { + show(types) + } else { + systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + hide(types) + } + } +} + +fun Window.showNavigationBar(show: Boolean, view: View) { + WindowCompat.getInsetsController(this, view).run { + val types = WindowInsetsCompat.Type.navigationBars() + if (show) { + show(types) + } else { + systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + hide(types) + } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt index 8d0a69ecdc4c..5049c8dfd5d3 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt @@ -44,6 +44,7 @@ import com.nextcloud.model.WorkerState import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSerializableArgument import com.nextcloud.utils.extensions.observeWorker +import com.nextcloud.utils.extensions.toggle import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager @@ -195,12 +196,8 @@ class PreviewImageActivity : private fun displayCutOutSafeInsetTop(): Int = ViewCompat.getRootWindowInsets(window.decorView)?.displayCutout?.safeInsetTop ?: 0 - fun toggleActionBarVisibility(hide: Boolean) { - if (hide) { - supportActionBar?.hide() - } else { - supportActionBar?.show() - } + fun toggleActionBarVisibility(show: Boolean) { + supportActionBar?.toggle(show) } private fun initViewPager(user: User) { @@ -543,11 +540,11 @@ class PreviewImageActivity : if (isInPictureInPictureMode) { wasSystemUiVisibleBeforePictureInPicture = isSystemUIVisible - toggleActionBarVisibility(true) + toggleActionBarVisibility(false) return } - toggleActionBarVisibility(!wasSystemUiVisibleBeforePictureInPicture) + toggleActionBarVisibility(wasSystemUiVisibleBeforePictureInPicture) if (lifecycle.currentState != Lifecycle.State.CREATED) return diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt index 587fdf35d586..5cbe74c00289 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt @@ -38,7 +38,6 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.view.MenuHost import androidx.core.view.MenuProvider -import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.caverock.androidsvg.SVG @@ -49,6 +48,7 @@ import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.di.Injectable import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.client.player.model.file.PlaybackCollection import com.nextcloud.ui.fileactions.FileAction import com.nextcloud.ui.fileactions.FileActionsBottomSheet.Companion.newInstance import com.nextcloud.utils.extensions.clickWithDebounce @@ -57,7 +57,6 @@ import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSmallThumbnail import com.nextcloud.utils.extensions.typedActivity import com.owncloud.android.MainApp -import com.nextcloud.client.player.model.file.PlaybackCollection import com.owncloud.android.R import com.owncloud.android.databinding.PreviewImageFragmentBinding import com.owncloud.android.datamodel.OCFile @@ -162,7 +161,7 @@ class PreviewImageFragment : private fun hideActionBar() { (requireActivity() as PreviewImageActivity).run { - toggleActionBarVisibility(true) + toggleActionBarVisibility(false) } } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt index 9da820b28125..893623cadfa6 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewPlaybackFragment.kt @@ -30,7 +30,6 @@ import com.nextcloud.client.player.media3.PlaybackModel import com.nextcloud.client.player.model.PlayerThumbnailLoader import com.nextcloud.client.player.model.file.PlaybackCollection import com.nextcloud.client.player.model.file.PlaybackFile -import com.nextcloud.client.player.util.PlayerUtil.toPlaybackFile import com.nextcloud.client.player.model.state.PlaybackState import com.nextcloud.client.player.model.state.VideoSize import com.nextcloud.client.player.ui.MediaNavigator @@ -38,10 +37,14 @@ import com.nextcloud.client.player.ui.PlayerLauncher import com.nextcloud.client.player.util.PlayerUtil.applyVideoSize import com.nextcloud.client.player.util.PlayerUtil.isPictureInPictureAllowed import com.nextcloud.client.player.util.PlayerUtil.ownsPlayback +import com.nextcloud.client.player.util.PlayerUtil.toPlaybackFile import com.nextcloud.ui.fileactions.FileAction import com.nextcloud.ui.fileactions.FileActionsBottomSheet import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSerializableArgument +import com.nextcloud.utils.extensions.setVisibilityWithAnimation +import com.nextcloud.utils.extensions.showNavigationBar +import com.nextcloud.utils.extensions.showSystemBar import com.owncloud.android.R import com.owncloud.android.databinding.PreviewPlaybackFragmentBinding import com.owncloud.android.datamodel.OCFile @@ -120,6 +123,7 @@ class PreviewPlaybackFragment : private var renderedVideoSize: VideoSize? = null private var pictureInPictureCallback: OnBackPressedCallback? = null + private var isFullScreen = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -138,6 +142,8 @@ class PreviewPlaybackFragment : loadThumbnail() registerPictureInPictureOnBack() binding.playerControlView.navigator = activity as? MediaNavigator + isFullScreen = previewActivity()?.isSystemUIVisible == false + binding.surfaceView.setOnClickListener { toggleFullScreen() } updatePlayerControlsVisibility() binding.root.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> if (ownsPlayback(binding.surfaceView)) render(playbackModel.state) @@ -399,8 +405,27 @@ class PreviewPlaybackFragment : private fun isInPictureInPictureMode(): Boolean = activity?.isInPictureInPictureMode == true + private fun toggleFullScreen() { + val previewActivity = previewActivity() ?: return + isFullScreen = !isFullScreen + previewActivity.toggleActionBarVisibility(!isFullScreen) + binding.playerControlView.setVisibilityWithAnimation(!isFullScreen) + previewActivity.window.run { + showSystemBar(!isFullScreen, binding.root) + showNavigationBar(!isFullScreen, binding.root) + } + } + private fun updatePlayerControlsVisibility() { - binding.playerControlView.isVisible = !isInPictureInPictureMode() + if (isInPictureInPictureMode()) { + binding.playerControlView.isVisible = false + return + } + + // playback updates arrive every second, so they must not undo full screen or cut the fade short + if (!isFullScreen) { + binding.playerControlView.isVisible = true + } } private fun showVideo(videoSize: VideoSize?) {