diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/torrentfilepicker/view/TorrentProcessingFragment.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/torrentfilepicker/view/TorrentProcessingFragment.kt index c19b003ae..2a8485bb2 100644 --- a/app/app/src/main/java/com/github/livingwithhippos/unchained/torrentfilepicker/view/TorrentProcessingFragment.kt +++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/torrentfilepicker/view/TorrentProcessingFragment.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.ViewGroup import android.widget.PopupMenu import androidx.annotation.MenuRes +import androidx.core.widget.addTextChangedListener import androidx.hilt.navigation.fragment.hiltNavGraphViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -104,18 +105,11 @@ class TorrentProcessingFragment : UnchainedFragment(), TorrentContentListener { ) findNavController().navigate(action) } else { - val filesList = mutableListOf() val torrentStructure: Node = getFilesNodes(content.item, selectedOnly = false) if (torrentStructure.children.isNotEmpty()) { currentStructure = torrentStructure - Node.traverseDepthFirst(torrentStructure) { item -> - filesList.add(item) - } - (binding.rvTorrentFilePicker.adapter - as TorrentContentFilesSelectionAdapter) - .submitList(filesList) - binding.rvTorrentFilePicker.adapter?.notifyDataSetChanged() + updateFilesList() } binding.loadingLayout.visibility = View.INVISIBLE @@ -169,13 +163,7 @@ class TorrentProcessingFragment : UnchainedFragment(), TorrentContentListener { } is TorrentEvent.SelectionUpdated -> { - currentStructure?.let { structure -> - val filesList = mutableListOf() - Node.traverseDepthFirst(structure) { item -> filesList.add(item) } - (binding.rvTorrentFilePicker.adapter as TorrentContentFilesSelectionAdapter) - .submitList(filesList) - binding.rvTorrentFilePicker.adapter?.notifyDataSetChanged() - } + updateFilesList() } else -> { @@ -237,6 +225,18 @@ class TorrentProcessingFragment : UnchainedFragment(), TorrentContentListener { binding.fabDownload.setOnClickListener { showMenu(it, R.menu.download_mode_picker) } + binding.tiFilter.addTextChangedListener { + // the selection checkbox refers to the currently visible files + binding.cbSelectAll.isChecked = false + updateFilesList() + } + + binding.cbSelectAll.setOnClickListener { + val selected = binding.cbSelectAll.isChecked + getFilteredFiles().forEach { item -> item.selected = selected } + viewModel.updateTorrentStructure() + } + if (args.torrentID != null) { // we are loading an already available torrent args.torrentID?.let { viewModel.fetchTorrentDetails(it) } @@ -266,6 +266,41 @@ class TorrentProcessingFragment : UnchainedFragment(), TorrentContentListener { } } + /** + * Returns the files matching the current filter. With an empty filter the whole structure is + * returned, folders included, otherwise only the matching files are listed, see #450 + */ + private fun getFilteredFiles(): List { + if (_binding == null) return emptyList() + val structure = currentStructure ?: return emptyList() + val query = binding.tiFilter.text?.toString()?.trim() ?: "" + val filesList = mutableListOf() + if (query.isEmpty()) { + Node.traverseDepthFirst(structure) { item -> filesList.add(item) } + } else { + // a query with wildcards must match the whole file name, a plain one any part of it + val regexQuery = + if (query.contains('*')) { + val pattern = query.split('*').joinToString(".*") { Regex.escape(it) } + Regex("^$pattern$", RegexOption.IGNORE_CASE) + } else { + Regex(Regex.escape(query), RegexOption.IGNORE_CASE) + } + Node.traverseDepthFirst(structure) { item -> + if (item.id != TYPE_FOLDER && regexQuery.containsMatchIn(item.name)) + filesList.add(item) + } + } + return filesList + } + + private fun updateFilesList() { + if (_binding == null) return + with(binding.rvTorrentFilePicker.adapter as TorrentContentFilesSelectionAdapter) { + submitList(getFilteredFiles()) + notifyDataSetChanged() + } + private fun showMenu(v: View, @MenuRes menuRes: Int) { val popup = PopupMenu(requireContext(), v) popup.menuInflater.inflate(menuRes, popup.menu) diff --git a/app/app/src/main/res/layout/fragment_torrent_processing.xml b/app/app/src/main/res/layout/fragment_torrent_processing.xml index 905f944d8..414afaa41 100644 --- a/app/app/src/main/res/layout/fragment_torrent_processing.xml +++ b/app/app/src/main/res/layout/fragment_torrent_processing.xml @@ -69,11 +69,48 @@ android:layout_marginTop="10dp" android:text="@string/select_files" android:textAlignment="center" - app:layout_constraintBottom_toTopOf="@id/rvTorrentFilePicker" + app:layout_constraintBottom_toTopOf="@id/tfFilter" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index 372c8d2d9..14916a17d 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -654,6 +654,7 @@ Previous Ready Select files + Filter names, * supported Total files Total size Loading torrent files. Use the download all button on the bottom if you want to download all the content.