Made All Long Running Calls Asynchronous(Coroutines deployed!)

This commit is contained in:
shabinder 2020-07-22 11:34:08 +05:30
parent d4823f0e10
commit 735f7c270e
15 changed files with 407 additions and 293 deletions

View File

@ -1,6 +1,7 @@
<component name="ProjectDictionaryState"> <component name="ProjectDictionaryState">
<dictionary name="shabinder"> <dictionary name="shabinder">
<words> <words>
<w>moshi</w>
<w>musicforeveryone</w> <w>musicforeveryone</w>
<w>musicplaceholder</w> <w>musicplaceholder</w>
<w>shabinder</w> <w>shabinder</w>

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectPlainTextFileTypeManager">
<file url="file://$PROJECT_DIR$/app/src/main/java/com/shabinder/musicForEveryone/downloadHelper/DownloadHelperLibrary.kt" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>

View File

@ -68,9 +68,14 @@ dependencies {
implementation 'com.google.apis:google-api-services-youtube:v3-rev180-1.22.0' implementation 'com.google.apis:google-api-services-youtube:v3-rev180-1.22.0'
implementation 'com.google.oauth-client:google-oauth-client:1.22.0' implementation 'com.google.oauth-client:google-oauth-client:1.22.0'
implementation 'com.spotify.android:auth:1.1.0' implementation 'com.spotify.android:auth:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:okhttp:4.8.0' implementation 'com.squareup.okhttp3:okhttp:4.8.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.moshi:moshi:1.9.3"
implementation "com.squareup.moshi:moshi-kotlin:1.9.3"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
implementation 'com.github.sealedtx:java-youtube-downloader:2.2.2' implementation 'com.github.sealedtx:java-youtube-downloader:2.2.2'
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'

View File

@ -21,6 +21,11 @@
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name="com.shabinder.musicForEveryone.MainActivity"> <activity android:name="com.shabinder.musicForEveryone.MainActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -6,43 +6,52 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.StrictMode
import android.util.Log import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.musicForEveryone.databinding.MainActivityBinding import com.shabinder.musicForEveryone.databinding.MainActivityBinding
import com.shabinder.musicForEveryone.downloadHelper.DownloadHelper
import com.shabinder.musicForEveryone.utils.SpotifyNewService import com.shabinder.musicForEveryone.utils.SpotifyNewService
import com.shabinder.musicForEveryone.utils.YoutubeInterface import com.shabinder.musicForEveryone.utils.YoutubeInterface
import com.spotify.sdk.android.authentication.AuthenticationClient import com.spotify.sdk.android.authentication.AuthenticationClient
import com.spotify.sdk.android.authentication.AuthenticationRequest import com.spotify.sdk.android.authentication.AuthenticationRequest
import com.spotify.sdk.android.authentication.AuthenticationResponse import com.spotify.sdk.android.authentication.AuthenticationResponse
import com.spotify.sdk.android.authentication.LoginActivity import com.spotify.sdk.android.authentication.LoginActivity
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kaaes.spotify.webapi.android.SpotifyApi import kaaes.spotify.webapi.android.SpotifyApi
import kaaes.spotify.webapi.android.SpotifyService import kaaes.spotify.webapi.android.SpotifyService
import retrofit.RestAdapter import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() ,DownloadHelper{
private lateinit var binding: MainActivityBinding private lateinit var binding: MainActivityBinding
var ytDownloader : YoutubeDownloader? = null var ytDownloader : YoutubeDownloader? = null
var spotifyExtra : SpotifyNewService? = null
var downloadManager : DownloadManager? = null var downloadManager : DownloadManager? = null
val REDIRECT_URI = "musicforeveryone://callback" val REDIRECT_URI = "musicforeveryone://callback"
val CLIENT_ID:String = "694d8bf4f6ec420fa66ea7fb4c68f89d" val CLIENT_ID:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
// val musicDirectory = File(this.filesDir?.absolutePath + "/Music/")
var message :String =""
var token :String ="" var token :String =""
var spotify: SpotifyService? = null var spotify: SpotifyService? = null
lateinit var sharedViewModel: SharedViewModel lateinit var sharedViewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.main_activity) binding = DataBindingUtil.setContentView(this,R.layout.main_activity)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
val policy = // val policy =
StrictMode.ThreadPolicy.Builder().permitAll().build() // StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy) // StrictMode.setThreadPolicy(policy)
//TODO Use Coroutines //TODO Use Coroutines
if(spotify==null){ if(spotify==null){
authenticateSpotify() authenticateSpotify()
@ -59,7 +68,14 @@ class MainActivity : AppCompatActivity() {
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
sharedViewModel.downloadManager = downloadManager sharedViewModel.downloadManager = downloadManager
if (intent?.action == Intent.ACTION_SEND) {
if ("text/plain" == intent.type) {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
Log.i("Intent Received",it)
sharedViewModel.intentString = it
}
}
}
} }
@ -85,13 +101,21 @@ class MainActivity : AppCompatActivity() {
spotify = api.service spotify = api.service
sharedViewModel.spotify = api.service sharedViewModel.spotify = api.service
//Initiate Processes In Main Fragment //Initiate Processes In Main Fragment
val me = spotify?.me?.display_name
sharedViewModel.uiScope.launch {
val me = spotifyExtra?.getMe()?.display_name
sharedViewModel.userName.value = me sharedViewModel.userName.value = me
Log.i("Network",me!!) Log.i("Network",me!!)
} }
sharedViewModel.userName.observe(this, Observer {
binding.message.text = it
})
}
AuthenticationResponse.Type.ERROR -> { AuthenticationResponse.Type.ERROR -> {
Log.i("Network",response.error.toString()) Log.i("Network",response.error.toString())
} }
else -> { else -> {
} }
@ -103,17 +127,33 @@ class MainActivity : AppCompatActivity() {
* Adding my own new Spotify Web Api Requests! * Adding my own new Spotify Web Api Requests!
* */ * */
private fun implementSpotifyExtra() { private fun implementSpotifyExtra() {
val restAdapter = RestAdapter.Builder()
.setEndpoint(SpotifyApi.SPOTIFY_WEB_API_ENDPOINT) val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
.setRequestInterceptor { request ->
request.addHeader( httpClient.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request =
chain.request().newBuilder().addHeader(
"Authorization", "Authorization",
"Bearer $token" "Bearer $token"
) ).build()
return chain.proceed(request)
} }
})
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build() .build()
val spotifyExtra = restAdapter.create(SpotifyNewService::class.java) val retrofit: Retrofit =
Retrofit.Builder()
.baseUrl("https://api.spotify.com/v1/")
.client(httpClient.build())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
spotifyExtra = retrofit.create(SpotifyNewService::class.java)
sharedViewModel.spotifyExtra = spotifyExtra sharedViewModel.spotifyExtra = spotifyExtra
} }
@ -129,7 +169,7 @@ class MainActivity : AppCompatActivity() {
private fun authenticateSpotify() { private fun authenticateSpotify() {
val builder = AuthenticationRequest.Builder(CLIENT_ID,AuthenticationResponse.Type.TOKEN,REDIRECT_URI) val builder = AuthenticationRequest.Builder(CLIENT_ID,AuthenticationResponse.Type.TOKEN,REDIRECT_URI)
.setShowDialog(true) .setShowDialog(false)
.setScopes(arrayOf("user-read-private","streaming","user-read-email","user-modify-playback-state","user-top-read","user-library-modify","user-read-currently-playing","user-library-read","user-read-recently-played")) .setScopes(arrayOf("user-read-private","streaming","user-read-email","user-modify-playback-state","user-top-read","user-library-modify","user-read-currently-playing","user-library-read","user-read-recently-played"))
val request: AuthenticationRequest = builder.build() val request: AuthenticationRequest = builder.build()
AuthenticationClient.openLoginActivity(this, LoginActivity.REQUEST_CODE, request) AuthenticationClient.openLoginActivity(this, LoginActivity.REQUEST_CODE, request)

View File

@ -9,8 +9,12 @@ import kaaes.spotify.webapi.android.SpotifyService
import kaaes.spotify.webapi.android.models.Album import kaaes.spotify.webapi.android.models.Album
import kaaes.spotify.webapi.android.models.Playlist import kaaes.spotify.webapi.android.models.Playlist
import kaaes.spotify.webapi.android.models.Track import kaaes.spotify.webapi.android.models.Track
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
class SharedViewModel : ViewModel() { class SharedViewModel : ViewModel() {
var intentString = ""
var accessToken:String = "" var accessToken:String = ""
var userName = MutableLiveData<String>().apply { value = "Placeholder" } var userName = MutableLiveData<String>().apply { value = "Placeholder" }
var spotify :SpotifyService? = null var spotify :SpotifyService? = null
@ -18,15 +22,23 @@ class SharedViewModel : ViewModel() {
var ytDownloader : YoutubeDownloader? = null var ytDownloader : YoutubeDownloader? = null
var downloadManager : DownloadManager? = null var downloadManager : DownloadManager? = null
var viewModelJob = Job()
fun getTrackDetails(trackLink:String): Track?{ val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
return spotify?.getTrack(trackLink)
suspend fun getTrackDetails(trackLink:String): Track?{
return spotifyExtra?.getTrack(trackLink)
} }
fun getAlbumDetails(albumLink:String): Album?{ suspend fun getAlbumDetails(albumLink:String): Album?{
return spotify?.getAlbum(albumLink) return spotifyExtra?.getAlbum(albumLink)
} }
fun getPlaylistDetails(link:String): Playlist?{ suspend fun getPlaylistDetails(link:String): Playlist?{
return spotifyExtra?.getPlaylist(link) return spotifyExtra?.getPlaylist(link)
} }
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
} }

View File

@ -9,16 +9,20 @@ import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.formats.Format
import com.github.kiulian.downloader.model.quality.AudioQuality import com.github.kiulian.downloader.model.quality.AudioQuality
import com.shabinder.musicForEveryone.utils.YoutubeInterface import com.shabinder.musicForEveryone.utils.YoutubeInterface
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
interface DownloadHelper { interface DownloadHelper {
fun downloadTrack(ytDownloader: YoutubeDownloader?, downloadManager: DownloadManager?, searchQuery:String){ suspend fun downloadTrack(ytDownloader: YoutubeDownloader?, downloadManager: DownloadManager?, searchQuery:String){
//@data = 1st object from YT query.
withContext(Dispatchers.IO){
val downloadIdList = mutableListOf<Int>()
val data = YoutubeInterface.search(searchQuery)?.get(0) val data = YoutubeInterface.search(searchQuery)?.get(0)
if (data==null){Log.i("DownloadHelper","Youtube Request Failed!")}else{ if (data==null){Log.i("DownloadHelper","Youtube Request Failed!")}else{
//Fetching a Video Object.
val video = ytDownloader?.getVideo(data.id) val video = ytDownloader?.getVideo(data.id)
//Fetching a Video Object.
val details = video?.details() val details = video?.details()
val format:Format = video?.findAudioWithQuality(AudioQuality.low)?.get(0) as Format val format:Format = video?.findAudioWithQuality(AudioQuality.low)?.get(0) as Format
@ -26,19 +30,25 @@ interface DownloadHelper {
val audioUrl = format.url() val audioUrl = format.url()
if (audioUrl != null) { if (audioUrl != null) {
downloadFile(audioUrl,downloadManager,details?.title()?:"Error") downloadFile(audioUrl,downloadManager,details!!.title())
Log.i("DHelper Start Download", audioUrl) Log.i("DHelper Start Download", audioUrl)
}else{Log.i("YT audio url is null", format.toString())} }else{Log.i("YT audio url is null", format.toString())}
}
// Library Inbuilt function to Save File (Need Scoped Storage Implementation) // Library Inbuilt function to Save File (Need Scoped Storage Implementation)
// val file: File = video.download( format , outputDir) // val file: File = video.download( format , outputDir)
} }
//@data = 1st object from YT query.
} }
/** /**
* Downloading Using Android Download Manager * Downloading Using Android Download Manager
* */ * */
fun downloadFile(url: String, downloadManager: DownloadManager?,title:String){ suspend fun downloadFile(url: String, downloadManager: DownloadManager?,title:String){
withContext(Dispatchers.IO){
val audioUri = Uri.parse(url) val audioUri = Uri.parse(url)
val outputDir = File.separator + "Spotify-Downloads" +File.separator + "${removeIllegalChars(title)}.mp3" val outputDir = File.separator + "Spotify-Downloads" +File.separator + "${removeIllegalChars(title)}.mp3"
@ -55,6 +65,7 @@ interface DownloadHelper {
downloadManager?.enqueue(request) downloadManager?.enqueue(request)
Log.i("DownloadManager","Download Request Sent") Log.i("DownloadManager","Download Request Sent")
} }
}
/** /**

View File

@ -18,6 +18,7 @@ import com.shabinder.musicForEveryone.recyclerView.TrackListAdapter
import com.shabinder.musicForEveryone.utils.bindImage import com.shabinder.musicForEveryone.utils.bindImage
import kaaes.spotify.webapi.android.SpotifyService import kaaes.spotify.webapi.android.SpotifyService
import kaaes.spotify.webapi.android.models.Track import kaaes.spotify.webapi.android.models.Track
import kotlinx.coroutines.launch
class MainFragment : Fragment(),DownloadHelper { class MainFragment : Fragment(),DownloadHelper {
@ -25,6 +26,7 @@ class MainFragment : Fragment(),DownloadHelper {
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
var spotify : SpotifyService? = null var spotify : SpotifyService? = null
var type:String = "" var type:String = ""
var spotifyLink = ""
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@ -33,13 +35,9 @@ class MainFragment : Fragment(),DownloadHelper {
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
spotify = sharedViewModel.spotify spotify = sharedViewModel.spotify
sharedViewModel.userName.observe(viewLifecycleOwner, Observer {
binding.message.text = it
// if(it!="Placeholder"){Snackbar.make(requireView(),"Hello, $it!", Snackbar.LENGTH_SHORT).show()}
})
binding.btnSearch.setOnClickListener { binding.btnSearch.setOnClickListener {
val spotifyLink = binding.spotifyLink.text.toString() spotifyLink = binding.spotifyLink.text.toString()
val link = spotifyLink.substringAfterLast('/' , "Error").substringBefore('?') val link = spotifyLink.substringAfterLast('/' , "Error").substringBefore('?')
type = spotifyLink.substringBeforeLast('/' , "Error").substringAfterLast('/') type = spotifyLink.substringBeforeLast('/' , "Error").substringAfterLast('/')
@ -49,25 +47,29 @@ class MainFragment : Fragment(),DownloadHelper {
val adapter = TrackListAdapter() val adapter = TrackListAdapter()
binding.trackList.adapter = adapter binding.trackList.adapter = adapter
adapter.sharedViewModel = sharedViewModel adapter.sharedViewModel = sharedViewModel
when(type){ when(type){
"track" -> { "track" -> {
sharedViewModel.uiScope.launch{
val trackObject = sharedViewModel.getTrackDetails(link) val trackObject = sharedViewModel.getTrackDetails(link)
binding.imageView.visibility =View.VISIBLE
val trackList = mutableListOf<Track>() val trackList = mutableListOf<Track>()
trackList.add(trackObject!!) trackList.add(trackObject!!)
bindImage(binding.imageView, trackObject.album.images[0].url)
adapter.totalItems = 1 adapter.totalItems = 1
adapter.trackList = trackList adapter.trackList = trackList
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString()) Log.i("Adapter",trackList.size.toString())
} }
}
"album" -> { "album" -> {
sharedViewModel.uiScope.launch{
val albumObject = sharedViewModel.getAlbumDetails(link) val albumObject = sharedViewModel.getAlbumDetails(link)
bindImage(binding.imageView,albumObject!!.images[1].url) binding.titleView.text = albumObject!!.name
binding.titleView.text = albumObject.name
binding.titleView.visibility =View.VISIBLE binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE binding.imageView.visibility =View.VISIBLE
@ -79,30 +81,49 @@ class MainFragment : Fragment(),DownloadHelper {
Log.i("Adapter",trackList.size.toString()) Log.i("Adapter",trackList.size.toString())
bindImage(binding.imageView, albumObject.images[0].url)
}
} }
"playlist" -> { "playlist" -> {
sharedViewModel.uiScope.launch{
val playlistObject = sharedViewModel.getPlaylistDetails(link) val playlistObject = sharedViewModel.getPlaylistDetails(link)
bindImage(binding.imageView,playlistObject!!.images[0].url) binding.titleView.text = "${if(playlistObject!!.name.length > 18){"${playlistObject.name.subSequence(0,17)}..."}else{playlistObject.name}}"
binding.titleView.text = playlistObject.name
binding.titleView.visibility =View.VISIBLE binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE binding.imageView.visibility =View.VISIBLE
binding.playlistOwner.visibility =View.VISIBLE
binding.playlistOwner.text = "by: ${playlistObject.owner.display_name}"
val trackList = mutableListOf<Track>() val trackList = mutableListOf<Track>()
playlistObject.tracks?.items!!.forEach { trackList.add(it.track) } playlistObject.tracks?.items!!.forEach { trackList.add(it.track) }
adapter.trackList = trackList.toList() adapter.trackList = trackList.toList()
adapter.totalItems = trackList.size adapter.totalItems = trackList.size
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString()) Log.i("Adapter",trackList.size.toString())
bindImage(binding.imageView, playlistObject.images[0].url)
} }
}
"episode" -> {showToast("Implementation Pending")} "episode" -> {showToast("Implementation Pending")}
"show" -> {showToast("Implementation Pending ")} "show" -> {showToast("Implementation Pending ")}
} }
binding.spotifyLink.setText(link)
} }
binding.spotifyLink.setText(sharedViewModel.intentString)
sharedViewModel.userName.observe(viewLifecycleOwner, Observer {
//Waiting for Authentication to Finish with Spotify
if (it != "Placeholder"){
if(sharedViewModel.intentString != ""){binding.btnSearch.performClick()}
}
})
return binding.root return binding.root
} }

View File

@ -3,7 +3,7 @@ package com.shabinder.musicForEveryone.recyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -12,6 +12,7 @@ import com.shabinder.musicForEveryone.SharedViewModel
import com.shabinder.musicForEveryone.downloadHelper.DownloadHelper import com.shabinder.musicForEveryone.downloadHelper.DownloadHelper
import com.shabinder.musicForEveryone.utils.bindImage import com.shabinder.musicForEveryone.utils.bindImage
import kaaes.spotify.webapi.android.models.Track import kaaes.spotify.webapi.android.models.Track
import kotlinx.coroutines.launch
class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),DownloadHelper { class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),DownloadHelper {
@ -30,25 +31,25 @@ class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),Downl
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = trackList[position] val item = trackList[position]
if(totalItems == 1){holder.coverImage.visibility = View.GONE}else{
bindImage(holder.coverImage,item.album.images[0].url) bindImage(holder.coverImage,item.album.images[0].url)
}
holder.trackName.text = "${if(item.name.length > 22){"${item.name.subSequence(0,19)}..."}else{item.name}}" holder.trackName.text = "${if(item.name.length > 17){"${item.name.subSequence(0,16)}..."}else{item.name}}"
holder.artistName.text = "${item.artists[0]?.name?:""}..." holder.artistName.text = "${item.artists[0]?.name?:""}..."
holder.popularity.text = item.popularity.toString() holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} seconds"
holder.downloadBtn.setOnClickListener{ holder.downloadBtn.setOnClickListener{
sharedViewModel.uiScope.launch {
downloadTrack(sharedViewModel.ytDownloader,sharedViewModel.downloadManager,"${item.name} ${item.artists[0].name?:""}") downloadTrack(sharedViewModel.ytDownloader,sharedViewModel.downloadManager,"${item.name} ${item.artists[0].name?:""}")
} }
}
} }
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val trackName:TextView = itemView.findViewById(R.id.track_name) val trackName:TextView = itemView.findViewById(R.id.track_name)
val artistName:TextView = itemView.findViewById(R.id.artist) val artistName:TextView = itemView.findViewById(R.id.artist)
val popularity:TextView = itemView.findViewById(R.id.popularity)
val duration:TextView = itemView.findViewById(R.id.duration) val duration:TextView = itemView.findViewById(R.id.duration)
val downloadBtn:Button = itemView.findViewById(R.id.btn_download) val downloadBtn:ImageButton = itemView.findViewById(R.id.btn_download)
val coverImage:ImageView = itemView.findViewById(R.id.imageUrl) val coverImage:ImageView = itemView.findViewById(R.id.imageUrl)
} }
} }

View File

@ -10,6 +10,7 @@ import com.shabinder.musicForEveryone.R
@BindingAdapter("imageUrl") @BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) { fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let { imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build() val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context) Glide.with(imgView.context)
@ -18,4 +19,5 @@ fun bindImage(imgView: ImageView, imgUrl: String?) {
.error(R.drawable.ic_musicplaceholder)) .error(R.drawable.ic_musicplaceholder))
.into(imgView) .into(imgView)
} }
} }

View File

@ -1,15 +1,36 @@
package com.shabinder.musicForEveryone.utils package com.shabinder.musicForEveryone.utils
import kaaes.spotify.webapi.android.models.Playlist import kaaes.spotify.webapi.android.models.*
import retrofit.http.GET import retrofit2.http.GET
import retrofit.http.Path import retrofit2.http.Path
interface SpotifyNewService { interface SpotifyNewService {
@GET("/playlists/{playlist_id}") @GET("playlists/{playlist_id}")
fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist? suspend fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist?
@GET("tracks/{id}")
suspend fun getTrack(@Path("id") var1: String?): Track?
@GET("albums/{id}")
suspend fun getAlbum(@Path("id") var1: String?): Album?
@GET("me")
suspend fun getMe(): com.shabinder.musicForEveryone.utils.UserPrivate?
} }
data class UserPrivate(
val country:String,
var display_name: String,
val email:String,
var external_urls: Map<String?, String?>? = null,
var followers: Followers? = null,
var href: String? = null,
var id: String? = null,
var images: List<Image?>? = null,
var product:String,
var type: String? = null,
var uri: String? = null)

View File

@ -7,13 +7,32 @@
android:id="@+id/mainActivity" android:id="@+id/mainActivity"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/text_background"
android:padding="5dp"
android:paddingTop="6dp"
android:text="MainFragment"
android:textColor="@color/colorPrimary"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<fragment <fragment
android:id="@+id/NavHostFragment" android:id="@+id/NavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
app:defaultNavHost="true" app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/navigation" app:navGraph="@navigation/navigation"
tools:ignore="FragmentTagUsage" /> tools:ignore="FragmentTagUsage" />

View File

@ -1,105 +1,128 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="25dp"
android:fitsSystemWindows="true"
tools:context=".fragments.MainFragment"> tools:context=".fragments.MainFragment">
<TextView <com.google.android.material.appbar.AppBarLayout
android:id="@+id/message" android:id="@+id/appbar"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="230dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:layout_scrollInterpolator="@android:anim/decelerate_interpolator"
app:toolbarId="@+id/toolbar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp" android:orientation="horizontal">
android:text="MainFragment"
android:background="@drawable/text_backdround"
android:padding="5dp"
android:textColor="@color/colorPrimary"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText <EditText
android:id="@+id/spotifyLink" android:id="@+id/spotifyLink"
android:layout_width="wrap_content" android:layout_width="257dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginTop="25dp" android:layout_marginStart="8dp"
android:background="@drawable/text_backdround" android:layout_marginTop="8dp"
android:hint="Link From Spotify" android:layout_marginEnd="20dp"
android:background="@drawable/text_background"
android:ems="10" android:ems="10"
android:padding="5dp" android:hint="Link From Spotify"
android:inputType="text" android:inputType="text"
android:padding="5dp"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/white" android:textColor="@color/white"
android:textColorHint="@color/grey" android:textColorHint="@color/grey"
android:textSize="20dp" android:textSize="19sp" />
app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button <Button
android:id="@+id/btn_search" android:id="@+id/btn_search"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:backgroundTint="@color/colorPrimary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:backgroundTint="@color/colorPrimary"
android:text="Search" android:text="Search"
android:textSize="18sp" android:textSize="18sp" />
app:layout_constraintBottom_toBottomOf="@+id/spotifyLink" </LinearLayout>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/spotifyLink"
app:layout_constraintTop_toTopOf="@+id/spotifyLink" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="3dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_view"
app:layout_constraintVertical_bias="0.0" />
<ImageView <ImageView
android:id="@+id/image_view" android:id="@+id/image_view"
android:layout_width="150dp" android:layout_width="match_parent"
android:layout_height="150dp" android:layout_height="match_parent"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent" android:contentDescription="Album Cover"
app:layout_constraintStart_toStartOf="parent" android:scaleType="centerInside"
app:layout_constraintTop_toBottomOf="@+id/spotifyLink" android:src="@drawable/ic_launcher_foreground"
android:visibility="gone"/> app:layout_collapseMode="parallax"/>
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<TextView <TextView
android:id="@+id/title_view" android:id="@+id/title_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="10dp"
android:layout_marginBottom="5dp" android:text="Shabinder's Playlist"
android:text="TextView" android:textAlignment="center"
android:background="@drawable/text_backdround"
android:drawableTint="@color/colorPrimary"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textAllCaps="false"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:textSize="24sp" android:textSize="26sp"
android:visibility="gone" android:visibility="gone"
android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/track_list"
app:layout_constraintEnd_toStartOf="@+id/playlist_owner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playlist_owner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/text_background_accented"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="by : Shabinder"
android:textColor="@color/white"
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/title_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/title_view" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_view" /> app:layout_constraintTop_toBottomOf="@+id/title_view" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View File

@ -6,15 +6,19 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="80dp"
android:background="#000000" android:background="#000000"
android:layout_marginBottom="12dp"
> >
<ImageView <ImageView
android:id="@+id/imageUrl" android:id="@+id/imageUrl"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="80dp"
android:contentDescription="Track Image" android:contentDescription="Track Image"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/artist"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_musicplaceholder" app:srcCompat="@drawable/ic_musicplaceholder"
@ -22,114 +26,60 @@
<TextView <TextView
android:id="@+id/track_name" android:id="@+id/track_name"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:fontFamily="monospace" android:fontFamily="monospace"
android:text="TextView" android:letterSpacing="-0.03"
android:lines="1"
android:text="The Spectre"
android:textAllCaps="false" android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4" android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:textSize="20sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintStart_toEndOf="@+id/imageUrl" app:layout_constraintStart_toStartOf="@+id/artist"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:paddingLeft="9dp"
android:text="Artist"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/textview3"
app:layout_constraintEnd_toStartOf="@+id/artist"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintTop_toBottomOf="@+id/track_name" />
<TextView <TextView
android:id="@+id/artist" android:id="@+id/artist"
style="@style/TextAppearance.AppCompat.Body2" style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:paddingLeft="9dp" android:paddingLeft="9dp"
android:text="TextView" android:text="Alan Walker"
app:layout_constraintBottom_toBottomOf="@+id/textView1" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView1"
app:layout_constraintTop_toTopOf="@+id/textView1" />
<TextView
android:id="@+id/textview3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="9dp"
android:text="Popularity"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/textView5"
app:layout_constraintEnd_toStartOf="@+id/popularity"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintTop_toBottomOf="@+id/textView1" />
<TextView
android:id="@+id/popularity"
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="9dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="@+id/textview3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textview3"
app:layout_constraintTop_toTopOf="@+id/textview3" />
<TextView
android:id="@+id/textView5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:paddingLeft="9dp"
android:text="Duration"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/btn_download"
app:layout_constraintEnd_toStartOf="@+id/duration" app:layout_constraintEnd_toStartOf="@+id/duration"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageUrl" app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintTop_toBottomOf="@+id/textview3" /> app:layout_constraintTop_toBottomOf="@+id/track_name" />
<TextView <TextView
android:id="@+id/duration" android:id="@+id/duration"
style="@style/TextAppearance.AppCompat.Body2" style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="9dp" android:paddingLeft="9dp"
android:text="TextView" android:text="4 minutes, 20 sec"
app:layout_constraintBottom_toBottomOf="@+id/textView5" app:layout_constraintBottom_toBottomOf="@+id/artist"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/artist"
app:layout_constraintStart_toEndOf="@+id/textView5" app:layout_constraintTop_toTopOf="@+id/artist" />
app:layout_constraintTop_toTopOf="@+id/textView5" />
<Button <ImageButton
android:id="@+id/btn_download" android:id="@+id/btn_download"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="60dp"
android:layout_width="0dp" android:layout_height="0dp"
android:layout_height="wrap_content" android:backgroundTint="@color/black"
android:backgroundTint="#27FF3D" android:scaleType="fitCenter"
android:text="Download" app:layout_constraintBottom_toBottomOf="parent"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:srcCompat="@drawable/ic_arrow" />
app:layout_constraintTop_toBottomOf="@+id/imageUrl" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>