diff --git a/.idea/dictionaries/shabinder.xml b/.idea/dictionaries/shabinder.xml
index 6457524e..739b47bf 100755
--- a/.idea/dictionaries/shabinder.xml
+++ b/.idea/dictionaries/shabinder.xml
@@ -6,6 +6,7 @@
emoji
ffmpeg
flyer
+ hqdefault
insta
instagram
jetbrains
diff --git a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YTDownloadHelper.kt b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YTDownloadHelper.kt
index 02e17f8f..523447d4 100755
--- a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YTDownloadHelper.kt
+++ b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YTDownloadHelper.kt
@@ -48,8 +48,8 @@ object YTDownloadHelper {
val downloadObject = DownloadObject(
trackDetails = it,
- ytVideoId = "https://i.ytimg.com/vi/${it.albumArt.absolutePath.substringAfterLast("/")
- .substringBeforeLast(".")}/maxresdefault.jpg",
+ ytVideoId = it.albumArt.absolutePath.substringAfterLast("/")
+ .substringBeforeLast("."),
outputFile = outputFile
)
diff --git a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt
index 696ad40d..670abecf 100755
--- a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt
+++ b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt
@@ -44,7 +44,7 @@ class DownloadRecordAdapter: ListAdapter(YouTubeTrackDiffCallback()) {
-
- private val adapterScope = CoroutineScope(Dispatchers.Default)
+class YoutubeTrackListAdapter(private val youtubeViewModel :YoutubeViewModel): ListAdapter(YouTubeTrackDiffCallback()) {
override fun onCreateViewHolder(
parent: ViewGroup,
@@ -48,24 +51,50 @@ class YoutubeTrackListAdapter: ListAdapter {
+ holder.binding.btnDownload.setImageResource(R.drawable.ic_tick)
+ holder.binding.btnDownload.clearAnimation()
+ }
+ DownloadStatus.Downloading -> {
+ holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
+ rotateAnim(holder.binding.btnDownload)
+ }
+ DownloadStatus.NotDownloaded -> {
+ holder.binding.btnDownload.setImageResource(R.drawable.ic_arrow)
+ holder.binding.btnDownload.clearAnimation()
+ holder.binding.btnDownload.setOnClickListener{
+ Toast.makeText(Provider.activity,"Processing!", Toast.LENGTH_SHORT).show()
+ holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
+ rotateAnim(it)
+ item.downloaded = DownloadStatus.Downloading
+ youtubeViewModel.uiScope.launch {
+ val itemList = mutableListOf()
+ itemList.add(item)
+ YTDownloadHelper.downloadYTTracks(
+ youtubeViewModel.folderType,
+ youtubeViewModel.subFolder,
+ itemList
+ )
+ }
+ notifyItemChanged(position)//start showing anim!
+ }
+ }
+ }
+
holder.binding.trackName.text = "${if(item.title.length > 17){"${item.title.subSequence(0,16)}..."}else{item.title}}"
holder.binding.artist.text = "${item.artists.get(0)}..."
holder.binding.duration.text = "${item.durationSec/60} minutes, ${item.durationSec%60} sec"
- holder.binding.btnDownload.setOnClickListener{
- adapterScope.launch {
-// YTDownloadHelper.downloadFile(null,"YT_Downloads",item,format)
- }
- }
}
}
class YouTubeTrackDiffCallback: DiffUtil.ItemCallback(){
diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt
index 9801997a..7f382b40 100755
--- a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt
+++ b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt
@@ -17,7 +17,12 @@
package com.shabinder.spotiflyer.ui.youtube
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -52,6 +57,8 @@ class YoutubeFragment : Fragment() {
private lateinit var adapter : YoutubeTrackListAdapter
private val sampleDomain1 = "youtube.com"
private val sampleDomain2 = "youtu.be"
+ private var intentFilter: IntentFilter? = null
+ private var updateUIReceiver: BroadcastReceiver? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -60,10 +67,11 @@ class YoutubeFragment : Fragment() {
binding = DataBindingUtil.inflate(inflater,R.layout.youtube_fragment,container,false)
youtubeViewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
- adapter = YoutubeTrackListAdapter()
+ adapter = YoutubeTrackListAdapter(youtubeViewModel)
binding.trackList.adapter = adapter
initializeLiveDataObservers()
+ initializeBroadcast()
val args = YoutubeFragmentArgs.fromBundle(requireArguments())
val link = args.link
@@ -89,6 +97,10 @@ class YoutubeFragment : Fragment() {
youtubeViewModel.getYTTrack(searchId,ytDownloader)
}else{showToast("Your Youtube Link is not of a Video!!")}
}
+
+ /*
+ * Download All Tracks
+ * */
binding.btnDownloadAll.setOnClickListener {
binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.visibility = View.VISIBLE
@@ -105,7 +117,7 @@ class YoutubeFragment : Fragment() {
sharedViewModel.uiScope.launch(Dispatchers.Default){
val urlList = arrayListOf()
youtubeViewModel.ytTrackList.value?.forEach { urlList.add("https://i.ytimg.com/vi/${it.albumArt.absolutePath.substringAfterLast("/")
- .substringBeforeLast(".")}/maxresdefault.jpg")}
+ .substringBeforeLast(".")}/hqdefault.jpg")}
//Appending Source
urlList.add("youtube")
loadAllImages(
@@ -122,7 +134,56 @@ class YoutubeFragment : Fragment() {
}
}
}
+ override fun onResume() {
+ super.onResume()
+ initializeBroadcast()
+ }
+ private fun initializeBroadcast() {
+ intentFilter = IntentFilter()
+ intentFilter?.addAction("track_download_completed")
+
+ updateUIReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ //UI update here
+ if (intent != null){
+ val trackDetails = intent.getParcelableExtra("track")
+ trackDetails?.let {
+ val position: Int = youtubeViewModel.ytTrackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
+ Log.i("Track","Download Completed Intent :$position")
+ if(position != -1) {
+ val track = youtubeViewModel.ytTrackList.value?.get(position)
+ track?.let{
+ it.downloaded = DownloadStatus.Downloaded
+ youtubeViewModel.ytTrackList.value?.set(position, it)
+ adapter.notifyItemChanged(position)
+ checkIfAllDownloaded()
+ }
+ }
+ }
+ }
+ }
+ }
+ requireActivity().registerReceiver(updateUIReceiver, intentFilter)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ requireActivity().unregisterReceiver(updateUIReceiver)
+ }
+
+ private fun checkIfAllDownloaded() {
+ if(!youtubeViewModel.ytTrackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
+ //All Tracks Downloaded
+ binding.btnDownloadAll.visibility = View.GONE
+ binding.downloadingFab.apply{
+ setImageResource(R.drawable.ic_tick)
+ visibility = View.VISIBLE
+ clearAnimation()
+ keepScreenOn = false
+ }
+ }
+ }
private fun initializeLiveDataObservers() {
/**
* CoverUrl Binding Observer!
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 f9a7b15c..dedfb6e0 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
@@ -32,6 +32,7 @@ import com.shabinder.spotiflyer.models.Source
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.utils.Provider
import com.shabinder.spotiflyer.utils.Provider.defaultDir
+import com.shabinder.spotiflyer.utils.Provider.showToast
import com.shabinder.spotiflyer.utils.finalOutputDir
import com.shabinder.spotiflyer.utils.removeIllegalChars
import kotlinx.coroutines.*
@@ -41,10 +42,11 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
/*
* YT Album Art Schema
- * Normal Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
+ * HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
+ * Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
- val ytTrackList = MutableLiveData>()
+ val ytTrackList = MutableLiveData>()
val format = MutableLiveData()
private val loading = "Loading"
var title = MutableLiveData().apply { value = "\"Loading!\"" }
@@ -56,81 +58,101 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
fun getYTPlaylist(searchId:String, ytDownloader:YoutubeDownloader){
- uiScope.launch(Dispatchers.IO) {
- Log.i("YT Playlist",searchId)
- val playlist = ytDownloader.getPlaylist(searchId)
- val playlistDetails = playlist.details()
- val name = playlistDetails.title()
- subFolder = removeIllegalChars(name).toString()
- val videos = playlist.videos()
- coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/maxresdefault.jpg")
- title.postValue(
- if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
- )
- ytTrackList.postValue(videos.map {
- TrackDetails(
- title = it.title(),
- artists = listOf(it.author().toString()),
- durationSec = it.lengthSeconds(),
- albumArt = File(
- Environment.getExternalStorageDirectory(),
- defaultDir +".Images/" + it.videoId() + ".jpeg"
- ),
- source = Source.YouTube,
- downloaded = if(File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists())
- DownloadStatus.Downloaded
- else DownloadStatus.NotDownloaded
+ try{
+ uiScope.launch(Dispatchers.IO) {
+ Log.i("YT Playlist",searchId)
+ val playlist = ytDownloader.getPlaylist(searchId)
+ val playlistDetails = playlist.details()
+ val name = playlistDetails.title()
+ subFolder = removeIllegalChars(name).toString()
+ val videos = playlist.videos()
+ coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg")
+ title.postValue(
+ if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
)
- })
+ ytTrackList.postValue(videos.map {
+ TrackDetails(
+ title = it.title(),
+ artists = listOf(it.author().toString()),
+ durationSec = it.lengthSeconds(),
+ albumArt = File(
+ Environment.getExternalStorageDirectory(),
+ defaultDir + ".Images/" + it.videoId() + ".jpeg"
+ ),
+ source = Source.YouTube,
+ downloaded = if (File(
+ finalOutputDir(
+ itemName = it.title(),
+ type = folderType,
+ subFolder = subFolder
+ )
+ ).exists()
+ )
+ DownloadStatus.Downloaded
+ else {
+ DownloadStatus.NotDownloaded
+ }
+ )
+ }.toMutableList())
withContext(Dispatchers.IO){
- databaseDAO.insert(DownloadRecord(
- type = "PlayList",
- name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
- link = "https://www.youtube.com/playlist?list=$searchId",
- coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/maxresdefault.jpg",
- totalFiles = videos.size,
- directory = finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder),
- downloaded = File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists()
- ))
+ databaseDAO.insert(DownloadRecord(
+ type = "PlayList",
+ name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
+ link = "https://www.youtube.com/playlist?list=$searchId",
+ coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg",
+ totalFiles = videos.size,
+ directory = finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder),
+ downloaded = File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists()
+ ))
+ }
}
+ }catch (e:com.github.kiulian.downloader.YoutubeException.BadPageException){
+ showToast("An Error Occurred While Processing!")
}
+
}
@SuppressLint("DefaultLocale")
fun getYTTrack(searchId:String, ytDownloader:YoutubeDownloader) {
- uiScope.launch(Dispatchers.IO) {
- Log.i("YT Video",searchId)
- val video = ytDownloader.getVideo(searchId)
- coverUrl.postValue("https://i.ytimg.com/vi/$searchId/maxresdefault.jpg")
- val detail = video?.details()
- val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
- Log.i("YT View Model",detail.toString())
- ytTrackList.postValue(listOf(TrackDetails(
- title = name,
- artists = listOf(detail?.author().toString()),
- durationSec = detail?.lengthSeconds()?:0,
- albumArt = File(
- Environment.getExternalStorageDirectory(),
- Provider.defaultDir +".Images/" + searchId + ".jpeg"
- ),
- source = Source.YouTube
- )))
- title.postValue(
- if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
- )
+ try{
+ uiScope.launch(Dispatchers.IO) {
+ Log.i("YT Video",searchId)
+ val video = ytDownloader.getVideo(searchId)
+ coverUrl.postValue("https://i.ytimg.com/vi/$searchId/hqdefault.jpg")
+ val detail = video?.details()
+ val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
+ Log.i("YT View Model",detail.toString())
+ ytTrackList.postValue(
+ listOf(TrackDetails(
+ title = name,
+ artists = listOf(detail?.author().toString()),
+ durationSec = detail?.lengthSeconds()?:0,
+ albumArt = File(
+ Environment.getExternalStorageDirectory(),
+ Provider.defaultDir +".Images/" + searchId + ".jpeg"
+ ),
+ source = Source.YouTube
+ )).toMutableList()
+ )
+ title.postValue(
+ if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
+ )
- withContext(Dispatchers.IO){
- databaseDAO.insert(DownloadRecord(
- type = "Track",
- name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
- link = "https://www.youtube.com/watch?v=$searchId",
- coverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg",
- totalFiles = 1,
- downloaded = false,
- directory = finalOutputDir(type = "YT_Downloads")
- ))
+ withContext(Dispatchers.IO){
+ databaseDAO.insert(DownloadRecord(
+ type = "Track",
+ name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
+ link = "https://www.youtube.com/watch?v=$searchId",
+ coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
+ totalFiles = 1,
+ downloaded = false,
+ directory = finalOutputDir(type = "YT_Downloads")
+ ))
+ }
}
+ } catch (e:com.github.kiulian.downloader.YoutubeException){
+ showToast("An Error Occurred While Processing!")
}
}
}
diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt
index 123a2fd2..84863e5f 100755
--- a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt
+++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt
@@ -19,6 +19,7 @@ package com.shabinder.spotiflyer.utils
import android.content.Context
import android.os.Environment
+import android.widget.Toast
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.App
import com.shabinder.spotiflyer.MainActivity
@@ -117,4 +118,7 @@ object Provider {
return retrofit.create(YoutubeMusicApi::class.java)
}
+ fun showToast(string: String,long:Boolean=false){
+ Toast.makeText(activity,string,if(long)Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show()
+ }
}
\ No newline at end of file
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 8435cb1e..77c84308 100755
--- a/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt
+++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt
@@ -28,7 +28,6 @@ import android.view.animation.RotateAnimation
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
-import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
@@ -77,8 +76,7 @@ fun rotateAnim(view: View){
view.animation = rotate
}
-@BindingAdapter("imageUrl")
-fun bindImage(imgView: ImageView, imgUrl: String?,source: Source) {
+fun bindImage(imgView: ImageView, imgUrl: String?,source: Source?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Glide
@@ -122,6 +120,10 @@ fun bindImage(imgView: ImageView, imgUrl: String?,source: Source) {
defaultDir+".Images/" + imgUrl.substringBeforeLast('/',imgUrl).substringAfterLast('/',imgUrl) + ".jpeg"
)
}
+ else -> File(
+ Environment.getExternalStorageDirectory(),
+ defaultDir+".Images/" + imgUrl.substringAfterLast('/',imgUrl) + ".jpeg"
+ )
}
// the File to save , append increasing numeric counter to prevent files from getting overwritten.
resource?.copyTo(file)