diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt index 2eb564a6f..e52270769 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.kt @@ -13,12 +13,10 @@ */ package code.name.monkey.retromusic.fragments.folder -import android.app.Dialog import android.content.Context import android.media.MediaScannerConnection import android.os.Bundle import android.os.Environment -import android.text.Html import android.view.Menu import android.view.MenuInflater import android.view.MenuItem @@ -26,7 +24,9 @@ import android.view.View import android.webkit.MimeTypeMap import androidx.activity.OnBackPressedCallback import androidx.appcompat.widget.PopupMenu +import androidx.core.text.parseAsHtml import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import androidx.navigation.fragment.findNavController @@ -35,7 +35,6 @@ import androidx.recyclerview.widget.RecyclerView import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper -import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.SongFileAdapter import code.name.monkey.retromusic.adapter.Storage @@ -44,17 +43,13 @@ import code.name.monkey.retromusic.adapter.StorageClickListener import code.name.monkey.retromusic.databinding.FragmentFolderBinding import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment -import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListPathsAsyncTask.OnPathsListedCallback -import code.name.monkey.retromusic.fragments.folder.FoldersFragment.ListSongsAsyncTask.OnSongsListedCallback import code.name.monkey.retromusic.helper.MusicPlayerRemote.openQueue import code.name.monkey.retromusic.helper.MusicPlayerRemote.playingQueue -import code.name.monkey.retromusic.helper.menu.SongMenuHelper.handleMenuClick import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.interfaces.ICabCallback import code.name.monkey.retromusic.interfaces.ICabHolder import code.name.monkey.retromusic.interfaces.ICallbacks import code.name.monkey.retromusic.interfaces.IMainActivityFragmentCallbacks -import code.name.monkey.retromusic.misc.DialogAsyncTask import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader import code.name.monkey.retromusic.model.Song @@ -69,10 +64,12 @@ import com.afollestad.materialcab.attached.AttachedCab import com.afollestad.materialcab.attached.destroy import com.afollestad.materialcab.attached.isActive import com.afollestad.materialcab.createCab -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.snackbar.Snackbar import com.google.android.material.transition.MaterialFadeThrough +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File import java.io.FileFilter import java.io.IOException @@ -101,6 +98,7 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { _binding = FragmentFolderBinding.bind(view) + setHasOptionsMenu(true) mainActivity.addMusicServiceEventListener(libraryViewModel) mainActivity.setSupportActionBar(binding.toolbar) mainActivity.supportActionBar?.title = null @@ -185,23 +183,20 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), popupMenu.setOnMenuItemClickListener { item: MenuItem -> when (val itemId = item.itemId) { R.id.action_play_next, R.id.action_add_to_current_playing, R.id.action_add_to_playlist, R.id.action_delete_from_device -> { - ListSongsAsyncTask( - activity, - null, - object : OnSongsListedCallback { - override fun onSongsListed(songs: List, extra: Any?) { - if (songs.isNotEmpty()) { - SongsMenuHelper.handleMenuClick( - requireActivity(), songs, itemId - ) - } + lifecycleScope.launch(Dispatchers.IO) { + listSongs( + requireContext(), + toList(file), + AUDIO_FILE_FILTER, + fileComparator + ) { songs -> + if (songs.isNotEmpty()) { + SongsMenuHelper.handleMenuClick( + requireActivity(), songs, itemId + ) } - }) - .execute( - ListSongsAsyncTask.LoadingInfo( - toList(file), AUDIO_FILE_FILTER, fileComparator - ) - ) + } + } return@setOnMenuItemClickListener true } R.id.action_add_to_blacklist -> { @@ -216,14 +211,9 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), return@setOnMenuItemClickListener true } R.id.action_scan -> { - ListPathsAsyncTask( - activity, - object : OnPathsListedCallback { - override fun onPathsListed(paths: Array) { - scanPaths(paths) - } - }) - .execute(ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)) + lifecycleScope.launch { + listPaths(file, AUDIO_FILE_FILTER) { paths -> scanPaths(paths) } + } return@setOnMenuItemClickListener true } } @@ -234,32 +224,26 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), popupMenu.setOnMenuItemClickListener { item: MenuItem -> when (val itemId = item.itemId) { R.id.action_play_next, R.id.action_add_to_current_playing, R.id.action_add_to_playlist, R.id.action_go_to_album, R.id.action_go_to_artist, R.id.action_share, R.id.action_tag_editor, R.id.action_details, R.id.action_set_as_ringtone, R.id.action_delete_from_device -> { - ListSongsAsyncTask( - activity, - null, - object : OnSongsListedCallback { - override fun onSongsListed(songs: List, extra: Any?) { - handleMenuClick( - requireActivity(), songs[0], itemId + lifecycleScope.launch(Dispatchers.IO) { + listSongs( + requireContext(), + toList(file), + AUDIO_FILE_FILTER, + fileComparator + ) { songs -> + if (songs.isNotEmpty()) { + SongsMenuHelper.handleMenuClick( + requireActivity(), songs, itemId ) } - }) - .execute( - ListSongsAsyncTask.LoadingInfo( - toList(file), AUDIO_FILE_FILTER, fileComparator - ) - ) + } + } return@setOnMenuItemClickListener true } R.id.action_scan -> { - ListPathsAsyncTask( - activity, - object : OnPathsListedCallback { - override fun onPathsListed(paths: Array) { - scanPaths(paths) - } - }) - .execute(ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)) + lifecycleScope.launch { + listPaths(file, AUDIO_FILE_FILTER) { paths -> scanPaths(paths) } + } return@setOnMenuItemClickListener true } } @@ -278,16 +262,17 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), val fileFilter = FileFilter { pathname: File -> !pathname.isDirectory && AUDIO_FILE_FILTER.accept(pathname) } - ListSongsAsyncTask( - activity, - mFile, - object : OnSongsListedCallback { - override fun onSongsListed(songs: List, extra: Any?) { - val file1 = extra as File + lifecycleScope.launch(Dispatchers.IO) { + listSongs( + requireContext(), + toList(mFile.parentFile), + fileFilter, + fileComparator + ) { songs -> + if (songs.isNotEmpty()) { var startIndex = -1 for (i in songs.indices) { - if (file1 - .path + if (mFile.path == songs[i].data ) { // path is already canonical here startIndex = i @@ -299,39 +284,29 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), } else { Snackbar.make( mainActivity.slidingPanel, - Html.fromHtml( - String.format( - getString(R.string.not_listed_in_media_store), file1.name - ) - ), + String.format( + getString(R.string.not_listed_in_media_store), mFile.name + + ).parseAsHtml(), Snackbar.LENGTH_LONG ) .setAction( R.string.action_scan ) { - ListPathsAsyncTask( - requireActivity(), - object : OnPathsListedCallback { - override fun onPathsListed(paths: Array) { - scanPaths(paths) - } - }) - .execute( - ListPathsAsyncTask.LoadingInfo( - file1, AUDIO_FILE_FILTER + lifecycleScope.launch { + listPaths(mFile, AUDIO_FILE_FILTER) { paths -> + scanPaths( + paths ) - ) + } + } } .setActionTextColor(accentColor(requireActivity())) .show() } } - }) - .execute( - ListSongsAsyncTask.LoadingInfo( - toList(mFile.parentFile), fileFilter, fileComparator - ) - ) + } + } } } @@ -345,19 +320,16 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), override fun onMultipleItemAction(item: MenuItem, files: ArrayList) { val itemId = item.itemId - ListSongsAsyncTask( - activity, - null, - object : OnSongsListedCallback { - override fun onSongsListed(songs: List, extra: Any?) { + + lifecycleScope.launch(Dispatchers.IO) { + listSongs(requireContext(), files, AUDIO_FILE_FILTER, fileComparator) { songs -> + if (songs.isNotEmpty()) { SongsMenuHelper.handleMenuClick( - requireActivity(), - songs, - itemId + requireActivity(), songs, itemId ) } - }) - .execute(ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, fileComparator)) + } + } } override fun onPrepareOptionsMenu(menu: Menu) { @@ -397,14 +369,9 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), R.id.action_scan -> { val crumb = activeCrumb if (crumb != null) { - ListPathsAsyncTask( - activity, - object : OnPathsListedCallback { - override fun onPathsListed(paths: Array) { - scanPaths(paths) - } - }) - .execute(ListPathsAsyncTask.LoadingInfo(crumb.file, AUDIO_FILE_FILTER)) + lifecycleScope.launch { + listPaths(crumb.file, AUDIO_FILE_FILTER) { paths -> scanPaths(paths) } + } } return true } @@ -557,69 +524,32 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), _binding = null } - class ListPathsAsyncTask(context: Context?, callback: OnPathsListedCallback) : - ListingFilesDialogAsyncTask>( - context - ) { - private val onPathsListedCallbackWeakReference: WeakReference = - WeakReference(callback) - - override fun doInBackground(vararg params: LoadingInfo): Array { - return try { - if (isCancelled || checkCallbackReference() == null) { - return arrayOf() + private suspend fun listPaths( + file: File, + fileFilter: FileFilter, + doOnPathListed: (paths: Array) -> Unit + ) { + val paths = try { + val paths: Array + if (file.isDirectory) { + val files = FileUtil.listFilesDeep(file, fileFilter) + paths = arrayOfNulls(files.size) + for (i in files.indices) { + val f = files[i] + paths[i] = FileUtil.safeGetCanonicalPath(f) } - val info = params[0] - val paths: Array - if (info.file.isDirectory) { - val files = FileUtil.listFilesDeep(info.file, info.fileFilter) - if (isCancelled || checkCallbackReference() == null) { - return arrayOf() - } - paths = arrayOfNulls(files.size) - for (i in files.indices) { - val f = files[i] - paths[i] = FileUtil.safeGetCanonicalPath(f) - if (isCancelled || checkCallbackReference() == null) { - return arrayOf() - } - } - } else { - paths = arrayOfNulls(1) - paths[0] = info.file.path - } - paths - } catch (e: Exception) { - e.printStackTrace() - cancel(false) - arrayOf() + } else { + paths = arrayOfNulls(1) + paths[0] = file.path } + paths + } catch (e: Exception) { + e.printStackTrace() + arrayOf() } - - override fun onPostExecute(paths: Array) { - super.onPostExecute(paths) - checkCallbackReference()?.onPathsListed(paths) + withContext(Dispatchers.Main) { + doOnPathListed(paths) } - - override fun onPreExecute() { - super.onPreExecute() - checkCallbackReference() - } - - private fun checkCallbackReference(): OnPathsListedCallback? { - val callback = onPathsListedCallbackWeakReference.get() - if (callback == null) { - cancel(false) - } - return callback - } - - interface OnPathsListedCallback { - fun onPathsListed(paths: Array) - } - - class LoadingInfo(val file: File, val fileFilter: FileFilter) - } private class AsyncFileLoader(foldersFragment: FoldersFragment) : @@ -647,87 +577,25 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), LinkedList() } } - } - private open class ListSongsAsyncTask( - context: Context?, - private val extra: Any?, - callback: OnSongsListedCallback - ) : ListingFilesDialogAsyncTask>(context) { - private val callbackWeakReference = WeakReference(callback) - private val contextWeakReference = WeakReference(context) - override fun doInBackground(vararg params: LoadingInfo): List { - return try { - val info = params[0] - val files = FileUtil.listFilesDeep(info.files, info.fileFilter) - if (isCancelled || checkContextReference() == null || checkCallbackReference() == null) { - return emptyList() - } - Collections.sort(files, info.fileComparator) - val context = checkContextReference() - if (isCancelled || context == null || checkCallbackReference() == null) { - emptyList() - } else FileUtil.matchFilesWithMediaStore(context, files) - } catch (e: Exception) { - e.printStackTrace() - cancel(false) - emptyList() - } + suspend fun listSongs( + context: Context, + files: List, + fileFilter: FileFilter, + fileComparator: Comparator, + doOnSongsListed: (songs: List) -> Unit + ) { + val songs = try { + val fileList = FileUtil.listFilesDeep(files, fileFilter) + Collections.sort(fileList, fileComparator) + FileUtil.matchFilesWithMediaStore(context, fileList) + } catch (e: Exception) { + e.printStackTrace() + emptyList() } - - override fun onPostExecute(songs: List) { - super.onPostExecute(songs) - checkCallbackReference()?.onSongsListed(songs, extra) - } - - override fun onPreExecute() { - super.onPreExecute() - checkCallbackReference() - checkContextReference() - } - - private fun checkCallbackReference(): OnSongsListedCallback? { - val callback = callbackWeakReference.get() - if (callback == null) { - cancel(false) - } - return callback - } - - private fun checkContextReference(): Context? { - val context = contextWeakReference.get() - if (context == null) { - cancel(false) - } - return context - } - - interface OnSongsListedCallback { - fun onSongsListed(songs: List, extra: Any?) - } - - class LoadingInfo( - val files: List, - val fileFilter: FileFilter, - val fileComparator: Comparator - ) - - } - - abstract class ListingFilesDialogAsyncTask internal constructor( - context: Context? - ) : - DialogAsyncTask(context) { - - override fun createDialog(context: Context): Dialog { - return MaterialAlertDialogBuilder(context) - .setTitle(R.string.listing_files) - .setCancelable(false) - .setView(R.layout.loading) - .setOnCancelListener { cancel(false) } - .setOnDismissListener { cancel(false) } - .create() + withContext(Dispatchers.Main) { + doOnSongsListed(songs) } } @@ -768,7 +636,11 @@ class FoldersFragment : AbsMainActivityFragment(R.layout.fragment_folder), (!file.isHidden && (file.isDirectory || FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) - || FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) + || FileUtil.fileIsMimeType( + file, + "application/opus", + MimeTypeMap.getSingleton() + ) || FileUtil.fileIsMimeType( file, "application/ogg",