Gaana Implementation Done

This commit is contained in:
Shabinder 2020-11-09 16:29:33 +05:30
parent 180a284a54
commit d97536ee50
15 changed files with 314 additions and 49 deletions

View File

@ -17,8 +17,11 @@
package com.shabinder.spotiflyer.models.gaana
import com.squareup.moshi.Json
data class Artist (
val popularity : Int,
val seokey : String,
val name : String,
@Json(name = "artwork_175x175")var artworkLink :String?
)

View File

@ -18,8 +18,7 @@
package com.shabinder.spotiflyer.models.gaana
data class GaanaPlaylist (
val tags : String,
val fromCache : Int,
val tags : String?,
val modified_on : String,
val count : Int,
val created_on : String,

View File

@ -21,12 +21,13 @@ import com.shabinder.spotiflyer.models.DownloadStatus
import com.squareup.moshi.Json
data class GaanaTrack (
val tags : List<Tags>,
val tags : List<Tags?>?,
val seokey : String,
val albumseokey : String,
val track_title : String,
val album_title : String,
val language : String,
val duration: Int,
@Json(name = "artwork_large") val artworkLink : String,
val artist : List<Artist>,
@Json(name = "gener") val genre : List<Genre>,
@ -35,6 +36,6 @@ data class GaanaTrack (
val total_favourite_count : Int,
val release_date : String,
val play_ct : String,
val secondary_language : String,
val secondary_language : String?,
var downloaded: DownloadStatus? = DownloadStatus.NotDownloaded
)

View File

@ -20,4 +20,5 @@ package com.shabinder.spotiflyer.models.spotify
enum class Source {
Spotify,
YouTube,
Gaana,
}

View File

@ -31,7 +31,7 @@ interface GaanaInterface {
*
* subtype : ["most_popular_playlist" , "playlist_home_featured" ,"playlist_detail" ,"user_playlist" ,"topCharts"]
**/
@GET
@GET(".")
suspend fun getGaanaPlaylist(
@Query("type") type: String = "playlist",
@Query("subtype") subtype: String = "playlist_detail",
@ -46,7 +46,7 @@ interface GaanaInterface {
*
* subtype : ["most_popular" , "new_release" ,"featured_album" ,"similar_album" ,"all_albums", "album" ,"album_detail" ,"album_detail_info"]
**/
@GET
@GET(".")
suspend fun getGaanaAlbum(
@Query("type") type: String = "album",
@Query("subtype") subtype: String = "album_detail",
@ -61,7 +61,7 @@ interface GaanaInterface {
*
* subtype : ["most_popular" , "hot_songs" ,"recommendation" ,"song_detail"]
**/
@GET
@GET(".")
suspend fun getGaanaSong(
@Query("type") type: String = "song",
@Query("subtype") subtype: String = "song_detail",
@ -75,7 +75,7 @@ interface GaanaInterface {
*
* subtype : ["most_popular" , "artist_list" ,"artist_track_listing" ,"artist_album" ,"similar_artist","artist_details" ,"artist_details_info"]
**/
@GET
@GET(".")
suspend fun getGaanaArtistDetails(
@Query("type") type: String = "artist",
@Query("subtype") subtype: String = "artist_details_info",
@ -88,7 +88,7 @@ interface GaanaInterface {
*
* subtype : ["most_popular" , "artist_list" ,"artist_track_listing" ,"artist_album" ,"similar_artist","artist_details" ,"artist_details_info"]
**/
@GET
@GET(".")
suspend fun getGaanaArtistTracks(
@Query("type") type: String = "artist",
@Query("subtype") subtype: String = "artist_track_listing",

View File

@ -76,18 +76,18 @@ class TrackListAdapter(private val viewModel :BaseViewModel): ListAdapter<TrackD
rotateAnim(it)
item.downloaded = DownloadStatus.Downloading
when(source){
Source.Spotify -> {
Source.YouTube -> {
viewModel.uiScope.launch {
DownloadHelper.downloadAllTracks(
YTDownloadHelper.downloadYTTracks(
viewModel.folderType,
viewModel.subFolder,
listOf(item)
)
}
}
Source.YouTube -> {
else -> {
viewModel.uiScope.launch {
YTDownloadHelper.downloadYTTracks(
DownloadHelper.downloadAllTracks(
viewModel.folderType,
viewModel.subFolder,
listOf(item)

View File

@ -18,6 +18,8 @@
package com.shabinder.spotiflyer.ui.gaana
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
@ -27,19 +29,24 @@ import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class GaanaFragment : Fragment() {
private lateinit var binding: TrackListFragmentBinding
@ -56,8 +63,9 @@ class GaanaFragment : Fragment() {
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment, container, false)
viewModel = ViewModelProvider(this).get(GaanaViewModel::class.java)
adapter = TrackListAdapter(viewModel)
initializeAll()
initializeLiveDataObservers()
initializeBroadcast()
val gaanaLink = GaanaFragmentArgs.fromBundle(requireArguments()).link.substringAfter("gaana.com/")
//Link Schema: https://gaana.com/type/link
@ -95,7 +103,7 @@ class GaanaFragment : Fragment() {
val urlList = arrayListOf<String>()
viewModel.trackList.value?.forEach { urlList.add(it.albumArtURL) }
//Appending Source
urlList.add("spotify")
urlList.add("gaana")
loadAllImages(
requireActivity(),
urlList
@ -116,4 +124,89 @@ class GaanaFragment : Fragment() {
return binding.root
}
/**
* Basic Initialization
**/
private fun initializeAll() {
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
viewModel = ViewModelProvider(this).get(GaanaViewModel::class.java)
viewModel.gaanaInterface = gaanaInterface
adapter = TrackListAdapter(viewModel)
DownloadHelper.youtubeMusicApi = youtubeMusicApi
DownloadHelper.sharedViewModel = sharedViewModel
DownloadHelper.statusBar = binding.statusBar
binding.trackList.adapter = adapter
(binding.trackList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
/**
*Live Data Observers
**/
private fun initializeLiveDataObservers() {
viewModel.trackList.observe(viewLifecycleOwner, {
if (it.isNotEmpty()){
Log.i("GaanaFragment","TrackList Updated")
adapter.submitList(it,Source.Gaana)
checkIfAllDownloaded()
}
})
viewModel.coverUrl.observe(viewLifecycleOwner, {
if(it!="Loading") bindImage(binding.coverImage,it, Source.Gaana)
})
viewModel.title.observe(viewLifecycleOwner, {
binding.titleView.text = it
})
}
private fun checkIfAllDownloaded() {
if(!viewModel.trackList.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()
}
}
}
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<TrackDetails?>("track")
trackDetails?.let {
val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position")
if(position != -1) {
val track = viewModel.trackList.value?.get(position)
track?.let{
it.downloaded = DownloadStatus.Downloaded
viewModel.trackList.value?.set(position, it)
adapter.notifyItemChanged(position)
checkIfAllDownloaded()
}
}
}
}
}
}
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
}
override fun onResume() {
super.onResume()
initializeBroadcast()
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver)
}
}

View File

@ -17,10 +17,23 @@
package com.shabinder.spotiflyer.ui.gaana
import android.os.Environment
import android.util.Log
import androidx.hilt.lifecycle.ViewModelInject
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.gaana.*
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.utils.BaseViewModel
import com.shabinder.spotiflyer.utils.Provider
import com.shabinder.spotiflyer.utils.finalOutputDir
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : BaseViewModel(){
@ -29,7 +42,156 @@ class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO)
var gaanaInterface : GaanaInterface? = null
fun gaanaSearch(type:String,link:String){
when(type){
"song" -> {
uiScope.launch {
getGaanaSong(link)?.tracks?.firstOrNull()?.also {
folderType = "Tracks"
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){
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)
)
)
}
}
}
}
"album" -> {
uiScope.launch {
getGaanaAlbum(link)?.also {
folderType = "Albums"
subFolder = link
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()
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)
))
}
}
}
}
"playlist" -> {
uiScope.launch {
getGaanaPlaylist(link)?.also {
folderType = "Playlists"
subFolder = link
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()
title.value = link
//coverUrl.value = "TODO"
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" -> {
uiScope.launch {
folderType = "Artist"
subFolder = link
val artistDetails = getGaanaArtistDetails(link)?.artist?.firstOrNull()?.also {
title.value = it.name
coverUrl.value = it.artworkLink
}
getGaanaArtistTracks(link)?.also {
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)
))
}
}
}
}
}
}
private fun List<GaanaTrack>.toTrackDetailsList() = this.map {
TrackDetails(
title = it.track_title,
artists = it.artist.map { artist -> artist.name },
durationSec = it.duration,
albumArt = File(
Environment.getExternalStorageDirectory(),
Provider.defaultDir +".Images/" + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg"),
albumName = it.album_title,
year = it.release_date,
comment = "Genres:${it.genre.map { genre -> genre.name }.reduceOrNull { acc, s -> acc + s }}",
trackUrl = it.lyrics_url,
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
source = Source.Gaana,
albumArtURL = it.artworkLink
)
}.toMutableList()
private suspend fun getGaanaSong(songLink:String): GaanaSong?{
Log.i("Requesting","https://gaana.com/song/$songLink")
return gaanaInterface?.getGaanaSong(seokey = songLink)?.value
}
private suspend fun getGaanaAlbum(albumLink:String): GaanaAlbum?{
Log.i("Requesting","https://gaana.com/album/$albumLink")
return gaanaInterface?.getGaanaAlbum(seokey = albumLink)?.value
}
private suspend fun getGaanaPlaylist(link:String): GaanaPlaylist?{
Log.i("Requesting","https://gaana.com/playlist/$link")
return gaanaInterface?.getGaanaPlaylist(seokey = link)?.value
}
private suspend fun getGaanaArtistDetails(link:String): GaanaArtistDetails?{
Log.i("Requesting","https://gaana.com/artist/$link")
return gaanaInterface?.getGaanaArtistDetails(seokey = link)?.value
}
private suspend fun getGaanaArtistTracks(link:String,limit:Int = 50): GaanaArtistTracks?{
Log.i("Requesting","Tracks of: https://gaana.com/artist/$link")
return gaanaInterface?.getGaanaArtistTracks(seokey = link,limit = limit)?.value
}
}

View File

@ -163,7 +163,7 @@ class SpotifyFragment : Fragment() {
viewModel.trackList.observe(viewLifecycleOwner, {
if (it.isNotEmpty()){
Log.i("SpotifyFragment","TrackList Updated")
adapter.submitList(it)
adapter.submitList(it,Source.Spotify)
checkIfAllDownloaded()
}
})

View File

@ -45,26 +45,25 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
when (type) {
"track" -> {
uiScope.launch {
val trackObject = getTrackDetails(link)
folderType = "Tracks"
val tempTrackList = mutableListOf<Track>()
if(File(finalOutputDir(trackObject?.name!!,folderType,subFolder)).exists()){//Download Already Present!!
trackObject.downloaded = DownloadStatus.Downloaded
}
tempTrackList.add(trackObject)
trackList.value = tempTrackList.toTrackDetailsList()
title.value = trackObject.name
coverUrl.value = trackObject.album!!.images?.get(0)!!.url!!
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Track",
name = title.value!!,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value!!,
totalFiles = tempTrackList.size,
downloaded = trackObject.downloaded == DownloadStatus.Downloaded,
directory = finalOutputDir(trackObject.name!!,folderType,subFolder)
))
getTrackDetails(link)?.also {
folderType = "Tracks"
if(File(finalOutputDir(it.name,folderType,subFolder)).exists()){//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
trackList.value = listOf(it).toTrackDetailsList()
title.value = it.name
coverUrl.value = it.album!!.images?.get(0)!!.url!!
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Track",
name = title.value!!,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value!!,
totalFiles = 1,
downloaded = it.downloaded == DownloadStatus.Downloaded,
directory = finalOutputDir(it.name,folderType,subFolder)
))
}
}
}
}
@ -74,15 +73,13 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
val albumObject = getAlbumDetails(link)
folderType = "Albums"
subFolder = albumObject?.name.toString()
val tempTrackList = mutableListOf<Track>()
albumObject?.tracks?.items?.forEach {
if(File(finalOutputDir(it.name!!,folderType,subFolder)).exists()){//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
it.album = Album(images = listOf(Image(url = albumObject.images?.get(0)?.url)))
tempTrackList.add(it)
}
trackList.value = tempTrackList.toTrackDetailsList()
trackList.value = albumObject?.tracks?.items?.toTrackDetailsList()
title.value = albumObject?.name
coverUrl.value = albumObject?.images?.get(0)?.url
withContext(Dispatchers.IO){
@ -91,8 +88,8 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
name = title.value!!,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = tempTrackList.size,
downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == tempTrackList.size,
totalFiles = trackList.value?.size ?: 0,
downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == trackList.value?.size,
directory = finalOutputDir(type = folderType,subFolder = subFolder)
))
}
@ -149,11 +146,9 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
}
private fun List<Track>.toTrackDetailsList() = this.map {
val artistsList = mutableListOf<String>()
it.artists?.forEach { artist -> artistsList.add(artist!!.name!!) }
TrackDetails(
title = it.name.toString(),
artists = artistsList,
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms/1000).toInt(),
albumArt = File(
Environment.getExternalStorageDirectory(),

View File

@ -196,7 +196,7 @@ class YoutubeFragment : Fragment() {
* TrackList Binding Observer!
**/
viewModel.trackList.observe(viewLifecycleOwner, {
adapter.submitList(it)
adapter.submitList(it,Source.YouTube)
})
/**

View File

@ -30,7 +30,7 @@ abstract class BaseViewModel:ViewModel() {
abstract var subFolder:String
open val trackList = MutableLiveData<MutableList<TrackDetails>>()
private val viewModelJob:CompletableJob = Job()
open val uiScope = CoroutineScope(Dispatchers.Default + viewModelJob)
open val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private val loading = "Loading!"
open var title = MutableLiveData<String>().apply { value = loading }

View File

@ -125,7 +125,7 @@ object Provider {
@Singleton
fun getGaanaInterface(moshi: Moshi,okHttpClient: OkHttpClient):GaanaInterface{
val retrofit = Retrofit.Builder()
.baseUrl("http://api.gaana.com/")
.baseUrl("https://api.gaana.com/")
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()

View File

@ -179,6 +179,11 @@ fun bindImage(imgView: ImageView, imgUrl: String?,source: Source?) {
defaultDir+".Images/" + imgUrl.substringBeforeLast('/',imgUrl).substringAfterLast('/',imgUrl) + ".jpeg"
)
}
Source.Gaana -> {
File(
Environment.getExternalStorageDirectory(),
Provider.defaultDir +".Images/" + (imgUrl.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg")
}
else -> File(
Environment.getExternalStorageDirectory(),
defaultDir+".Images/" + imgUrl.substringAfterLast('/',imgUrl) + ".jpeg"

View File

@ -49,6 +49,7 @@ import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.utils.Provider
import com.shabinder.spotiflyer.utils.copyTo
import com.tonyodev.fetch2.*
import com.tonyodev.fetch2core.DownloadBlock
@ -628,6 +629,11 @@ class ForegroundService : Service(){
defaultDir +".Images/" + url.substringBeforeLast('/',url).substringAfterLast('/',url) + ".jpeg"
)
}
"gaana" -> {
File(
Environment.getExternalStorageDirectory(),
Provider.defaultDir +".Images/" + (url.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg")
}
else -> File(
Environment.getExternalStorageDirectory(),
defaultDir +".Images/" + url.substringAfterLast('/') + ".jpeg")