From 32a7240c2094c8bf10ff272b1a5300830a1ebcd6 Mon Sep 17 00:00:00 2001 From: kostas214pro Date: Wed, 12 Apr 2023 23:38:03 +0300 Subject: [PATCH] Added a recycler Implemented a recycler view to display information like song name artist name and song cover and also a progress Bar to display visually download progress. Know issue: If you leave the app and reenter the app the application will crash working on a fix but it will probably be a while --- .idea/deploymentTargetDropDown.xml | 12 +-- .idea/vcs.xml | 1 + app/build.gradle | 6 +- .../example/spotifydownloader/Application.kt | 8 +- .../example/spotifydownloader/MainActivity.kt | 6 ++ .../spotifydownloader/downloadFragment.kt | 95 ++++++++++++++++--- .../model/recyclerViewAdaptor.kt | 56 +++++++++++ .../spotifydownloader/model/songItemData.kt | 8 ++ app/src/main/python/main.py | 17 +++- app/src/main/res/layout/activity_main.xml | 8 +- app/src/main/res/layout/fragment_download.xml | 23 ++--- app/src/main/res/layout/song_item.xml | 50 ++++++++++ app/src/main/res/menu/bottom_nav_menu.xml | 2 +- 13 files changed, 251 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/example/spotifydownloader/model/recyclerViewAdaptor.kt create mode 100644 app/src/main/java/com/example/spotifydownloader/model/songItemData.kt create mode 100644 app/src/main/res/layout/song_item.xml diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 12b9340..5380a26 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,17 +1,17 @@ - + - + - - + + - - + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..a854d63 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7d86d07..d5bf187 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -87,8 +87,8 @@ dependencies { //YoutubeDl - implementation 'com.github.yausername.youtubedl-android:library:-SNAPSHOT' - implementation 'com.github.yausername.youtubedl-android:ffmpeg:-SNAPSHOT' // Optional + implementation "com.github.yausername.youtubedl-android:library:3a0252d88b4ae573068c63c3d08fc52c66102d55" + implementation "com.github.yausername.youtubedl-android:ffmpeg:3a0252d88b4ae573068c63c3d08fc52c66102d55" //Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' @@ -105,6 +105,8 @@ dependencies { implementation "androidx.navigation:navigation-ui-ktx:$nav_version" implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" + implementation "io.coil-kt:coil:2.3.0" + } \ No newline at end of file diff --git a/app/src/main/java/com/example/spotifydownloader/Application.kt b/app/src/main/java/com/example/spotifydownloader/Application.kt index 8070ac6..fd70613 100644 --- a/app/src/main/java/com/example/spotifydownloader/Application.kt +++ b/app/src/main/java/com/example/spotifydownloader/Application.kt @@ -27,7 +27,13 @@ class SpotifyDownloaderApplication : Application(){ YoutubeDL.getInstance().updateYoutubeDL(applicationContext) } } catch (e: Exception) { - Toast.makeText(applicationContext, "Download Library Initialization Failed", Toast.LENGTH_LONG).show() + withContext(Dispatchers.Main) { + Toast.makeText( + applicationContext, + "Download Library Initialization Failed", + Toast.LENGTH_LONG + ).show() + } } if (!Python.isStarted()) { Python.start(AndroidPlatform(this@SpotifyDownloaderApplication)) diff --git a/app/src/main/java/com/example/spotifydownloader/MainActivity.kt b/app/src/main/java/com/example/spotifydownloader/MainActivity.kt index ba22768..40187cd 100644 --- a/app/src/main/java/com/example/spotifydownloader/MainActivity.kt +++ b/app/src/main/java/com/example/spotifydownloader/MainActivity.kt @@ -93,6 +93,12 @@ class MainActivity : AppCompatActivity() { } + + + + + + override fun onOptionsItemSelected(item: MenuItem): Boolean { if (toggle.onOptionsItemSelected(item)) { return true diff --git a/app/src/main/java/com/example/spotifydownloader/downloadFragment.kt b/app/src/main/java/com/example/spotifydownloader/downloadFragment.kt index 11993b5..18fd342 100644 --- a/app/src/main/java/com/example/spotifydownloader/downloadFragment.kt +++ b/app/src/main/java/com/example/spotifydownloader/downloadFragment.kt @@ -17,17 +17,23 @@ import androidx.activity.OnBackPressedCallback import androidx.core.view.forEach import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.Navigation import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.LinearLayoutManager import com.chaquo.python.PyObject import com.chaquo.python.Python import com.chaquo.python.android.AndroidPlatform import com.example.spotifydownloader.databinding.FragmentDownloadBinding +import com.example.spotifydownloader.model.recyclerViewAdaptor +import com.example.spotifydownloader.model.songItemData import com.google.android.material.bottomnavigation.BottomNavigationView import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLException import com.yausername.youtubedl_android.YoutubeDLRequest +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.apache.commons.io.IOUtils import java.io.File import java.util.concurrent.Executors @@ -41,7 +47,11 @@ class downloadFragment : Fragment(R.layout.fragment_download) { private val args by navArgs() private lateinit var module:PyObject private var stop = false + private var songItems = mutableListOf() + private var completed = 0 + private var index = 0 + private val adapter = recyclerViewAdaptor(songItems) override fun onDestroy() { super.onDestroy() @@ -51,13 +61,17 @@ class downloadFragment : Fragment(R.layout.fragment_download) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + Log.d(tag,completed.toString()) + binding = FragmentDownloadBinding.bind(view) if (! Python.isStarted()) { Python.start( AndroidPlatform(context as Activity)) } + binding.recyclerView.adapter = adapter + binding.recyclerView.layoutManager = LinearLayoutManager(context) + - binding = FragmentDownloadBinding.bind(view) navController =Navigation.findNavController(view) val py = Python.getInstance() module = py.getModule("main") @@ -73,10 +87,15 @@ class downloadFragment : Fragment(R.layout.fragment_download) { } - binding.cancelBtn.setOnClickListener { + + + + + + binding.fabButton.setOnClickListener { stop = true Toast.makeText((context as Activity),"Stopping...",Toast.LENGTH_SHORT).show() - binding.cancelBtn.isEnabled = false + binding.fabButton.isEnabled = false } @@ -93,9 +112,6 @@ class downloadFragment : Fragment(R.layout.fragment_download) { - - binding.progressBar.progress = 0 - if (isDeviceOnline(context as Activity)){ val concurrentThreads = args.Data.concurrentDownloads @@ -103,7 +119,9 @@ class downloadFragment : Fragment(R.layout.fragment_download) { val songNamesSize = args.Data.songNames.size - binding.progressBar.max = songNamesSize + + + for (i in args.Data.songNames as MutableList) { @@ -119,8 +137,9 @@ class downloadFragment : Fragment(R.layout.fragment_download) { when (download(songName, args.Data.folderURI)) { 0 -> { + Log.d(tag,"$completed") - if (songNamesSize == binding.progressBar.progress) { + if (songNamesSize == completed) { runOnUiThread { Toast.makeText( context as Activity, @@ -160,7 +179,8 @@ class downloadFragment : Fragment(R.layout.fragment_download) { } } 2 -> { - if (songNamesSize == binding.progressBar.progress) { + Log.d(tag,"Completed $completed") + if (songNamesSize == completed) { runOnUiThread { Toast.makeText( context as Activity, @@ -264,7 +284,22 @@ class downloadFragment : Fragment(R.layout.fragment_download) { val filename = videoInfo[0].toString() val ytLInk = videoInfo[1].toString() - val code = videoInfo[2].toInt() + val imgUrl = videoInfo[2].toString() + val code = videoInfo[3].toInt() + val artistName = videoInfo[4].toString() + val trackName = videoInfo[5].toString() + + val songData = songItemData(imgUrl,trackName,artistName,0) + songItems.add(songData) + val position = index + runOnUiThread { + adapter.notifyItemInserted(index) + binding.recyclerView.scrollToPosition(position) + } + index += 1 + + + if (code == 0) { try { @@ -283,11 +318,38 @@ class downloadFragment : Fragment(R.layout.fragment_download) { val documentFIle: DocumentFile = DocumentFile.fromTreeUri((context as Activity), data!!)!! val dc: DocumentFile? = documentFIle.findFile(filename) + + + + + + + + + if (isDeviceOnline(context as Activity) && dc?.exists() == null) { try { + + + + + + YoutubeDL.getInstance().execute( request - ) { _: Float, _: Long, _: String? -> } + ) { progress: Float, _: Long, _: String? -> + //Log.d(tag,"Progress is $progress and the song is $songName") + if (progress >0){ + songItems[position].progress = progress.toInt() + runOnUiThread { + adapter.notifyItemChanged(position) + } + + } + + + + } } catch (e: YoutubeDLException) { Log.e(tag, e.message!!) @@ -332,11 +394,18 @@ class downloadFragment : Fragment(R.layout.fragment_download) { } else { Log.d(tag, "Already exists skipping") tmpFile.deleteRecursively() - binding.progressBar.incrementProgressBy(1) + songItems[position].progress = 100 + runOnUiThread { + adapter.notifyItemChanged(position) + } + //binding.progressBar.incrementProgressBy(1) + completed += 1 return 2 } Log.d(tag, "No errors") - binding.progressBar.incrementProgressBy(1) + //binding.progressBar.incrementProgressBy(1) + completed += 1 + tmpFile.deleteRecursively() return 0 } catch (e: java.lang.NullPointerException) { diff --git a/app/src/main/java/com/example/spotifydownloader/model/recyclerViewAdaptor.kt b/app/src/main/java/com/example/spotifydownloader/model/recyclerViewAdaptor.kt new file mode 100644 index 0000000..9d8d3bc --- /dev/null +++ b/app/src/main/java/com/example/spotifydownloader/model/recyclerViewAdaptor.kt @@ -0,0 +1,56 @@ +package com.example.spotifydownloader.model + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import coil.load +import com.example.spotifydownloader.R + +class recyclerViewAdaptor (private val songData: List) + :RecyclerView.Adapter() { + + + + + class ViewHolder(ItemView: View):RecyclerView.ViewHolder(ItemView){ + val songCoverImage: ImageView = itemView.findViewById(R.id.songCoverImage) + val songTitle : TextView = itemView.findViewById(R.id.songTitle) + val artistName : TextView = itemView.findViewById(R.id.artistName) + val progressBar : ProgressBar = itemView.findViewById(R.id.progressBar) + + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.song_item,parent,false) + return ViewHolder(view) + } + + override fun getItemCount(): Int { + return songData.size + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val recyclerViewAdaptor = songData[position] + + holder.songCoverImage.load(recyclerViewAdaptor.imageUrl) + + + if (recyclerViewAdaptor.songName.length>19) { + holder.songTitle.text = recyclerViewAdaptor.songName.take(17).plus("...") + } + else{ + holder.songTitle.text = recyclerViewAdaptor.songName + } + holder.artistName.text = recyclerViewAdaptor.artistName + holder.progressBar.progress = recyclerViewAdaptor.progress + + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/spotifydownloader/model/songItemData.kt b/app/src/main/java/com/example/spotifydownloader/model/songItemData.kt new file mode 100644 index 0000000..ecabb36 --- /dev/null +++ b/app/src/main/java/com/example/spotifydownloader/model/songItemData.kt @@ -0,0 +1,8 @@ +package com.example.spotifydownloader.model + +import android.graphics.Bitmap + +data class songItemData(val imageUrl : String, + val songName: String, + val artistName:String, + var progress:Int) diff --git a/app/src/main/python/main.py b/app/src/main/python/main.py index a9184b4..8cc1437 100644 --- a/app/src/main/python/main.py +++ b/app/src/main/python/main.py @@ -101,23 +101,34 @@ def songSearchSpotifyAlbum(albumLink): def getDownloadPath(songs): fileName = "" songUrl="" + imgUrl="" + songName ="" + artistName="" + albumUrl="" + songName="" try: + tracks = sp.search(songs) + albumUrl = tracks['tracks']['items'][0]['album']['images'][1]['url'] songSearch = VideosSearch(songs, limit=1).result() + artistName = tracks['tracks']['items'][0]['artists'][0]['name'] + songName = tracks['tracks']['items'][0]['name'] + + try: songId = songSearch['result'][0]['id'] except(IndexError): - return fileName,songUrl,2 + return fileName,songUrl,albumUrl,2,artistName,songName songUrl= "https://youtu.be/"+songId songTitle = songSearch['result'][0]['title'] translation_table = str.maketrans('', '', string.punctuation) safeString = songTitle.translate(translation_table) fileName = safeString.replace(" ", "") + ".mp3" except(httpx.ConnectError, ChunkedEncodingError, ReadError): - return fileName,songUrl,1 - return fileName,songUrl,0 + return fileName,songUrl,albumUrl,1,songName,artistName,songName + return fileName,songUrl,albumUrl,0,artistName,songName def insertMetaData(songs,fileLocation): diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6f4f1cc..e703154 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,15 +14,15 @@ diff --git a/app/src/main/res/layout/fragment_download.xml b/app/src/main/res/layout/fragment_download.xml index e11805e..beb2006 100644 --- a/app/src/main/res/layout/fragment_download.xml +++ b/app/src/main/res/layout/fragment_download.xml @@ -7,22 +7,23 @@ tools:context=".downloadFragment"> - -