Replaced AsyncTasks of Folders tab with coroutines

This commit is contained in:
Prathamesh More 2022-04-18 19:36:11 +05:30
parent 0604307b88
commit c570be349d

View file

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