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
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<Song>, 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<String?>) {
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<Song>, 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<String?>) {
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<Song>, 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<String?>) {
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<File>) {
val itemId = item.itemId
ListSongsAsyncTask(
activity,
null,
object : OnSongsListedCallback {
override fun onSongsListed(songs: List<Song>, 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<String?>) {
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<ListPathsAsyncTask.LoadingInfo, String?, Array<String?>>(
context
) {
private val onPathsListedCallbackWeakReference: WeakReference<OnPathsListedCallback> =
WeakReference(callback)
override fun doInBackground(vararg params: LoadingInfo): Array<String?> {
return try {
if (isCancelled || checkCallbackReference() == null) {
return arrayOf()
private suspend fun listPaths(
file: File,
fileFilter: FileFilter,
doOnPathListed: (paths: Array<String?>) -> Unit
) {
val paths = try {
val paths: Array<String?>
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<String?>
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<String?>) {
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<String?>)
}
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<ListSongsAsyncTask.LoadingInfo, Void, List<Song>>(context) {
private val callbackWeakReference = WeakReference(callback)
private val contextWeakReference = WeakReference(context)
override fun doInBackground(vararg params: LoadingInfo): List<Song> {
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<File>,
fileFilter: FileFilter,
fileComparator: Comparator<File>,
doOnSongsListed: (songs: List<Song>) -> 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<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()
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",