From 69eb9e84d24000fe02c990531404e3843447e0c2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 26 May 2026 09:09:09 +0300 Subject: [PATCH 1/4] wip Signed-off-by: alperozturk96 --- .../ui/preview/PreviewMediaActivity.kt | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt index abc808e6f1eb..cbda8838028b 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt @@ -12,7 +12,7 @@ */ package com.owncloud.android.ui.preview -import android.app.Activity +import android.annotation.SuppressLint import android.content.ComponentName import android.content.DialogInterface import android.content.Intent @@ -23,8 +23,10 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.AsyncTask import android.os.Bundle +import android.view.GestureDetector import android.view.Menu import android.view.MenuItem +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.FrameLayout @@ -36,6 +38,7 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri +import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -100,6 +103,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.lang.ref.WeakReference +import kotlin.math.abs /** * This activity shows a preview of a downloaded media file (audio or video). @@ -134,6 +138,9 @@ class PreviewMediaActivity : private var mediaControllerFuture: ListenableFuture? = null private lateinit var windowInsetsController: WindowInsetsControllerCompat + private var swipeGestureDetector: GestureDetectorCompat? = null + private var isHorizontalSwipeActive = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -492,6 +499,47 @@ class PreviewMediaActivity : it.player = videoPlayer it.setFullscreenButtonClickListener { startFullScreenVideo() } } + + setupSwipeGestures() + } + + @SuppressLint("ClickableViewAccessibility") + private fun setupSwipeGestures() { + val gestureListener = object : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent): Boolean = true + + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + if (abs(distanceX) < abs(distanceY)) return false + + if (!isHorizontalSwipeActive) { + isHorizontalSwipeActive = true + } + + // TODO CALL PAGER + return true + } + } + + swipeGestureDetector = GestureDetectorCompat(this, gestureListener) + + binding.exoplayerView.setOnTouchListener { _, event -> + swipeGestureDetector?.onTouchEvent(event) + + when (event.action) { + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + if (isHorizontalSwipeActive) { + return@setOnTouchListener true + } + } + } + + false + } } private fun startFullScreenVideo() { @@ -799,13 +847,8 @@ class PreviewMediaActivity : showDetails(file) } - override fun onBrowsedDownTo(folder: OCFile?) { - // TODO Auto-generated method stub - } - - override fun onTransferStateChanged(file: OCFile?, downloading: Boolean, uploading: Boolean) { - // TODO Auto-generated method stub - } + override fun onBrowsedDownTo(folder: OCFile?) = Unit + override fun onTransferStateChanged(file: OCFile?, downloading: Boolean, uploading: Boolean) = Unit override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) @@ -818,15 +861,12 @@ class PreviewMediaActivity : Log_OC.v(TAG, "onActivityResult $this") super.onActivityResult(requestCode, resultCode, data) - if (resultCode == Activity.RESULT_OK) { + if (resultCode == RESULT_OK) { savedPlaybackPosition = data?.getLongExtra(EXTRA_START_POSITION, 0) ?: 0 autoplay = data?.getBooleanExtra(EXTRA_AUTOPLAY, false) ?: false } } - /** - * Opens the previewed file with an external application. - */ private fun openFile() { stopPreview(true) fileOperationsHelper.openFile(file) @@ -855,12 +895,6 @@ class PreviewMediaActivity : private const val PLAYBACK_POSITION = "PLAYBACK_POSITION" private const val AUTOPLAY = "AUTOPLAY" - /** - * Helper method to test if an [OCFile] can be passed to a [PreviewMediaActivity] to be previewed. - * - * @param file File to test if can be previewed. - * @return 'True' if the file can be handled by the activity. - */ fun canBePreviewed(file: OCFile?): Boolean = file != null && (MimeTypeUtil.isAudio(file) || MimeTypeUtil.isVideo(file)) } From c288a49f34be47efc32b9cc55f9cbf9dbb61d373 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 26 May 2026 09:12:36 +0300 Subject: [PATCH 2/4] wip Signed-off-by: alperozturk96 --- .../owncloud/android/ui/preview/PreviewMediaActivity.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt index cbda8838028b..8f2d6d770b09 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt @@ -38,7 +38,6 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri -import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -138,7 +137,6 @@ class PreviewMediaActivity : private var mediaControllerFuture: ListenableFuture? = null private lateinit var windowInsetsController: WindowInsetsControllerCompat - private var swipeGestureDetector: GestureDetectorCompat? = null private var isHorizontalSwipeActive = false override fun onCreate(savedInstanceState: Bundle?) { @@ -525,10 +523,9 @@ class PreviewMediaActivity : } } - swipeGestureDetector = GestureDetectorCompat(this, gestureListener) - + val swipeGestureDetector = GestureDetector(this, gestureListener) binding.exoplayerView.setOnTouchListener { _, event -> - swipeGestureDetector?.onTouchEvent(event) + swipeGestureDetector.onTouchEvent(event) when (event.action) { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { From adb0dcf61f1c0cd9c5561515098c4e76eb913eb3 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 26 May 2026 09:17:00 +0300 Subject: [PATCH 3/4] wip Signed-off-by: alperozturk96 --- .../ui/preview/PreviewMediaActivity.kt | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt index 8f2d6d770b09..2eb57bbc026a 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt @@ -516,9 +516,12 @@ class PreviewMediaActivity : if (!isHorizontalSwipeActive) { isHorizontalSwipeActive = true + // decide direction and open next/previous media in same folder + // distanceX > 0 -> user swiped left -> show next + // distanceX < 0 -> user swiped right -> show previous + handleHorizontalSwipe(distanceX) } - // TODO CALL PAGER return true } } @@ -530,6 +533,8 @@ class PreviewMediaActivity : when (event.action) { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { if (isHorizontalSwipeActive) { + // reset flag after gesture completed + isHorizontalSwipeActive = false return@setOnTouchListener true } } @@ -539,6 +544,46 @@ class PreviewMediaActivity : } } + private fun handleHorizontalSwipe(distanceX: Float) { + val currentFile = file ?: return + + val parentFolder = storageManager.getFileById(currentFile.parentId) ?: return + + val folderContent = storageManager.getFolderContent(parentFolder, false) + val mediaItems = folderContent.filter { MimeTypeUtil.isImageOrVideo(it) } + + if (mediaItems.isEmpty()) return + + val currentIndex = mediaItems.indexOfFirst { it.fileId == currentFile.fileId } + if (currentIndex == -1) return + + val targetIndex = if (distanceX > 0) currentIndex + 1 else currentIndex - 1 + if (targetIndex < 0 || targetIndex >= mediaItems.size) return + + val targetFile = mediaItems[targetIndex] + openPreviewForFile(targetFile) + } + + private fun openPreviewForFile(targetFile: OCFile) { + stopPreview(false) + + if (MimeTypeUtil.isImage(targetFile)) { + val intent = PreviewImageActivity.previewFileIntent(this, user, targetFile) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + finish() + } else if (MimeTypeUtil.isVideo(targetFile) || MimeTypeUtil.isAudio(targetFile)) { + val intent = Intent(this, PreviewMediaActivity::class.java).apply { + putExtra(FILE, targetFile) + putExtra(USER, user) + putExtra(AUTOPLAY, true) + } + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + finish() + } + } + private fun startFullScreenVideo() { val client = nextcloudClient ?: return val player = videoPlayer ?: return From acb0acb4dd5afc07aac5779b797980a8a8678b25 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 26 May 2026 10:10:57 +0300 Subject: [PATCH 4/4] wip Signed-off-by: alperozturk96 --- .../ui/preview/PreviewImageActivity.kt | 44 ++++++++++++ .../ui/preview/PreviewMediaActivity.kt | 71 ++++++++++--------- 2 files changed, 82 insertions(+), 33 deletions(-) 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 fbca6af56939..259f42286b3b 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 @@ -12,7 +12,9 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Bundle +import android.view.GestureDetector import android.view.MenuItem +import android.view.MotionEvent import android.view.View import androidx.activity.OnBackPressedCallback import androidx.core.content.ContextCompat @@ -87,6 +89,8 @@ class PreviewImageActivity : @Inject lateinit var localBroadcastManager: LocalBroadcastManager + private var backSwipeGestureDetector: GestureDetector? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -206,6 +210,7 @@ class PreviewImageActivity : viewPager?.setCurrentItem(position, false) } + initPreviousMediaSwipeGesture() if (position == 0 && file?.isDown == false) { // this is necessary because mViewPager.setCurrentItem(0) just after setting the // adapter does not result in a call to #onPageSelected(0) @@ -213,6 +218,44 @@ class PreviewImageActivity : } } + @Suppress("ReturnCount") + private fun initPreviousMediaSwipeGesture() { + val previousMediaFile: OCFile? = intent.getParcelableArgument(EXTRA_PREVIOUS_MEDIA_FILE, OCFile::class.java) + if (previousMediaFile != null) { + val gestureListener = object : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent): Boolean = true + + override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { + if (kotlin.math.abs(distanceX) < kotlin.math.abs(distanceY)) return false + + // only trigger for swipe to left since this logic is needed for previous item + if ((viewPager?.currentItem ?: 0) == 0 && distanceX < 0) { + backSwipeGestureDetector = null + val mediaIntent = Intent(this@PreviewImageActivity, PreviewMediaActivity::class.java).apply { + putExtra(PreviewMediaActivity.EXTRA_FILE, previousMediaFile) + + user.ifPresent { + putExtra(PreviewMediaActivity.EXTRA_USER, it) + } + + putExtra(PreviewMediaActivity.EXTRA_AUTOPLAY, true) + } + startActivity(mediaIntent) + finish() + return true + } + return false + } + } + backSwipeGestureDetector = GestureDetector(this, gestureListener) + } + } + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + backSwipeGestureDetector?.onTouchEvent(ev) + return super.dispatchTouchEvent(ev) + } + override fun onFilesRemoved() { initViewPager() } @@ -568,6 +611,7 @@ class PreviewImageActivity : const val EXTRA_VIRTUAL_TYPE: String = "EXTRA_VIRTUAL_TYPE" private const val KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER" private const val KEY_SYSTEM_VISIBLE = "TRUE" + const val EXTRA_PREVIOUS_MEDIA_FILE = "EXTRA_PREVIOUS_MEDIA_FILE" fun previewFileIntent(context: Context?, user: User?, file: OCFile?): Intent = Intent(context, PreviewImageActivity::class.java).apply { diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt index 2eb57bbc026a..f7de9803f0e3 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt @@ -114,7 +114,7 @@ import kotlin.math.abs * By now, if the [OCFile] passed is not downloaded, an [IllegalStateException] is generated on * instantiation too. */ -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass", "ReturnCount") @OptIn(UnstableApi::class) class PreviewMediaActivity : FileActivity(), @@ -137,6 +137,7 @@ class PreviewMediaActivity : private var mediaControllerFuture: ListenableFuture? = null private lateinit var windowInsetsController: WindowInsetsControllerCompat + private var mediaItemsInSameDirectory: List = listOf() private var isHorizontalSwipeActive = false override fun onCreate(savedInstanceState: Bundle?) { @@ -148,7 +149,7 @@ class PreviewMediaActivity : WindowCompat.setDecorFitsSystemWindows(window, false) applyWindowInsets() initArguments(savedInstanceState) - + prepareMediaItemsInSameDirectory() if (MimeTypeUtil.isVideo(file)) { // release any background media session if exists sendAudioSessionReleaseBroadcast() @@ -506,19 +507,11 @@ class PreviewMediaActivity : val gestureListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean = true - override fun onScroll( - e1: MotionEvent?, - e2: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { + override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { if (abs(distanceX) < abs(distanceY)) return false if (!isHorizontalSwipeActive) { isHorizontalSwipeActive = true - // decide direction and open next/previous media in same folder - // distanceX > 0 -> user swiped left -> show next - // distanceX < 0 -> user swiped right -> show previous handleHorizontalSwipe(distanceX) } @@ -533,7 +526,6 @@ class PreviewMediaActivity : when (event.action) { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { if (isHorizontalSwipeActive) { - // reset flag after gesture completed isHorizontalSwipeActive = false return@setOnTouchListener true } @@ -544,23 +536,25 @@ class PreviewMediaActivity : } } + private fun prepareMediaItemsInSameDirectory() { + lifecycleScope.launch(Dispatchers.IO) { + val currentFile = file ?: return@launch + val parentFolder = storageManager.getFileById(currentFile.parentId) ?: return@launch + val folderContent = storageManager.getFolderContent(parentFolder, false) + mediaItemsInSameDirectory = folderContent.filter { MimeTypeUtil.isImageOrVideo(it) } + } + } + private fun handleHorizontalSwipe(distanceX: Float) { val currentFile = file ?: return - - val parentFolder = storageManager.getFileById(currentFile.parentId) ?: return - - val folderContent = storageManager.getFolderContent(parentFolder, false) - val mediaItems = folderContent.filter { MimeTypeUtil.isImageOrVideo(it) } - - if (mediaItems.isEmpty()) return - - val currentIndex = mediaItems.indexOfFirst { it.fileId == currentFile.fileId } + if (mediaItemsInSameDirectory.isEmpty()) return + val currentIndex = mediaItemsInSameDirectory.indexOfFirst { it.fileId == currentFile.fileId } if (currentIndex == -1) return val targetIndex = if (distanceX > 0) currentIndex + 1 else currentIndex - 1 - if (targetIndex < 0 || targetIndex >= mediaItems.size) return + if (targetIndex < 0 || targetIndex >= mediaItemsInSameDirectory.size) return - val targetFile = mediaItems[targetIndex] + val targetFile = mediaItemsInSameDirectory[targetIndex] openPreviewForFile(targetFile) } @@ -569,18 +563,29 @@ class PreviewMediaActivity : if (MimeTypeUtil.isImage(targetFile)) { val intent = PreviewImageActivity.previewFileIntent(this, user, targetFile) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(intent) - finish() - } else if (MimeTypeUtil.isVideo(targetFile) || MimeTypeUtil.isAudio(targetFile)) { - val intent = Intent(this, PreviewMediaActivity::class.java).apply { - putExtra(FILE, targetFile) - putExtra(USER, user) - putExtra(AUTOPLAY, true) - } - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + file?.let { intent.putExtra(PreviewImageActivity.EXTRA_PREVIOUS_MEDIA_FILE, it) } startActivity(intent) finish() + return + } + + if (MimeTypeUtil.isVideo(targetFile) || MimeTypeUtil.isAudio(targetFile)) { + updateVideoFile(targetFile) + } + } + + private fun updateVideoFile(targetFile: OCFile) { + file = targetFile + savedPlaybackPosition = 0L + autoplay = true + showMediaTypeViews() + configureSystemBars() + showProgressLayout() + + if (MimeTypeUtil.isVideo(file)) { + initializeVideoPlayer() + } else if (MimeTypeUtil.isAudio(file)) { + initializeAudioPlayer() } }