Dependencies Handling at Lower Level.

This commit is contained in:
Shabinder 2020-11-12 00:03:47 +05:30
parent c58f73be1d
commit 04b6f13b22
14 changed files with 171 additions and 179 deletions

View File

@ -36,6 +36,7 @@ import androidx.navigation.findNavController
import com.github.javiersantos.appupdater.AppUpdater import com.github.javiersantos.appupdater.AppUpdater
import com.github.javiersantos.appupdater.enums.UpdateFrom import com.github.javiersantos.appupdater.enums.UpdateFrom
import com.shabinder.spotiflyer.databinding.MainActivityBinding import com.shabinder.spotiflyer.databinding.MainActivityBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
@ -57,9 +58,9 @@ import javax.inject.Inject
class MainActivity : AppCompatActivity(){ class MainActivity : AppCompatActivity(){
private var spotifyService : SpotifyService? = null private var spotifyService : SpotifyService? = null
private lateinit var binding: MainActivityBinding private lateinit var binding: MainActivityBinding
lateinit var snackBarAnchor: View
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
private lateinit var navController: NavController lateinit var snackBarAnchor: View
lateinit var navController: NavController
@Inject lateinit var moshi: Moshi @Inject lateinit var moshi: Moshi
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest @Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
@ -72,6 +73,7 @@ class MainActivity : AppCompatActivity(){
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
navController = findNavController(R.id.navHostFragment) navController = findNavController(R.id.navHostFragment)
snackBarAnchor = binding.snackBarPosition snackBarAnchor = binding.snackBarPosition
DownloadHelper.youtubeMusicApi = sharedViewModel.youtubeMusicApi
authenticateSpotify() authenticateSpotify()
@ -80,7 +82,6 @@ class MainActivity : AppCompatActivity(){
checkIfLatestVersion() checkIfLatestVersion()
createDirectories() createDirectories()
Log.i("Connection Status", isOnline().toString()) Log.i("Connection Status", isOnline().toString())
//starting Notification and Downloader Service! //starting Notification and Downloader Service!
startService(this) startService(this)

View File

@ -17,14 +17,18 @@
package com.shabinder.spotiflyer package com.shabinder.spotiflyer
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
class SharedViewModel : ViewModel() { class SharedViewModel @ViewModelInject constructor(
val youtubeMusicApi: YoutubeMusicApi
) : ViewModel() {
var intentString = MutableLiveData<String>() var intentString = MutableLiveData<String>()
var spotifyService = MutableLiveData<SpotifyService>() var spotifyService = MutableLiveData<SpotifyService>()

View File

@ -17,6 +17,7 @@
package com.shabinder.spotiflyer.recyclerView package com.shabinder.spotiflyer.recyclerView
import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -46,6 +47,7 @@ class TrackListAdapter(private val viewModel :TrackListViewModel): ListAdapter<T
return ViewHolder(binding) return ViewHolder(binding)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
if(itemCount == 1){ holder.binding.imageUrl.visibility = View.GONE}else{ if(itemCount == 1){ holder.binding.imageUrl.visibility = View.GONE}else{
@ -100,8 +102,8 @@ class TrackListAdapter(private val viewModel :TrackListViewModel): ListAdapter<T
} }
} }
holder.binding.trackName.text = "${if(item.title.length > 17){"${item.title.subSequence(0,16)}..."}else{item.title}}" holder.binding.trackName.text = if(item.title.length > 20){"${item.title.subSequence(0,18)}..."}else{item.title}
holder.binding.artist.text = "${item.artists.get(0)}..." holder.binding.artist.text = "${item.artists.firstOrNull()}..."
holder.binding.duration.text = "${item.durationSec/60} minutes, ${item.durationSec%60} sec" holder.binding.duration.text = "${item.durationSec/60} minutes, ${item.durationSec%60} sec"
} }

View File

@ -24,25 +24,18 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.spotify.Source 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.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class GaanaFragment : TrackListFragment<GaanaViewModel,GaanaFragmentArgs>() { class GaanaFragment : TrackListFragment<GaanaViewModel,GaanaFragmentArgs>() {
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
@Inject lateinit var gaanaInterface: GaanaInterface
override lateinit var viewModel: GaanaViewModel override lateinit var viewModel: GaanaViewModel
override lateinit var adapter: TrackListAdapter override lateinit var adapter: TrackListAdapter
override var source: Source = Source.Gaana override var source: Source = Source.Gaana
@ -53,8 +46,8 @@ class GaanaFragment : TrackListFragment<GaanaViewModel,GaanaFragmentArgs>() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
viewModel = ViewModelProvider(this).get(GaanaViewModel::class.java)
initializeAll() adapter = TrackListAdapter(viewModel)
val gaanaLink = GaanaFragmentArgs.fromBundle(requireArguments()).link.substringAfter("gaana.com/") val gaanaLink = GaanaFragmentArgs.fromBundle(requireArguments()).link.substringAfter("gaana.com/")
//Link Schema: https://gaana.com/type/link //Link Schema: https://gaana.com/type/link
@ -112,21 +105,4 @@ class GaanaFragment : TrackListFragment<GaanaViewModel,GaanaFragmentArgs>() {
} }
return binding.root 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
}
} }

View File

@ -18,13 +18,12 @@
package com.shabinder.spotiflyer.ui.gaana package com.shabinder.spotiflyer.ui.gaana
import android.os.Environment import android.os.Environment
import android.util.Log
import androidx.hilt.lifecycle.ViewModelInject import androidx.hilt.lifecycle.ViewModelInject
import com.shabinder.spotiflyer.database.DatabaseDAO import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.gaana.* import com.shabinder.spotiflyer.models.gaana.GaanaTrack
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.utils.Provider import com.shabinder.spotiflyer.utils.Provider
@ -35,18 +34,20 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : TrackListViewModel(){ class GaanaViewModel @ViewModelInject constructor(
val databaseDAO: DatabaseDAO,
val gaanaInterface : GaanaInterface
) : TrackListViewModel(){
override var folderType:String = "" override var folderType:String = ""
override var subFolder:String = "" override var subFolder:String = ""
var gaanaInterface : GaanaInterface? = null private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
fun gaanaSearch(type:String,link:String){ fun gaanaSearch(type:String,link:String){
when(type){ when(type){
"song" -> { "song" -> {
uiScope.launch { uiScope.launch {
getGaanaSong(link)?.tracks?.firstOrNull()?.also { gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
folderType = "Tracks" folderType = "Tracks"
if(File(finalOutputDir(it.track_title,folderType,subFolder)).exists()){//Download Already Present!! if(File(finalOutputDir(it.track_title,folderType,subFolder)).exists()){//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded it.downloaded = DownloadStatus.Downloaded
@ -72,7 +73,7 @@ class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO)
} }
"album" -> { "album" -> {
uiScope.launch { uiScope.launch {
getGaanaAlbum(link)?.also { gaanaInterface.getGaanaAlbum(seokey = link).value?.also {
folderType = "Albums" folderType = "Albums"
subFolder = link subFolder = link
it.tracks.forEach { track -> it.tracks.forEach { track ->
@ -99,7 +100,7 @@ class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO)
} }
"playlist" -> { "playlist" -> {
uiScope.launch { uiScope.launch {
getGaanaPlaylist(link)?.also { gaanaInterface.getGaanaPlaylist(seokey = link).value?.also {
folderType = "Playlists" folderType = "Playlists"
subFolder = link subFolder = link
it.tracks.forEach {track -> it.tracks.forEach {track ->
@ -129,11 +130,11 @@ class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO)
uiScope.launch { uiScope.launch {
folderType = "Artist" folderType = "Artist"
subFolder = link subFolder = link
val artistDetails = getGaanaArtistDetails(link)?.artist?.firstOrNull()?.also { val artistDetails = gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull()?.also {
title.value = it.name title.value = it.name
coverUrl.value = it.artworkLink coverUrl.value = it.artworkLink
} }
getGaanaArtistTracks(link)?.also { gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also {
it.tracks.forEach {track -> it.tracks.forEach {track ->
if(File(finalOutputDir(track.track_title,folderType,subFolder)).exists()){//Download Already Present!! if(File(finalOutputDir(track.track_title,folderType,subFolder)).exists()){//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded track.downloaded = DownloadStatus.Downloaded
@ -175,25 +176,4 @@ class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO)
albumArtURL = it.artworkLink albumArtURL = it.artworkLink
) )
}.toMutableList() }.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

@ -25,23 +25,19 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.mainActivity import com.shabinder.spotiflyer.utils.Provider.mainActivity
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class SpotifyFragment : TrackListFragment<SpotifyViewModel,SpotifyFragmentArgs>() { class SpotifyFragment : TrackListFragment<SpotifyViewModel,SpotifyFragmentArgs>() {
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
override lateinit var viewModel: SpotifyViewModel override lateinit var viewModel: SpotifyViewModel
override lateinit var adapter: TrackListAdapter override lateinit var adapter: TrackListAdapter
override var source: Source = Source.Spotify override var source: Source = Source.Spotify
@ -132,10 +128,5 @@ class SpotifyFragment : TrackListFragment<SpotifyViewModel,SpotifyFragmentArgs>(
sharedViewModel.spotifyService.observe(viewLifecycleOwner, { sharedViewModel.spotifyService.observe(viewLifecycleOwner, {
this.viewModel.spotifyService = it this.viewModel.spotifyService = it
}) })
DownloadHelper.youtubeMusicApi = youtubeMusicApi
DownloadHelper.sharedViewModel = sharedViewModel
DownloadHelper.statusBar = binding.statusBar
binding.trackList.adapter = adapter
(binding.trackList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
} }
} }

View File

@ -24,7 +24,10 @@ import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.* import com.shabinder.spotiflyer.models.spotify.Album
import com.shabinder.spotiflyer.models.spotify.Image
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.models.spotify.Track
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.utils.Provider import com.shabinder.spotiflyer.utils.Provider
import com.shabinder.spotiflyer.utils.TrackListViewModel import com.shabinder.spotiflyer.utils.TrackListViewModel
@ -34,7 +37,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : TrackListViewModel(){ class SpotifyViewModel @ViewModelInject constructor(
val databaseDAO: DatabaseDAO,
) : TrackListViewModel(){
override var folderType:String = "" override var folderType:String = ""
override var subFolder:String = "" override var subFolder:String = ""
@ -42,70 +47,105 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
var spotifyService : SpotifyService? = null var spotifyService : SpotifyService? = null
fun spotifySearch(type:String,link: String){ fun spotifySearch(type:String,link: String){
uiScope.launch {
when (type) { when (type) {
"track" -> { "track" -> {
uiScope.launch { spotifyService?.getTrack(link)?.value?.also {
getTrackDetails(link)?.also {
folderType = "Tracks" folderType = "Tracks"
if(File(finalOutputDir(it.name,folderType,subFolder)).exists()){//Download Already Present!! if (File(
finalOutputDir(
it.name,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded it.downloaded = DownloadStatus.Downloaded
} }
trackList.value = listOf(it).toTrackDetailsList() trackList.value = listOf(it).toTrackDetailsList()
title.value = it.name title.value = it.name
coverUrl.value = it.album!!.images?.elementAtOrNull(1)?.url ?: it.album!!.images?.elementAtOrNull(0)?.url coverUrl.value = it.album!!.images?.elementAtOrNull(1)?.url
withContext(Dispatchers.IO){ ?: it.album!!.images?.elementAtOrNull(0)?.url
databaseDAO.insert(DownloadRecord( withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Track", type = "Track",
name = title.value!!, name = title.value!!,
link = "https://open.spotify.com/$type/$link", link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value!!, coverUrl = coverUrl.value!!,
totalFiles = 1, totalFiles = 1,
downloaded = it.downloaded == DownloadStatus.Downloaded, downloaded = it.downloaded == DownloadStatus.Downloaded,
directory = finalOutputDir(it.name,folderType,subFolder) directory = finalOutputDir(it.name, folderType, subFolder)
)) )
} )
} }
} }
} }
"album" -> { "album" -> {
uiScope.launch { val albumObject = spotifyService?.getAlbum(link)?.value
val albumObject = getAlbumDetails(link)
folderType = "Albums" folderType = "Albums"
subFolder = albumObject?.name.toString() subFolder = albumObject?.name.toString()
albumObject?.tracks?.items?.forEach { albumObject?.tracks?.items?.forEach {
if(File(finalOutputDir(it.name!!,folderType,subFolder)).exists()){//Download Already Present!! if (File(
finalOutputDir(
it.name!!,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded it.downloaded = DownloadStatus.Downloaded
} }
it.album = Album(images = listOf(Image(url = albumObject.images?.elementAtOrNull(1)?.url ?: albumObject.images?.elementAtOrNull(0)?.url ))) it.album = Album(
images = listOf(
Image(
url = albumObject.images?.elementAtOrNull(1)?.url
?: albumObject.images?.elementAtOrNull(0)?.url
)
)
)
} }
trackList.value = albumObject?.tracks?.items?.toTrackDetailsList() trackList.value = albumObject?.tracks?.items?.toTrackDetailsList()
title.value = albumObject?.name title.value = albumObject?.name
coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url ?: albumObject?.images?.elementAtOrNull(0)?.url coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url
withContext(Dispatchers.IO){ ?: albumObject?.images?.elementAtOrNull(0)?.url
databaseDAO.insert(DownloadRecord( withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Album", type = "Album",
name = title.value!!, name = title.value!!,
link = "https://open.spotify.com/$type/$link", link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value.toString(), coverUrl = coverUrl.value.toString(),
totalFiles = trackList.value?.size ?: 0, totalFiles = trackList.value?.size ?: 0,
downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == trackList.value?.size, downloaded = File(
directory = finalOutputDir(type = folderType,subFolder = subFolder) finalOutputDir(
)) type = folderType,
} subFolder = subFolder
)
).listFiles()?.size == trackList.value?.size,
directory = finalOutputDir(type = folderType, subFolder = subFolder)
)
)
} }
} }
"playlist" -> { "playlist" -> {
uiScope.launch { val playlistObject = spotifyService?.getPlaylist(link)?.value
val playlistObject = getPlaylistDetails(link)
folderType = "Playlists" folderType = "Playlists"
subFolder = playlistObject?.name.toString() subFolder = playlistObject?.name.toString()
val tempTrackList = mutableListOf<Track>() val tempTrackList = mutableListOf<Track>()
Log.i("Tracks Fetched",playlistObject?.tracks?.items?.size.toString()) Log.i("Tracks Fetched", playlistObject?.tracks?.items?.size.toString())
playlistObject?.tracks?.items?.forEach { playlistObject?.tracks?.items?.forEach {
it.track?.let { it.track?.let { it1 ->
it1 -> if(File(finalOutputDir(it1.name!!,folderType,subFolder)).exists()){//Download Already Present!! if (File(
finalOutputDir(
it1.name!!,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it1.downloaded = DownloadStatus.Downloaded it1.downloaded = DownloadStatus.Downloaded
} }
tempTrackList.add(it1) tempTrackList.add(it1)
@ -113,29 +153,36 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
} }
var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank() var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank()
while(moreTracksAvailable){ while (moreTracksAvailable) {
//Check For More Tracks If available //Check For More Tracks If available
val moreTracks = getPlaylistTrackDetails(link,offset = tempTrackList.size) val moreTracks = spotifyService?.getPlaylistTracks(link, offset = tempTrackList.size)?.value
moreTracks?.items?.forEach{ moreTracks?.items?.forEach {
it.track?.let { it1 -> tempTrackList.add(it1) } it.track?.let { it1 -> tempTrackList.add(it1) }
} }
moreTracksAvailable = !moreTracks?.next.isNullOrBlank() moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
} }
Log.i("Total Tracks Fetched",tempTrackList.size.toString()) Log.i("Total Tracks Fetched", tempTrackList.size.toString())
trackList.value = tempTrackList.toTrackDetailsList() trackList.value = tempTrackList.toTrackDetailsList()
title.value = playlistObject?.name title.value = playlistObject?.name
coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url ?: playlistObject?.images?.firstOrNull()?.url.toString() coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url
withContext(Dispatchers.IO){ ?: playlistObject?.images?.firstOrNull()?.url.toString()
databaseDAO.insert(DownloadRecord( withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Playlist", type = "Playlist",
name = title.value.toString(), name = title.value.toString(),
link = "https://open.spotify.com/$type/$link", link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value.toString(), coverUrl = coverUrl.value.toString(),
totalFiles = tempTrackList.size, totalFiles = tempTrackList.size,
downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == tempTrackList.size, downloaded = File(
directory = finalOutputDir(type = folderType,subFolder = subFolder) finalOutputDir(
)) type = folderType,
} subFolder = subFolder
)
).listFiles()?.size == tempTrackList.size,
directory = finalOutputDir(type = folderType, subFolder = subFolder)
)
)
} }
} }
"episode" -> {//TODO "episode" -> {//TODO
@ -144,6 +191,7 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
} }
} }
} }
}
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun List<Track>.toTrackDetailsList() = this.map { private fun List<Track>.toTrackDetailsList() = this.map {
@ -163,21 +211,4 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString() albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()
) )
}.toMutableList() }.toMutableList()
private suspend fun getTrackDetails(trackLink:String): Track?{
Log.i("Requesting","https://api.spotify.com/v1/tracks/$trackLink")
return spotifyService?.getTrack(trackLink)?.value
}
private suspend fun getAlbumDetails(albumLink:String): Album?{
Log.i("Requesting","https://api.spotify.com/v1/albums/$albumLink")
return spotifyService?.getAlbum(albumLink)?.value
}
private suspend fun getPlaylistDetails(link:String): Playlist?{
Log.i("Requesting","https://api.spotify.com/v1/playlists/$link")
return spotifyService?.getPlaylist(link)?.value
}
private suspend fun getPlaylistTrackDetails(link:String,offset:Int = 0,limit:Int = 100): PagingObjectPlaylistTrack?{
Log.i("Requesting","https://api.spotify.com/v1/playlists/$link/tracks?offset=$offset&limit=$limit")
return spotifyService?.getPlaylistTracks(link, offset, limit)?.value
}
} }

View File

@ -23,7 +23,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
@ -32,7 +31,6 @@ import com.shabinder.spotiflyer.utils.*
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
private const val sampleDomain2 = "youtu.be" private const val sampleDomain2 = "youtu.be"
private const val sampleDomain1 = "youtube.com" private const val sampleDomain1 = "youtube.com"
@ -40,7 +38,6 @@ private const val sampleDomain1 = "youtube.com"
@AndroidEntryPoint @AndroidEntryPoint
class YoutubeFragment : TrackListFragment<YoutubeViewModel,YoutubeFragmentArgs>() { class YoutubeFragment : TrackListFragment<YoutubeViewModel,YoutubeFragmentArgs>() {
@Inject lateinit var ytDownloader: YoutubeDownloader
override lateinit var viewModel: YoutubeViewModel override lateinit var viewModel: YoutubeViewModel
override lateinit var adapter : TrackListAdapter override lateinit var adapter : TrackListAdapter
override var source: Source = Source.YouTube override var source: Source = Source.YouTube
@ -53,7 +50,6 @@ class YoutubeFragment : TrackListFragment<YoutubeViewModel,YoutubeFragmentArgs>(
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
this.viewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java) this.viewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
adapter = TrackListAdapter(this.viewModel) adapter = TrackListAdapter(this.viewModel)
binding.trackList.adapter = adapter
val args = YoutubeFragmentArgs.fromBundle(requireArguments()) val args = YoutubeFragmentArgs.fromBundle(requireArguments())
val link = args.link val link = args.link
@ -66,7 +62,7 @@ class YoutubeFragment : TrackListFragment<YoutubeViewModel,YoutubeFragmentArgs>(
if(link.contains("playlist",true) || link.contains("list",true)){ if(link.contains("playlist",true) || link.contains("list",true)){
// Given Link is of a Playlist // Given Link is of a Playlist
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&") val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
this.viewModel.getYTPlaylist(playlistId,ytDownloader) this.viewModel.getYTPlaylist(playlistId)
}else{//Given Link is of a Video }else{//Given Link is of a Video
var searchId = "error" var searchId = "error"
if(link.contains(sampleDomain1,true) ){ if(link.contains(sampleDomain1,true) ){
@ -76,7 +72,7 @@ class YoutubeFragment : TrackListFragment<YoutubeViewModel,YoutubeFragmentArgs>(
searchId = link.substringAfterLast("/","error") searchId = link.substringAfterLast("/","error")
} }
if(searchId != "error") { if(searchId != "error") {
this.viewModel.getYTTrack(searchId,ytDownloader) this.viewModel.getYTTrack(searchId)
}else{showMessage("Your Youtube Link is not of a Video!!")} }else{showMessage("Your Youtube Link is not of a Video!!")}
} }

View File

@ -34,7 +34,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : TrackListViewModel(){ class YoutubeViewModel @ViewModelInject constructor(
val databaseDAO: DatabaseDAO,
val ytDownloader: YoutubeDownloader
) : TrackListViewModel(){
/* /*
* YT Album Art Schema * YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg" * HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
@ -44,7 +47,7 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
override var folderType = "YT_Downloads" override var folderType = "YT_Downloads"
override var subFolder = "" override var subFolder = ""
fun getYTPlaylist(searchId:String, ytDownloader:YoutubeDownloader){ fun getYTPlaylist(searchId:String){
if(!isOnline())return if(!isOnline())return
try{ try{
uiScope.launch(Dispatchers.IO) { uiScope.launch(Dispatchers.IO) {
@ -102,7 +105,7 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
} }
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
fun getYTTrack(searchId:String, ytDownloader:YoutubeDownloader) { fun getYTTrack(searchId:String) {
if(!isOnline())return if(!isOnline())return
try{ try{
uiScope.launch(Dispatchers.IO) { uiScope.launch(Dispatchers.IO) {

View File

@ -17,9 +17,9 @@
package com.shabinder.spotiflyer.utils package com.shabinder.spotiflyer.utils
import android.util.Log
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Protocol import okhttp3.Protocol
import okhttp3.RequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
@ -27,13 +27,14 @@ const val NoInternetErrorCode = 222
class NetworkInterceptor: Interceptor { class NetworkInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
Log.i("Network Requesting",chain.request().url.toString())
return if (!isOnline()){ return if (!isOnline()){
//No Internet Connection //No Internet Connection
showNoConnectionAlert() showNoConnectionAlert()
//Lets Stop the Incoming Request //Lets Stop the Incoming Request
Response.Builder() Response.Builder()
.code(NoInternetErrorCode) // code(200.300) = successful else = unsuccessful .code(NoInternetErrorCode) // code(200.300) = successful else = unsuccessful
.body("{}".toResponseBody(null)) // Whatever body .body("{}".toResponseBody(null)) // Empty Object
.protocol(Protocol.HTTP_2) .protocol(Protocol.HTTP_2)
.message("No Internet Connection") .message("No Internet Connection")
.request(chain.request()) .request(chain.request())
@ -52,15 +53,7 @@ class NetworkInterceptor: Interceptor {
.message(response.message) .message(response.message)
.request(chain.request()) .request(chain.request())
.build() .build()
// chain.proceed(chain.request())
} }
} }
/*
* Converts REQUEST's Body to String
* */
private fun RequestBody?.bodyToString(): String {
if (this == null) return ""
val buffer = okio.Buffer()
writeTo(buffer)
return buffer.readUtf8()
}
} }

View File

@ -49,6 +49,11 @@ import javax.inject.Singleton
@Module @Module
object Provider { object Provider {
/*
* mainActivity Instance to use whereEver Needed , as Its God Activity.
* (i.e, Active Through out App' Lifecycle )
* */
val mainActivity: MainActivity = MainActivity.getInstance() val mainActivity: MainActivity = MainActivity.getInstance()
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator

View File

@ -29,9 +29,11 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavArgs import androidx.navigation.NavArgs
import androidx.recyclerview.widget.SimpleItemAnimator
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
@ -53,7 +55,7 @@ abstract class TrackListFragment<VM : TrackListViewModel , args: NavArgs> : Frag
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if(!isOnline()){ if(!isOnline()){
showNoConnectionAlert() showNoConnectionAlert()
mainActivity.onBackPressed() mainActivity.navController.popBackStack()
} }
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
} }
@ -64,11 +66,20 @@ abstract class TrackListFragment<VM : TrackListViewModel , args: NavArgs> : Frag
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = TrackListFragmentBinding.inflate(inflater,container,false) binding = TrackListFragmentBinding.inflate(inflater,container,false)
initializeAll()
return binding.root return binding.root
} }
private fun initializeAll() {
DownloadHelper.youtubeMusicApi = sharedViewModel.youtubeMusicApi
DownloadHelper.sharedViewModel = sharedViewModel
DownloadHelper.statusBar = binding.statusBar
(binding.trackList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.trackList.adapter = adapter
initializeLiveDataObservers() initializeLiveDataObservers()
} }
@ -130,7 +141,6 @@ abstract class TrackListFragment<VM : TrackListViewModel , args: NavArgs> : Frag
super.onPause() super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver) requireActivity().unregisterReceiver(updateUIReceiver)
} }
private fun checkIfAllDownloaded() { private fun checkIfAllDownloaded() {
if(!viewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){ if(!viewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
//All Tracks Downloaded //All Tracks Downloaded
@ -142,5 +152,5 @@ abstract class TrackListFragment<VM : TrackListViewModel , args: NavArgs> : Frag
} }
} }
} }
open fun applicationContext(): Context = requireActivity().applicationContext
} }

View File

@ -116,19 +116,19 @@
app:layout_constraintBottom_toTopOf="@+id/cover_image" app:layout_constraintBottom_toTopOf="@+id/cover_image"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<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_marginBottom="24dp"
android:layout_marginBottom="22dp"
android:background="#00000000" android:background="#00000000"
android:fontFamily="@font/raleway_semibold" android:fontFamily="@font/raleway_semibold"
android:gravity="end" android:gravity="end"
android:text='"Loading..."' android:text='"Loading..."'
android:textAlignment="viewEnd" android:textAlignment="viewEnd"
android:textColor="#9AB3FF" android:textColor="#9AB3FF"
android:textSize="28sp" android:textSize="26sp"
android:textStyle="bold" android:textStyle="bold"
android:visibility="visible" android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -48,7 +48,7 @@
android:textAllCaps="false" android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4" android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textColor="#9AB3FF" android:textColor="#9AB3FF"
android:textSize="20sp" android:textSize="18sp"
app:layout_constraintEnd_toStartOf="@+id/btn_download" app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintStart_toStartOf="@+id/artist" app:layout_constraintStart_toStartOf="@+id/artist"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />