Skip to content

Commit 056d680

Browse files
Merge pull request #17391 from nextcloud/backport/17379/stable-34.1.x
[stable-34.1.x] fix(media): download and preview
2 parents 27f29c2 + 12dcb12 commit 056d680

9 files changed

Lines changed: 126 additions & 145 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
package com.nextcloud.utils.extensions
8+
9+
import android.view.View
10+
import androidx.annotation.OptIn
11+
import androidx.core.graphics.Insets
12+
import androidx.core.view.updatePadding
13+
import androidx.media3.common.util.UnstableApi
14+
import androidx.media3.ui.PlayerView
15+
16+
@OptIn(UnstableApi::class)
17+
fun PlayerView.applyControlsInsets(insets: Insets) {
18+
val controller = findViewById<View>(androidx.media3.ui.R.id.exo_controller) ?: return
19+
controller.updatePadding(left = insets.left, right = insets.right, bottom = insets.bottom)
20+
}
21+
22+
@OptIn(UnstableApi::class)
23+
fun PlayerView.setFullscreenButton(isFullscreen: Boolean, onClick: () -> Unit) {
24+
setFullscreenButtonClickListener(null)
25+
setFullscreenButtonState(isFullscreen)
26+
setFullscreenButtonClickListener { onClick() }
27+
}

app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,7 +2403,7 @@ class FileDisplayActivity :
24032403

24042404
is PreviewMediaFragment -> {
24052405
fragment.updateFile(ocFile)
2406-
if (PreviewMediaFragment.canBePreviewed(ocFile)) {
2406+
if (PreviewMediaFragment.isAudioOrVideo(ocFile)) {
24072407
startMediaPreview(
24082408
ocFile,
24092409
fragment.position,
@@ -2688,7 +2688,7 @@ class FileDisplayActivity :
26882688
startMediaActivity(file, startPlaybackPosition, autoplay, actualUser)
26892689
} else {
26902690
configureToolbarForPreview(file)
2691-
val mediaFragment: Fragment = newInstance(file, user.get(), startPlaybackPosition, autoplay, false)
2691+
val mediaFragment: Fragment = newInstance(file, user.get(), startPlaybackPosition, autoplay)
26922692
setLeftFragment(mediaFragment, false)
26932693
}
26942694
} else {

app/src/main/java/com/owncloud/android/ui/preview/FileDownloadFragment.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import android.os.Bundle
1717
import android.view.LayoutInflater
1818
import android.view.View
1919
import android.view.ViewGroup
20-
import androidx.fragment.app.Fragment
2120
import com.nextcloud.client.account.User
2221
import com.nextcloud.client.di.Injectable
2322
import com.nextcloud.client.jobs.download.FileDownloadHelper.Companion.instance
@@ -136,7 +135,7 @@ class FileDownloadFragment :
136135
private const val ARG_USER = "USER"
137136
private val TAG: String = FileDownloadFragment::class.java.simpleName
138137

139-
fun newInstance(file: OCFile?, user: User?, ignoreFirstSavedState: Boolean): Fragment =
138+
fun newInstance(file: OCFile?, user: User?, ignoreFirstSavedState: Boolean): FileDownloadFragment =
140139
FileDownloadFragment().apply {
141140
arguments = Bundle().apply {
142141
putParcelable(ARG_FILE, file)

app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import android.view.View
1717
import androidx.activity.OnBackPressedCallback
1818
import androidx.core.content.ContextCompat
1919
import androidx.core.graphics.drawable.toDrawable
20+
import androidx.core.view.ViewCompat
21+
import androidx.core.view.WindowCompat
22+
import androidx.core.view.WindowInsetsCompat
23+
import androidx.core.view.WindowInsetsControllerCompat
2024
import androidx.drawerlayout.widget.DrawerLayout
2125
import androidx.localbroadcastmanager.content.LocalBroadcastManager
2226
import androidx.viewpager2.widget.ViewPager2
@@ -59,7 +63,7 @@ import kotlin.math.max
5963
import kotlin.math.min
6064

6165
/**
62-
* Holds a swiping gallery where image files contained in an Nextcloud directory are shown.
66+
* Holds a swiping gallery where image and video files contained in a Nextcloud directory are shown.
6367
*/
6468
@Suppress("TooManyFunctions")
6569
class PreviewImageActivity :
@@ -70,13 +74,15 @@ class PreviewImageActivity :
7074
Injectable {
7175
private var livePhotoFile: OCFile? = null
7276
private var viewPager: ViewPager2? = null
73-
private var previewImagePagerAdapter: PreviewImagePagerAdapter? = null
77+
private var previewMediaPagerAdapter: PreviewMediaPagerAdapter? = null
7478
private var savedPosition: Int? = null
7579

7680
private val downloadStartReceiver = DownloadStartReceiver()
7781
private val downloadFinishReceiver = DownloadFinishReceiver()
7882

79-
private var fullScreenAnchorView: View? = null
83+
private val windowInsetsController: WindowInsetsControllerCompat by lazy {
84+
WindowCompat.getInsetsController(window, window.decorView)
85+
}
8086

8187
private var isDownloadWorkStarted = false
8288
private var screenState = PreviewImageActivityState.Idle
@@ -115,8 +121,6 @@ class PreviewImageActivity :
115121
it.setBackgroundDrawable(R.color.black.toDrawable())
116122
}
117123

118-
fullScreenAnchorView = window.decorView
119-
// to keep our UI controls visibility in line with system bars visibility
120124
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
121125

122126
val requestWaitingForBinder = savedInstanceState?.getBoolean(KEY_WAITING_FOR_BINDER) ?: false
@@ -163,7 +167,7 @@ class PreviewImageActivity :
163167
if (virtualFolderType != null && virtualFolderType !== VirtualFolderType.NONE) {
164168
val type = virtualFolderType as VirtualFolderType
165169

166-
previewImagePagerAdapter = PreviewImagePagerAdapter(
170+
previewMediaPagerAdapter = PreviewMediaPagerAdapter(
167171
this,
168172
type,
169173
user,
@@ -174,7 +178,7 @@ class PreviewImageActivity :
174178
val parentFolder = file?.let { storageManager.getFileById(it.parentId) }
175179
?: storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
176180

177-
previewImagePagerAdapter = PreviewImagePagerAdapter(
181+
previewMediaPagerAdapter = PreviewMediaPagerAdapter(
178182
this,
179183
livePhotoFile,
180184
parentFolder,
@@ -192,11 +196,11 @@ class PreviewImageActivity :
192196
) {
193197
savedPosition
194198
} else {
195-
file?.let { previewImagePagerAdapter?.getFilePosition(it) }
199+
file?.let { previewMediaPagerAdapter?.getFilePosition(it) }
196200
}
197201
position = position?.toDouble()?.let { max(it, 0.0).toInt() }
198202

199-
viewPager?.adapter = previewImagePagerAdapter
203+
viewPager?.adapter = previewMediaPagerAdapter
200204
viewPager?.registerOnPageChangeCallback(object : OnPageChangeCallback() {
201205
override fun onPageSelected(position: Int) {
202206
selectPage(position)
@@ -231,7 +235,7 @@ class PreviewImageActivity :
231235

232236
fun updateViewPagerAfterDeletionAndAdvanceForward() {
233237
val deletePosition = viewPager?.currentItem ?: return
234-
previewImagePagerAdapter?.let { adapter ->
238+
previewMediaPagerAdapter?.let { adapter ->
235239
val nextPosition = min(deletePosition, adapter.itemCount - 1)
236240
viewPager?.setCurrentItem(nextPosition, true)
237241
adapter.delete(deletePosition)
@@ -311,7 +315,7 @@ class PreviewImageActivity :
311315
super.onRemoteOperationFinish(operation, result)
312316

313317
if (operation is RemoveFileOperation) {
314-
previewImagePagerAdapter?.let {
318+
previewMediaPagerAdapter?.let {
315319
if (it.itemCount <= 1) {
316320
backToDisplayActivity()
317321
return
@@ -346,7 +350,7 @@ class PreviewImageActivity :
346350
private fun setDownloadedItem() {
347351
savedPosition?.let { position ->
348352

349-
previewImagePagerAdapter?.run {
353+
previewMediaPagerAdapter?.run {
350354
file?.let {
351355
updateFile(position, it)
352356
notifyItemChanged(position)
@@ -438,15 +442,15 @@ class PreviewImageActivity :
438442
if (position == null) return
439443
savedPosition = position
440444

441-
val currentFile = previewImagePagerAdapter?.getFileAt(position)
445+
val currentFile = previewMediaPagerAdapter?.getFileAt(position)
442446

443447
if (!isDownloadWorkStarted) {
444448
screenState = PreviewImageActivityState.WaitingForBinder
445449
} else {
446450
if (currentFile != null) {
447451
if (currentFile.isEncrypted &&
448452
!currentFile.isDown &&
449-
previewImagePagerAdapter?.pendingErrorAt(position) == false
453+
previewMediaPagerAdapter?.pendingErrorAt(position) == false
450454
) {
451455
requestForDownload(currentFile)
452456
}
@@ -507,13 +511,15 @@ class PreviewImageActivity :
507511
get() = supportActionBar == null || supportActionBar?.isShowing == true
508512

509513
fun toggleFullScreen() {
510-
fullScreenAnchorView?.let {
511-
val visible = (it.systemUiVisibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
512-
if (visible) {
513-
hideSystemUI(it)
514-
} else {
515-
showSystemUI(it)
516-
}
514+
val rootInsets = ViewCompat.getRootWindowInsets(window.decorView) ?: return
515+
516+
// the content is laid out edge to edge so that showing and hiding the bars does not move it
517+
WindowCompat.setDecorFitsSystemWindows(window, false)
518+
519+
if (rootInsets.isVisible(WindowInsetsCompat.Type.systemBars())) {
520+
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
521+
} else {
522+
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
517523
}
518524
}
519525

@@ -542,27 +548,6 @@ class PreviewImageActivity :
542548
override fun onBrowsedDownTo(folder: OCFile) = Unit
543549
override fun onTransferStateChanged(file: OCFile, downloading: Boolean, uploading: Boolean) = Unit
544550

545-
@Suppress("DEPRECATION")
546-
private fun hideSystemUI(anchorView: View) {
547-
anchorView.systemUiVisibility = (
548-
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
549-
or View.SYSTEM_UI_FLAG_FULLSCREEN
550-
or View.SYSTEM_UI_FLAG_IMMERSIVE
551-
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
552-
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
553-
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
554-
)
555-
}
556-
557-
@Suppress("DEPRECATION")
558-
private fun showSystemUI(anchorView: View) {
559-
anchorView.systemUiVisibility = (
560-
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
561-
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
562-
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
563-
)
564-
}
565-
566551
companion object {
567552
val TAG: String = PreviewImageActivity::class.java.simpleName
568553
const val EXTRA_VIRTUAL_TYPE: String = "EXTRA_VIRTUAL_TYPE"

app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class PreviewImageFragment :
164164

165165
hideActionBar()
166166

167-
val mediaFragment: Fragment = newInstance(file, accountManager.user, 0, true, true)
167+
val mediaFragment: Fragment = newInstance(file, accountManager.user, autoplay = true, isLivePhoto = true)
168168
val fragmentManager = requireActivity().supportFragmentManager
169169
fragmentManager.beginTransaction().run {
170170
replace(R.id.top, mediaFragment)

app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import com.nextcloud.ui.fileactions.FileActionsBottomSheet.Companion.newInstance
7272
import com.nextcloud.ui.fileactions.FileActionsBottomSheet.ResultListener
7373
import com.nextcloud.utils.extensions.getParcelableArgument
7474
import com.nextcloud.utils.extensions.logFileSize
75+
import com.nextcloud.utils.extensions.setFullscreenButton
7576
import com.nextcloud.utils.extensions.setTitleColor
7677
import com.owncloud.android.R
7778
import com.owncloud.android.databinding.ActivityPreviewMediaBinding
@@ -488,7 +489,7 @@ class PreviewMediaActivity :
488489
}
489490
)
490491
it.player = videoPlayer
491-
it.setFullscreenButtonClickListener { startFullScreenVideo() }
492+
it.setFullscreenButton(isFullscreen = false) { startFullScreenVideo() }
492493
}
493494
}
494495

app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.kt

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Nextcloud - Android Client
33
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
45
* SPDX-FileCopyrightText: 2023 TSI-mc
56
* SPDX-FileCopyrightText: 2023 Parneet Singh <gurayaparneet@gmail.com>
67
* SPDX-FileCopyrightText: 2020 Andy Scherzinger <info@andy-scherzinger.de>
@@ -27,17 +28,13 @@ import android.view.MotionEvent
2728
import android.view.View
2829
import android.view.View.OnTouchListener
2930
import android.view.ViewGroup
30-
import android.widget.FrameLayout
3131
import androidx.annotation.OptIn
3232
import androidx.annotation.StringRes
3333
import androidx.core.net.toUri
3434
import androidx.core.view.MenuHost
3535
import androidx.core.view.MenuProvider
3636
import androidx.core.view.ViewCompat
3737
import androidx.core.view.WindowInsetsCompat
38-
import androidx.core.view.marginBottom
39-
import androidx.core.view.updateLayoutParams
40-
import androidx.core.view.updatePadding
4138
import androidx.drawerlayout.widget.DrawerLayout
4239
import androidx.lifecycle.Lifecycle
4340
import androidx.lifecycle.lifecycleScope
@@ -46,7 +43,6 @@ import androidx.media3.common.Player
4643
import androidx.media3.common.util.UnstableApi
4744
import androidx.media3.exoplayer.ExoPlayer
4845
import androidx.media3.session.MediaSession
49-
import androidx.media3.ui.DefaultTimeBar
5046
import com.nextcloud.client.account.User
5147
import com.nextcloud.client.account.UserAccountManager
5248
import com.nextcloud.client.di.Injectable
@@ -60,8 +56,10 @@ import com.nextcloud.client.network.ClientFactory.CreationException
6056
import com.nextcloud.common.NextcloudClient
6157
import com.nextcloud.ui.fileactions.FileAction
6258
import com.nextcloud.ui.fileactions.FileActionsBottomSheet.Companion.newInstance
59+
import com.nextcloud.utils.extensions.applyControlsInsets
6360
import com.nextcloud.utils.extensions.getParcelableArgument
6461
import com.nextcloud.utils.extensions.getTypedActivity
62+
import com.nextcloud.utils.extensions.setFullscreenButton
6563
import com.owncloud.android.R
6664
import com.owncloud.android.databinding.FragmentPreviewMediaBinding
6765
import com.owncloud.android.datamodel.OCFile
@@ -192,24 +190,11 @@ class PreviewMediaFragment :
192190
private fun applyWindowInsets() {
193191
binding.root.post {
194192
val rootInsets = ViewCompat.getRootWindowInsets(binding.root) ?: return@post
195-
val insets = rootInsets.getInsets(
196-
WindowInsetsCompat.Type.systemBars() or
197-
WindowInsetsCompat.Type.displayCutout()
193+
binding.exoplayerView.applyControlsInsets(
194+
rootInsets.getInsets(
195+
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
196+
)
198197
)
199-
val playerView = binding.exoplayerView
200-
val exoControls = playerView
201-
.findViewById<FrameLayout>(androidx.media3.ui.R.id.exo_bottom_bar)
202-
val exoProgress = playerView
203-
.findViewById<DefaultTimeBar>(androidx.media3.ui.R.id.exo_progress)
204-
val progressOriginalMargin = exoProgress?.marginBottom ?: 0
205-
exoControls?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
206-
bottomMargin = insets.bottom
207-
}
208-
exoProgress?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
209-
bottomMargin = insets.bottom + progressOriginalMargin
210-
}
211-
exoControls?.updatePadding(left = insets.left, right = insets.right)
212-
exoProgress?.updatePadding(left = insets.left, right = insets.right)
213198
}
214199
}
215200

@@ -324,7 +309,7 @@ class PreviewMediaFragment :
324309
setShowNextButton(false)
325310
setShowPreviousButton(false)
326311
player = exoPlayer
327-
setFullscreenButtonClickListener { startFullScreenVideo() }
312+
setFullscreenButton(isFullscreen = false) { startFullScreenVideo() }
328313
}
329314
}
330315

@@ -598,22 +583,12 @@ class PreviewMediaFragment :
598583
private const val AUTOPLAY = "AUTOPLAY"
599584
private const val IS_LIVE_PHOTO = "IS_LIVE_PHOTO"
600585

601-
/**
602-
* Creates a fragment to preview a file.
603-
*
604-
*
605-
* When 'fileToDetail' or 'user' are null
606-
*
607-
* @param fileToDetail An [OCFile] to preview in the fragment
608-
* @param user Currently active user
609-
*/
610-
@JvmStatic
611586
fun newInstance(
612587
fileToDetail: OCFile?,
613588
user: User?,
614-
startPlaybackPosition: Long,
615-
autoplay: Boolean,
616-
isLivePhoto: Boolean
589+
startPlaybackPosition: Long = 0,
590+
autoplay: Boolean = false,
591+
isLivePhoto: Boolean = false
617592
): PreviewMediaFragment = PreviewMediaFragment().apply {
618593
arguments = Bundle().apply {
619594
putParcelable(FILE, fileToDetail)
@@ -624,14 +599,7 @@ class PreviewMediaFragment :
624599
}
625600
}
626601

627-
/**
628-
* Helper method to test if an [OCFile] can be passed to a [PreviewMediaFragment] to be previewed.
629-
*
630-
* @param file File to test if can be previewed.
631-
* @return 'True' if the file can be handled by the fragment.
632-
*/
633-
@JvmStatic
634-
fun canBePreviewed(file: OCFile?): Boolean =
602+
fun isAudioOrVideo(file: OCFile?): Boolean =
635603
file != null && (MimeTypeUtil.isAudio(file) || MimeTypeUtil.isVideo(file))
636604
}
637605
}

0 commit comments

Comments
 (0)