From 2e40db545221b464d5939017da467296af2af2b2 Mon Sep 17 00:00:00 2001 From: Shabinder Date: Tue, 1 Dec 2020 12:28:36 +0530 Subject: [PATCH] Progress Showing Improvements. --- .../base/tracklistbase/TrackListFragment.kt | 68 +++++-- .../spotiflyer/ui/gaana/GaanaViewModel.kt | 184 ++++++++++++------ .../spotiflyer/ui/spotify/SpotifyViewModel.kt | 2 + .../spotiflyer/ui/youtube/YoutubeViewModel.kt | 7 +- .../com/shabinder/spotiflyer/utils/Utils.kt | 8 +- .../spotiflyer/worker/ForegroundService.kt | 36 +++- 6 files changed, 215 insertions(+), 90 deletions(-) diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/base/tracklistbase/TrackListFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/base/tracklistbase/TrackListFragment.kt index dfa75523..61904b98 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/base/tracklistbase/TrackListFragment.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/base/tracklistbase/TrackListFragment.kt @@ -35,10 +35,8 @@ import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.recyclerView.TrackListAdapter import com.shabinder.spotiflyer.ui.base.BaseFragment +import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.Provider.mainActivity -import com.shabinder.spotiflyer.utils.bindImage -import com.shabinder.spotiflyer.utils.isOnline -import com.shabinder.spotiflyer.utils.showNoConnectionAlert import com.tonyodev.fetch2.Status abstract class TrackListFragment : BaseFragment() { @@ -48,6 +46,7 @@ abstract class TrackListFragment : BaseF protected abstract var source: Source private var intentFilter: IntentFilter? = null private var updateUIReceiver: BroadcastReceiver? = null + private var queryReceiver: BroadcastReceiver? = null protected abstract val args:NavArgs override fun onCreate(savedInstanceState: Bundle?) { @@ -87,7 +86,7 @@ abstract class TrackListFragment : BaseF if (!it.isNullOrEmpty()){ Log.i("GaanaFragment","TrackList Updated") adapter.submitList(it, source) - checkIfAllDownloaded() + updateTracksStatus() } }) @@ -101,14 +100,14 @@ abstract class TrackListFragment : BaseF } private fun initializeBroadcast() { - intentFilter = IntentFilter() - intentFilter?.addAction(Status.QUEUED.name) - intentFilter?.addAction(Status.FAILED.name) - intentFilter?.addAction(Status.DOWNLOADING.name) - intentFilter?.addAction("Progress") - intentFilter?.addAction("Converting") - intentFilter?.addAction("track_download_completed") - + intentFilter = IntentFilter().apply { + addAction(Status.QUEUED.name) + addAction(Status.FAILED.name) + addAction(Status.DOWNLOADING.name) + addAction("Progress") + addAction("Converting") + addAction("track_download_completed") + } updateUIReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { //UI update here @@ -145,7 +144,28 @@ abstract class TrackListFragment : BaseF } viewModel.trackList.value?.set(position, it) adapter.notifyItemChanged(position) - checkIfAllDownloaded() + updateTracksStatus() + } + } + } + } + } + } + val queryFilter = IntentFilter().apply { addAction("query_result") } + queryReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + //UI update here + if (intent != null){ + val trackList = intent.getParcelableArrayListExtra("tracks") ?: listOf() + Log.i("Service Response", "${trackList.size} Tracks Active") + for (trackDetails in trackList) { + trackDetails?.let { it -> + val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1 + Log.i("BroadCast Received","$position, ${it.downloaded} , ${trackDetails.title}") + if(position != -1) { + viewModel.trackList.value?.set(position,it) + adapter.notifyItemChanged(position) + updateTracksStatus() } } } @@ -153,6 +173,7 @@ abstract class TrackListFragment : BaseF } } requireActivity().registerReceiver(updateUIReceiver, intentFilter) + requireActivity().registerReceiver(queryReceiver, queryFilter) } override fun onResume() { @@ -163,10 +184,25 @@ abstract class TrackListFragment : BaseF override fun onPause() { super.onPause() requireActivity().unregisterReceiver(updateUIReceiver) + requireActivity().unregisterReceiver(queryReceiver) } - private fun checkIfAllDownloaded() { - if(!viewModel.trackList.value!!.any { it.downloaded == DownloadStatus.NotDownloaded || it.downloaded == DownloadStatus.Queued || it.downloaded == DownloadStatus.Converting }){ - //All Tracks Downloaded + + private fun updateTracksStatus() { + var allDownloaded = true + var allProcessing = true + for (track in viewModel.trackList.value!!){ + if(track.downloaded != DownloadStatus.Downloaded)allDownloaded = false + if(track.downloaded == DownloadStatus.NotDownloaded)allProcessing = false + } + if(allProcessing){ + binding.btnDownloadAll.visibility = View.GONE + binding.downloadingFab.apply{ + setImageResource(R.drawable.ic_refresh) + visible() + rotate() + } + } + if(allDownloaded){ binding.btnDownloadAll.visibility = View.GONE binding.downloadingFab.apply{ setImageResource(R.drawable.ic_tick) diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/gaana/GaanaViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/gaana/GaanaViewModel.kt index 5845260c..c410c55f 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/gaana/GaanaViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/gaana/GaanaViewModel.kt @@ -29,6 +29,7 @@ import com.shabinder.spotiflyer.networking.GaanaInterface import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListViewModel import com.shabinder.spotiflyer.utils.Provider import com.shabinder.spotiflyer.utils.finalOutputDir +import com.shabinder.spotiflyer.utils.queryActiveTracks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -45,67 +46,98 @@ class GaanaViewModel @ViewModelInject constructor( private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg" fun gaanaSearch(type:String,link:String){ - when(type){ - "song" -> { - viewModelScope.launch { - gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also { + viewModelScope.launch { + when (type) { + "song" -> { + gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also { folderType = "Tracks" - if(File(finalOutputDir(it.track_title,folderType,subFolder)).exists()){//Download Already Present!! + if (File( + finalOutputDir( + it.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! it.downloaded = DownloadStatus.Downloaded } trackList.value = listOf(it).toTrackDetailsList() title.value = it.track_title coverUrl.value = it.artworkLink - withContext(Dispatchers.IO){ + withContext(Dispatchers.IO) { databaseDAO.insert( DownloadRecord( - type = "Track", - name = title.value!!, - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value!!, - totalFiles = 1, - downloaded = it.downloaded == DownloadStatus.Downloaded, - directory = finalOutputDir(it.track_title,folderType,subFolder) - ) + type = "Track", + name = title.value!!, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl.value!!, + totalFiles = 1, + downloaded = it.downloaded == DownloadStatus.Downloaded, + directory = finalOutputDir( + it.track_title, + folderType, + subFolder + ) + ) ) } } } - } - "album" -> { - viewModelScope.launch { + "album" -> { gaanaInterface.getGaanaAlbum(seokey = link).value?.also { folderType = "Albums" subFolder = link it.tracks.forEach { track -> - if(File(finalOutputDir(track.track_title,folderType,subFolder)).exists()){//Download Already Present!! + if (File( + finalOutputDir( + track.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! track.downloaded = DownloadStatus.Downloaded } } trackList.value = it.tracks.toTrackDetailsList() title.value = link coverUrl.value = it.custom_artworks.size_480p - withContext(Dispatchers.IO){ - databaseDAO.insert(DownloadRecord( - type = "Album", - name = title.value!!, - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = trackList.value?.size ?: 0, - downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == trackList.value?.size, - directory = finalOutputDir(type = folderType,subFolder = subFolder) - )) + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Album", + name = title.value!!, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl.value.toString(), + totalFiles = trackList.value?.size ?: 0, + downloaded = File( + finalOutputDir( + type = folderType, + subFolder = subFolder + ) + ).listFiles()?.size == trackList.value?.size, + directory = finalOutputDir( + type = folderType, + subFolder = subFolder + ) + ) + ) } } } - } - "playlist" -> { - viewModelScope.launch { + "playlist" -> { gaanaInterface.getGaanaPlaylist(seokey = link).value?.also { folderType = "Playlists" subFolder = link - it.tracks.forEach {track -> - if(File(finalOutputDir(track.track_title,folderType,subFolder)).exists()){//Download Already Present!! + it.tracks.forEach { track -> + if (File( + finalOutputDir( + track.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! track.downloaded = DownloadStatus.Downloaded } } @@ -113,49 +145,77 @@ class GaanaViewModel @ViewModelInject constructor( title.value = link //coverUrl.value = "TODO" coverUrl.value = gaanaPlaceholderImageUrl - withContext(Dispatchers.IO){ - databaseDAO.insert(DownloadRecord( - type = "Playlist", - name = title.value.toString(), - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = it.tracks.size, - downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == trackList.value?.size, - directory = finalOutputDir(type = folderType,subFolder = subFolder) - )) + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Playlist", + name = title.value.toString(), + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl.value.toString(), + totalFiles = it.tracks.size, + downloaded = File( + finalOutputDir( + type = folderType, + subFolder = subFolder + ) + ).listFiles()?.size == trackList.value?.size, + directory = finalOutputDir( + type = folderType, + subFolder = subFolder + ) + ) + ) } } } - } - "artist" -> { - viewModelScope.launch { + "artist" -> { folderType = "Artist" subFolder = link - val artistDetails = gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull()?.also { - title.value = it.name - coverUrl.value = it.artworkLink - } + val artistDetails = + gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull() + ?.also { + title.value = it.name + coverUrl.value = it.artworkLink + } gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also { - it.tracks.forEach {track -> - if(File(finalOutputDir(track.track_title,folderType,subFolder)).exists()){//Download Already Present!! + it.tracks.forEach { track -> + if (File( + finalOutputDir( + track.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! track.downloaded = DownloadStatus.Downloaded } } trackList.value = it.tracks.toTrackDetailsList() - withContext(Dispatchers.IO){ - databaseDAO.insert(DownloadRecord( - type = "Artist", - name = artistDetails?.name ?: link, - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = trackList.value?.size ?: 0, - downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == trackList.value?.size, - directory = finalOutputDir(type = folderType,subFolder = subFolder) - )) + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Artist", + name = artistDetails?.name ?: link, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl.value.toString(), + totalFiles = trackList.value?.size ?: 0, + downloaded = File( + finalOutputDir( + type = folderType, + subFolder = subFolder + ) + ).listFiles()?.size == trackList.value?.size, + directory = finalOutputDir( + type = folderType, + subFolder = subFolder + ) + ) + ) } } } } + queryActiveTracks() } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt index 39ab9b5d..fb8006e2 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt @@ -33,6 +33,7 @@ import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListViewModel import com.shabinder.spotiflyer.utils.Provider.imageDir import com.shabinder.spotiflyer.utils.finalOutputDir +import com.shabinder.spotiflyer.utils.queryActiveTracks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -199,6 +200,7 @@ class SpotifyViewModel @ViewModelInject constructor( "show" -> {//TODO } } + queryActiveTracks() } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt index fa354495..4a72b0e7 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt @@ -28,11 +28,8 @@ import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListViewModel +import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.Provider.imageDir -import com.shabinder.spotiflyer.utils.finalOutputDir -import com.shabinder.spotiflyer.utils.isOnline -import com.shabinder.spotiflyer.utils.removeIllegalChars -import com.shabinder.spotiflyer.utils.showMessage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -100,6 +97,7 @@ class YoutubeViewModel @ViewModelInject constructor( downloaded = File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists() )) } + queryActiveTracks() } }catch (e:com.github.kiulian.downloader.YoutubeException.BadPageException){ showMessage("An Error Occurred While Processing!") @@ -145,6 +143,7 @@ class YoutubeViewModel @ViewModelInject constructor( directory = finalOutputDir(type = "YT_Downloads") )) } + queryActiveTracks() } } catch (e:com.github.kiulian.downloader.YoutubeException){ showMessage("An Error Occurred While Processing!") diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt index b3345f74..d9c1829f 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt @@ -52,11 +52,17 @@ fun loadAllImages(context: Context?, images:List? = null,source:Source) context?.let { ContextCompat.startForegroundService(it, serviceIntent) } } -fun startService(context:Context?,objects:ArrayList? = null ) { +fun startService(context:Context? = mainActivity,objects:ArrayList? = null ) { val serviceIntent = Intent(context, ForegroundService::class.java) objects?.let { serviceIntent.putParcelableArrayListExtra("object",it) } context?.let { ContextCompat.startForegroundService(it, serviceIntent) } } +fun queryActiveTracks(context:Context? = mainActivity) { + val serviceIntent = Intent(context, ForegroundService::class.java).apply { + action = "query" + } + context?.let { ContextCompat.startForegroundService(it, serviceIntent) } +} fun finalOutputDir(itemName:String? = null,type:String, subFolder:String?=null,extension:String? = ".mp3"): String{ return defaultDir + removeIllegalChars(type) + File.separator + diff --git a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt index 5e7d0ce2..828f51ac 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt @@ -47,6 +47,7 @@ import com.mpatric.mp3agic.ID3v24Tag import com.mpatric.mp3agic.Mp3File import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.models.DownloadObject +import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.utils.Provider @@ -73,11 +74,12 @@ class ForegroundService : Service(){ private var serviceJob = Job() private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) private val requestMap = mutableMapOf() + private val allTracksDetails = mutableListOf() private var defaultDir = Provider.defaultDir private var wakeLock: PowerManager.WakeLock? = null private var isServiceStarted = false - var notificationLine = 0 - var messageList = mutableListOf("", "", "", "") + private var notificationLine = 0 + private var messageList = mutableListOf("", "", "", "") private var cancelIntent:PendingIntent? = null override fun onBind(intent: Intent): IBinder? = null @@ -103,6 +105,14 @@ class ForegroundService : Service(){ if(intent.action == "kill") killService() + if(intent.action == "query"){ + val response = Intent().apply { + action = "query_result" + putParcelableArrayListExtra("tracks", allTracksDetails as ArrayList ) + } + sendBroadcast(response) + } + val downloadObjects: ArrayList? = (intent.getParcelableArrayListExtra("object") ?: intent.extras?.getParcelableArrayList( "object" )) @@ -119,6 +129,9 @@ class ForegroundService : Service(){ downloadObjects?.let { total += downloadObjects.size updateNotification() + it.forEach { it1 -> + allTracksDetails.add(it1.trackDetails.apply { downloaded = DownloadStatus.Queued }) + } downloadAllTracks(downloadObjects) } @@ -207,7 +220,6 @@ class ForegroundService : Service(){ download: Download, waitingOnNetwork: Boolean ) { - //Notify Download Completed val intent = Intent() .setAction(Status.QUEUED.name) .putExtra("track", requestMap[download.request]) @@ -247,10 +259,14 @@ class ForegroundService : Service(){ } } Log.i(tag, "${track?.title} Download Started") + track?.let{ + allTracksDetails[allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(it.title)] = + it.apply { downloaded = DownloadStatus.Downloading } + } updateNotification() val intent = Intent() .setAction(Status.DOWNLOADING.name) - .putExtra("track", requestMap[download.request]) + .putExtra("track", track) this@ForegroundService.sendBroadcast(intent) } @@ -277,7 +293,11 @@ class ForegroundService : Service(){ serviceScope.launch { try{ - track?.let { convertToMp3(download.file, it) } + track?.let { + convertToMp3(download.file, it) + allTracksDetails[allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(it.title)] = + it.apply { downloaded = DownloadStatus.Converting } + } Log.i(tag, "${track?.title} Download Completed") }catch ( e: KotlinNullPointerException @@ -360,6 +380,8 @@ class ForegroundService : Service(){ val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) //Checking if the received broadcast is for our enqueued download by matching download id if (downloadID == id) { + allTracksDetails[allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(track.title)] = + track.apply { downloaded = DownloadStatus.Converting } convertToMp3(outputDir, track) converted++ //Unregister this broadcast Receiver @@ -419,7 +441,7 @@ class ForegroundService : Service(){ newFile.renameTo(file) converted++ updateNotification() - + allTracksDetails.removeAt(allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(track.title)) //Notify Download Completed val intent = Intent() .setAction("track_download_completed") @@ -689,4 +711,4 @@ class ForegroundService : Service(){ //Starting fresh fetch.removeAll() } -} \ No newline at end of file +}