diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt index f327606..4522a77 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt @@ -14,6 +14,8 @@ import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import sushi.hardcore.droidfs.BaseActivity import sushi.hardcore.droidfs.FileTypes @@ -21,7 +23,6 @@ import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.VolumeManagerApp import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.filesystems.EncryptedVolume -import sushi.hardcore.droidfs.util.IntentUtils import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.finishOnClose import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder @@ -32,9 +33,8 @@ abstract class FileViewerActivity: BaseActivity() { private lateinit var originalParentPath: String private lateinit var windowInsetsController: WindowInsetsControllerCompat private var windowTypeMask = 0 - private var foldersFirst = true - private var wasMapped = false - protected val mappedPlaylist = mutableListOf() + protected val playlist = mutableListOf() + private val playlistMutex = Mutex() protected var currentPlaylistIndex = -1 private val isLegacyFullscreen = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R @@ -46,7 +46,6 @@ abstract class FileViewerActivity: BaseActivity() { intent.getIntExtra("volumeId", -1) )!! finishOnClose(encryptedVolume) - foldersFirst = sharedPrefs.getBoolean("folders_first", true) windowInsetsController = WindowInsetsControllerCompat(window, window.decorView) windowInsetsController.addOnControllableInsetsChangedListener { _, typeMask -> windowTypeMask = typeMask @@ -131,48 +130,53 @@ abstract class FileViewerActivity: BaseActivity() { } } - protected fun createPlaylist() { - if (!wasMapped){ - encryptedVolume.recursiveMapFiles(originalParentPath)?.let { elements -> - for (e in elements) { - if (e.isRegularFile) { - if (FileTypes.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) { - mappedPlaylist.add(e) - } - } - } + protected suspend fun createPlaylist() { + playlistMutex.withLock { + if (currentPlaylistIndex != -1) { + // playlist already initialized + return } - val sortOrder = intent.getStringExtra("sortOrder") ?: "name" - ExplorerElement.sortBy(sortOrder, foldersFirst, mappedPlaylist) - //find current index - for ((i, e) in mappedPlaylist.withIndex()){ - if (filePath == e.fullPath){ - currentPlaylistIndex = i - break + withContext(Dispatchers.IO) { + encryptedVolume.recursiveMapFiles(originalParentPath)?.filterTo(playlist) { e -> + e.isRegularFile && (FileTypes.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) } + val sortOrder = intent.getStringExtra("sortOrder") ?: "name" + val foldersFirst = sharedPrefs.getBoolean("folders_first", true) + ExplorerElement.sortBy(sortOrder, foldersFirst, playlist) + currentPlaylistIndex = playlist.indexOfFirst { it.fullPath == filePath } } - wasMapped = true } } - protected fun playlistNext(forward: Boolean) { + private fun updateCurrentItem() { + filePath = playlist[currentPlaylistIndex].fullPath + } + + protected suspend fun playlistNext(forward: Boolean) { createPlaylist() currentPlaylistIndex = if (forward) { - (currentPlaylistIndex+1)%mappedPlaylist.size + (currentPlaylistIndex + 1).mod(playlist.size) } else { - var x = (currentPlaylistIndex-1)%mappedPlaylist.size - if (x < 0) { - x += mappedPlaylist.size - } - x + (currentPlaylistIndex - 1).mod(playlist.size) } - filePath = mappedPlaylist[currentPlaylistIndex].fullPath + updateCurrentItem() } - protected fun refreshPlaylist() { - mappedPlaylist.clear() - wasMapped = false - createPlaylist() + protected suspend fun deleteCurrentFile(): Boolean { + createPlaylist() // ensure we know the current position in the playlist + return if (encryptedVolume.deleteFile(filePath)) { + playlist.removeAt(currentPlaylistIndex) + if (playlist.size != 0) { + if (currentPlaylistIndex == playlist.size) { + // deleted the last element of the playlist, go back to the first + currentPlaylistIndex = 0 + } + updateCurrentItem() + } + true + } else { + false + } } protected fun goBackToExplorer() { diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt index c0ba353..4ec7182 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt @@ -12,10 +12,12 @@ import android.widget.Toast import androidx.activity.addCallback import androidx.activity.viewModels import androidx.lifecycle.ViewModel +import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import kotlinx.coroutines.launch import sushi.hardcore.droidfs.Constants import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.databinding.ActivityImageViewerBinding @@ -105,22 +107,21 @@ class ImageViewer: FileViewerActivity() { .keepFullScreen() .setTitle(R.string.warning) .setPositiveButton(R.string.ok) { _, _ -> - createPlaylist() //be sure the playlist is created before deleting if there is only one image - if (encryptedVolume.deleteFile(filePath)) { - playlistNext(true) - refreshPlaylist() - if (mappedPlaylist.size == 0) { //deleted all images of the playlist - goBackToExplorer() + lifecycleScope.launch { + if (deleteCurrentFile()) { + if (playlist.size == 0) { // no more image left + goBackToExplorer() + } else { + loadImage(true) + } } else { - loadImage(true) + CustomAlertDialogBuilder(this@ImageViewer, theme) + .keepFullScreen() + .setTitle(R.string.error) + .setMessage(getString(R.string.remove_failed, fileName)) + .setPositiveButton(R.string.ok, null) + .show() } - } else { - CustomAlertDialogBuilder(this, theme) - .keepFullScreen() - .setTitle(R.string.error) - .setMessage(getString(R.string.remove_failed, fileName)) - .setPositiveButton(R.string.ok, null) - .show() } } .setNegativeButton(R.string.cancel, null) @@ -198,14 +199,16 @@ class ImageViewer: FileViewerActivity() { rotateImage() } - private fun swipeImage(deltaX: Float, slideshowSwipe: Boolean = false){ - playlistNext(deltaX < 0) - loadImage(true) - if (slideshowActive) { - if (!slideshowSwipe) { //reset slideshow delay if user swipes - handler.removeCallbacks(slideshowNext) + private fun swipeImage(deltaX: Float, slideshowSwipe: Boolean = false) { + lifecycleScope.launch { + playlistNext(deltaX < 0) + loadImage(true) + if (slideshowActive) { + if (!slideshowSwipe) { // reset slideshow delay if user swipes + handler.removeCallbacks(slideshowNext) + } + handler.postDelayed(slideshowNext, Constants.SLIDESHOW_DELAY) } - handler.postDelayed(slideshowNext, Constants.SLIDESHOW_DELAY) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt index 8a14202..a4bee07 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt @@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.file_viewers import android.view.WindowManager import androidx.annotation.OptIn +import androidx.lifecycle.lifecycleScope import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackException import androidx.media3.common.Player @@ -11,6 +12,7 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.MediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.extractor.DefaultExtractorsFactory +import kotlinx.coroutines.launch import sushi.hardcore.droidfs.Constants import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder @@ -39,12 +41,16 @@ abstract class MediaPlayer: FileViewerActivity() { private fun initializePlayer(){ player = ExoPlayer.Builder(this).setSeekForwardIncrementMs(5000).build() bindPlayer(player) - createPlaylist() - for (e in mappedPlaylist) { - player.addMediaSource(createMediaSource(e.fullPath)) + player.addMediaSource(createMediaSource(filePath)) + lifecycleScope.launch { + createPlaylist() + playlist.forEachIndexed { index, e -> + if (index != currentPlaylistIndex) { + player.addMediaSource(index, createMediaSource(e.fullPath)) + } + } } player.repeatMode = Player.REPEAT_MODE_ALL - player.seekToDefaultPosition(currentPlaylistIndex) player.playWhenReady = true player.addListener(object : Player.Listener{ override fun onVideoSizeChanged(videoSize: VideoSize) { @@ -67,9 +73,11 @@ abstract class MediaPlayer: FileViewerActivity() { } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { - if (player.repeatMode != Player.REPEAT_MODE_ONE) { - playlistNext(player.currentMediaItemIndex == (currentPlaylistIndex + 1) % mappedPlaylist.size) - refreshFileName() + if (player.repeatMode != Player.REPEAT_MODE_ONE && currentPlaylistIndex != -1) { + lifecycleScope.launch { + playlistNext(player.currentMediaItemIndex == (currentPlaylistIndex + 1) % player.mediaItemCount) + refreshFileName() + } } } })