[Android] Allow renaming a file or a folder

Nicolas Pomepuy git at videolan.org
Tue Jan 9 15:39:34 UTC 2024


vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Mon Jan  8 10:50:03 2024 +0100| [63ee26448b8076532d56f1ed05f4609014adb72d] | committer: Duncan McNamara

Allow renaming a file or a folder

Fixes #3018

> https://code.videolan.org/videolan/vlc-android/commit/63ee26448b8076532d56f1ed05f4609014adb72d
---

 .../vlc/gui/browser/BaseBrowserFragment.kt         | 66 ++++++++++++++++++++--
 .../org/videolan/vlc/gui/dialogs/RenameDialog.kt   | 16 ++++--
 2 files changed, 73 insertions(+), 9 deletions(-)

diff --git a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt
index dea76bb518..e17c522cd5 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserFragment.kt
@@ -26,7 +26,11 @@ import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
 import android.util.Log
-import android.view.*
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
 import androidx.appcompat.view.ActionMode
 import androidx.coordinatorlayout.widget.CoordinatorLayout
 import androidx.core.content.ContextCompat
@@ -42,18 +46,49 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 import com.google.android.material.appbar.AppBarLayout
 import com.google.android.material.floatingactionbutton.FloatingActionButton
 import com.google.android.material.snackbar.Snackbar
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.videolan.libvlc.MediaPlayer
 import org.videolan.libvlc.interfaces.IMedia
 import org.videolan.medialibrary.MLServiceLocator
 import org.videolan.medialibrary.interfaces.media.MediaWrapper
 import org.videolan.medialibrary.media.MediaLibraryItem
-import org.videolan.resources.*
+import org.videolan.resources.AndroidDevices
+import org.videolan.resources.CTX_ADD_FOLDER_AND_SUB_PLAYLIST
+import org.videolan.resources.CTX_ADD_FOLDER_PLAYLIST
+import org.videolan.resources.CTX_ADD_SCANNED
+import org.videolan.resources.CTX_ADD_TO_PLAYLIST
+import org.videolan.resources.CTX_APPEND
+import org.videolan.resources.CTX_DELETE
+import org.videolan.resources.CTX_DOWNLOAD_SUBTITLES
+import org.videolan.resources.CTX_FAV_ADD
+import org.videolan.resources.CTX_FAV_REMOVE
+import org.videolan.resources.CTX_FIND_METADATA
+import org.videolan.resources.CTX_INFORMATION
+import org.videolan.resources.CTX_PLAY
+import org.videolan.resources.CTX_PLAY_ALL
+import org.videolan.resources.CTX_PLAY_AS_AUDIO
+import org.videolan.resources.CTX_RENAME
+import org.videolan.resources.KEY_MRL
+import org.videolan.resources.MOVIEPEDIA_ACTIVITY
+import org.videolan.resources.MOVIEPEDIA_MEDIA
 import org.videolan.resources.util.getFromMl
 import org.videolan.resources.util.parcelable
-import org.videolan.tools.*
+import org.videolan.tools.BROWSER_SHOW_HIDDEN_FILES
+import org.videolan.tools.FORCE_PLAY_ALL_AUDIO
+import org.videolan.tools.FORCE_PLAY_ALL_VIDEO
+import org.videolan.tools.MultiSelectHelper
+import org.videolan.tools.Settings
+import org.videolan.tools.isStarted
+import org.videolan.tools.putSingle
+import org.videolan.tools.removeFileScheme
 import org.videolan.vlc.BuildConfig
 import org.videolan.vlc.PlaybackService
 import org.videolan.vlc.R
@@ -61,6 +96,7 @@ import org.videolan.vlc.databinding.DirectoryBrowserBinding
 import org.videolan.vlc.gui.AudioPlayerContainerActivity
 import org.videolan.vlc.gui.dialogs.ConfirmDeleteDialog
 import org.videolan.vlc.gui.dialogs.CtxActionReceiver
+import org.videolan.vlc.gui.dialogs.RenameDialog
 import org.videolan.vlc.gui.dialogs.SavePlaylistDialog
 import org.videolan.vlc.gui.dialogs.showContext
 import org.videolan.vlc.gui.helpers.MedialibraryUtils
@@ -82,7 +118,8 @@ import org.videolan.vlc.util.SchedulerCallback
 import org.videolan.vlc.util.isSchemeSupported
 import org.videolan.vlc.util.isTalkbackIsEnabled
 import org.videolan.vlc.viewmodels.browser.BrowserModel
-import java.util.*
+import java.io.File
+import java.util.LinkedList
 
 private const val TAG = "VLC/BaseBrowserFragment"
 
@@ -583,6 +620,7 @@ abstract class BaseBrowserFragment : MediaBrowserFragment<BrowserModel>(), IRefr
                     if (viewModel.provider.hasSubfolders(mw)) flags = flags or CTX_ADD_FOLDER_AND_SUB_PLAYLIST
                     flags = flags or CTX_APPEND
                 }
+                flags = flags or CTX_RENAME
             } else {
                 val isVideo = mw.type == MediaWrapper.TYPE_VIDEO
                 val isAudio = mw.type == MediaWrapper.TYPE_AUDIO
@@ -591,6 +629,7 @@ abstract class BaseBrowserFragment : MediaBrowserFragment<BrowserModel>(), IRefr
                 if (!isAudio && isMedia) flags = flags or CTX_PLAY_AS_AUDIO
                 if (!isMedia) flags = flags or CTX_PLAY
                 if (isVideo) flags = flags or CTX_DOWNLOAD_SUBTITLES
+                flags = flags or CTX_RENAME
             }
             if (flags != 0L) showContext(requireActivity(), this at BaseBrowserFragment, position, item, flags)
         }
@@ -624,6 +663,23 @@ abstract class BaseBrowserFragment : MediaBrowserFragment<BrowserModel>(), IRefr
                     MediaUtils.appendMedia(activity, getMediaWithMeta(mw))
             }
             CTX_DELETE -> removeItem(mw)
+            CTX_RENAME -> {
+                val dialog = RenameDialog.newInstance(mw, true)
+                dialog.show(requireActivity().supportFragmentManager, RenameDialog::class.simpleName)
+                dialog.setListener { item, name ->
+                    lifecycleScope.launch(Dispatchers.IO) {
+                        (item as MediaWrapper).uri.path?.let { File(it) }?.let { file ->
+                            if (file.exists()) {
+                                file.parent?.let {
+                                    val newFile = File("$it/$name")
+                                    file.renameTo(newFile)
+                                }
+                            }
+                        }
+                        viewModel.refresh()
+                    }
+                }
+            }
             CTX_INFORMATION -> requireActivity().showMediaInfo(mw)
             CTX_PLAY_AS_AUDIO -> lifecycleScope.launch {
                 mw.addFlags(MediaWrapper.MEDIA_FORCE_AUDIO)
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/dialogs/RenameDialog.kt b/application/vlc-android/src/org/videolan/vlc/gui/dialogs/RenameDialog.kt
index b62fae3de0..b17bbc0834 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/dialogs/RenameDialog.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/dialogs/RenameDialog.kt
@@ -63,6 +63,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import org.videolan.medialibrary.interfaces.media.MediaWrapper
 import org.videolan.medialibrary.media.MediaLibraryItem
 import org.videolan.resources.util.parcelable
 import org.videolan.vlc.R
@@ -70,6 +71,7 @@ import org.videolan.vlc.gui.helpers.UiTools
 import org.videolan.vlc.gui.helpers.UiTools.showPinIfNeeded
 
 const val RENAME_DIALOG_MEDIA = "RENAME_DIALOG_MEDIA"
+const val RENAME_DIALOG_FILE = "RENAME_DIALOG_FILE"
 
 class RenameDialog : VLCBottomSheetDialogFragment() {
 
@@ -77,13 +79,14 @@ class RenameDialog : VLCBottomSheetDialogFragment() {
     private lateinit var renameButton: Button
     private lateinit var newNameInputtext: TextInputEditText
     private lateinit var media: MediaLibraryItem
+    private var renameFile: Boolean = false
 
     companion object {
 
-        fun newInstance(media: MediaLibraryItem): RenameDialog {
+        fun newInstance(media: MediaLibraryItem, isFile:Boolean = false): RenameDialog {
 
             return RenameDialog().apply {
-                arguments = bundleOf(RENAME_DIALOG_MEDIA to media)
+                arguments = bundleOf(RENAME_DIALOG_MEDIA to media, RENAME_DIALOG_FILE to isFile)
             }
         }
     }
@@ -95,16 +98,21 @@ class RenameDialog : VLCBottomSheetDialogFragment() {
     override fun onCreate(savedInstanceState: Bundle?) {
         lifecycleScope.launch { if (requireActivity().showPinIfNeeded()) dismiss() }
         media = arguments?.parcelable(RENAME_DIALOG_MEDIA) ?: return
+        renameFile = arguments?.getBoolean(RENAME_DIALOG_FILE) ?: false
         super.onCreate(savedInstanceState)
     }
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                               savedInstanceState: Bundle?): View? {
         val view = inflater.inflate(R.layout.dialog_rename, container)
-        val name = media.title
+        val name = if (renameFile && media is MediaWrapper) (media as MediaWrapper).fileName else media.title
         newNameInputtext = view.findViewById(R.id.new_name)
         renameButton = view.findViewById(R.id.rename_button)
-        if (media.title.isNotEmpty()) newNameInputtext.setText(media.title)
+        if (media.title.isNotEmpty()) {
+            newNameInputtext.setText(name)
+        }
+        val extIndex = name.indexOfLast { it == '.' }
+        if (extIndex != -1 && renameFile) newNameInputtext.setSelection(0, extIndex) else newNameInputtext.setSelection(0, name.length)
         renameButton.setOnClickListener {
             performRename()
         }



More information about the Android mailing list