Unifying All Things For Better Maintenance and Less Trouble.

This commit is contained in:
Shabinder 2020-11-09 14:11:22 +05:30
parent cac306e9e8
commit 180a284a54
23 changed files with 403 additions and 378 deletions

View File

@ -25,7 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
class SharedViewModel : ViewModel() { class SharedViewModel : ViewModel() {
var intentString = MutableLiveData<String>().apply { value = "" } var intentString = MutableLiveData<String>()
var spotifyService = MutableLiveData<SpotifyService>() var spotifyService = MutableLiveData<SpotifyService>()
private var viewModelJob = Job() private var viewModelJob = Job()

View File

@ -33,8 +33,8 @@ import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.networking.YoutubeMusicApi import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.networking.makeJsonBody import com.shabinder.spotiflyer.networking.makeJsonBody
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.activity
import com.shabinder.spotiflyer.utils.Provider.defaultDir import com.shabinder.spotiflyer.utils.Provider.defaultDir
import com.shabinder.spotiflyer.utils.Provider.mainActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -76,9 +76,9 @@ object DownloadHelper {
//Delay is Added ,if a request is in processing it may finish //Delay is Added ,if a request is in processing it may finish
Log.i("Spotify Helper","Download Request Sent") Log.i("Spotify Helper","Download Request Sent")
sharedViewModel?.uiScope?.launch (Dispatchers.Main){ sharedViewModel?.uiScope?.launch (Dispatchers.Main){
Toast.makeText(activity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show() Toast.makeText(mainActivity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show()
} }
startService(activity,downloadList) startService(mainActivity,downloadList)
},5000) },5000)
} }
}else{ }else{
@ -121,9 +121,9 @@ object DownloadHelper {
//Delay is Added ,if a request is in processing it may finish //Delay is Added ,if a request is in processing it may finish
Log.i("Spotify Helper","Download Request Sent") Log.i("Spotify Helper","Download Request Sent")
sharedViewModel?.uiScope?.launch (Dispatchers.Main){ sharedViewModel?.uiScope?.launch (Dispatchers.Main){
Toast.makeText(activity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show() Toast.makeText(mainActivity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show()
} }
startService(activity,downloadList) startService(mainActivity,downloadList)
},5000) },5000)
} }
} }

View File

@ -22,8 +22,8 @@ import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.shabinder.spotiflyer.models.DownloadObject import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.utils.Provider.activity
import com.shabinder.spotiflyer.utils.Provider.defaultDir import com.shabinder.spotiflyer.utils.Provider.defaultDir
import com.shabinder.spotiflyer.utils.Provider.mainActivity
import com.shabinder.spotiflyer.utils.isOnline import com.shabinder.spotiflyer.utils.isOnline
import com.shabinder.spotiflyer.utils.removeIllegalChars import com.shabinder.spotiflyer.utils.removeIllegalChars
import com.shabinder.spotiflyer.utils.showNoConnectionAlert import com.shabinder.spotiflyer.utils.showNoConnectionAlert
@ -63,8 +63,8 @@ object YTDownloadHelper {
} }
Log.i("YT Downloader Helper","Download Request Sent") Log.i("YT Downloader Helper","Download Request Sent")
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
Toast.makeText(activity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show() Toast.makeText(mainActivity,"Download Started, Now You can leave the App!", Toast.LENGTH_SHORT).show()
startService(activity,downloadList) startService(mainActivity,downloadList)
} }
} }
} }

View File

@ -40,6 +40,7 @@ data class TrackDetails(
var lyrics:String?=null, var lyrics:String?=null,
var trackUrl:String?=null, var trackUrl:String?=null,
var albumArt: File, var albumArt: File,
var albumArtURL: String,
var source: Source, var source: Source,
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
):Parcelable ):Parcelable

View File

@ -18,7 +18,7 @@
package com.shabinder.spotiflyer.models.gaana package com.shabinder.spotiflyer.models.gaana
data class GaanaAlbum ( data class GaanaAlbum (
val tracks : List<Tracks>, val tracks : List<GaanaTrack>,
val count : Int, val count : Int,
val custom_artworks : CustomArtworks, val custom_artworks : CustomArtworks,
val release_year : Int, val release_year : Int,

View File

@ -19,5 +19,5 @@ package com.shabinder.spotiflyer.models.gaana
data class GaanaArtistTracks( data class GaanaArtistTracks(
val count : Int, val count : Int,
val tracks : List<Tracks> val tracks : List<GaanaTrack>
) )

View File

@ -24,5 +24,5 @@ data class GaanaPlaylist (
val count : Int, val count : Int,
val created_on : String, val created_on : String,
val favorite_count : Int, val favorite_count : Int,
val tracks : List<Tracks>, val tracks : List<GaanaTrack>,
) )

View File

@ -18,5 +18,5 @@
package com.shabinder.spotiflyer.models.gaana package com.shabinder.spotiflyer.models.gaana
data class GaanaSong( data class GaanaSong(
val tracks : List<Tracks> val tracks : List<GaanaTrack>
) )

View File

@ -17,9 +17,10 @@
package com.shabinder.spotiflyer.models.gaana package com.shabinder.spotiflyer.models.gaana
import com.shabinder.spotiflyer.models.DownloadStatus
import com.squareup.moshi.Json import com.squareup.moshi.Json
data class Tracks ( data class GaanaTrack (
val tags : List<Tags>, val tags : List<Tags>,
val seokey : String, val seokey : String,
val albumseokey : String, val albumseokey : String,
@ -35,4 +36,5 @@ data class Tracks (
val release_date : String, val release_date : String,
val play_ct : String, val play_ct : String,
val secondary_language : String, val secondary_language : String,
var downloaded: DownloadStatus? = DownloadStatus.NotDownloaded
) )

View File

@ -40,6 +40,6 @@ data class Track(
var album: Album? = null, var album: Album? = null,
var external_ids: Map<String?, String?>? = null, var external_ids: Map<String?, String?>? = null,
var popularity: Int? = null, var popularity: Int? = null,
var downloaded: DownloadStatus? = DownloadStatus.NotDownloaded var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
):Parcelable ):Parcelable

View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.recyclerView
import android.annotation.SuppressLint
import android.os.Environment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.databinding.TrackListItemBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.downloadAllTracks
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.models.spotify.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyViewModel
import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.activity
import kotlinx.coroutines.launch
import java.io.File
class SpotifyTrackListAdapter(private val spotifyViewModel : SpotifyViewModel): ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(SpotifyTrackDiffCallback()) {
var isAlbum:Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = TrackListItemBinding.inflate(layoutInflater,parent,false)
return ViewHolder(binding)
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
if(itemCount ==1 || isAlbum){
holder.binding.imageUrl.visibility = View.GONE}else{
spotifyViewModel.uiScope.launch {
//Placeholder Set
bindImage(holder.binding.imageUrl, item.album?.images?.get(0)?.url, Source.Spotify)
}
}
holder.binding.trackName.text = "${if(item.name!!.length > 17){"${item.name!!.subSequence(0,16)}..."}else{item.name}}"
holder.binding.artist.text = "${item.artists?.get(0)?.name?:""}..."
holder.binding.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
when (item.downloaded) {
DownloadStatus.Downloaded -> {
holder.binding.btnDownload.setImageResource(R.drawable.ic_tick)
holder.binding.btnDownload.clearAnimation()
}
DownloadStatus.Downloading -> {
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
rotateAnim(holder.binding.btnDownload)
}
DownloadStatus.NotDownloaded -> {
holder.binding.btnDownload.setImageResource(R.drawable.ic_arrow)
holder.binding.btnDownload.clearAnimation()
holder.binding.btnDownload.setOnClickListener{
if(!isOnline()){
showNoConnectionAlert()
return@setOnClickListener
}
Toast.makeText(activity,"Processing!",Toast.LENGTH_SHORT).show()
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
rotateAnim(it)
item.downloaded = DownloadStatus.Downloading
spotifyViewModel.uiScope.launch {
val itemList = mutableListOf<TrackDetails>()
itemList.add(item.let { track ->
val artistsList = mutableListOf<String>()
track.artists?.forEach { artist -> artistsList.add(artist!!.name!!) }
TrackDetails(
title = track.name.toString(),
artists = artistsList,
durationSec = (track.duration_ms/1000).toInt(),
albumArt = File(
Environment.getExternalStorageDirectory(),
Provider.defaultDir +".Images/" + (track.album?.images?.get(0)?.url.toString()).substringAfterLast('/') + ".jpeg"),
albumName = track.album?.name,
year = track.album?.release_date,
comment = "Genres:${track.album?.genres?.joinToString()}",
trackUrl = track.href,
source = Source.Spotify
)
}
)
downloadAllTracks(spotifyViewModel.folderType,spotifyViewModel.subFolder,itemList)
}
notifyItemChanged(position)//start showing anim!
}
}
}
}
class ViewHolder(val binding: TrackListItemBinding) : RecyclerView.ViewHolder(binding.root)
}
class SpotifyTrackDiffCallback: DiffUtil.ItemCallback<Track>(){
override fun areItemsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem == newItem //Downloaded Check
}
}

View File

@ -22,39 +22,35 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.databinding.TrackListItemBinding import com.shabinder.spotiflyer.databinding.TrackListItemBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
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.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.ui.youtube.YoutubeViewModel
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class YoutubeTrackListAdapter(private val youtubeViewModel :YoutubeViewModel): ListAdapter<TrackDetails,SpotifyTrackListAdapter.ViewHolder>(YouTubeTrackDiffCallback()) { class TrackListAdapter(private val viewModel :BaseViewModel): ListAdapter<TrackDetails, TrackListAdapter.ViewHolder>(TrackDiffCallback()) {
var source:Source =Source.Spotify
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
): SpotifyTrackListAdapter.ViewHolder { ): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
val binding = TrackListItemBinding.inflate(layoutInflater,parent,false) val binding = TrackListItemBinding.inflate(layoutInflater,parent,false)
// val view = layoutInflater.inflate(R.layout.track_list_item,parent,false) return ViewHolder(binding)
return SpotifyTrackListAdapter.ViewHolder(binding)
} }
override fun onBindViewHolder(holder: SpotifyTrackListAdapter.ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
if(itemCount == 1){ if(itemCount == 1){ holder.binding.imageUrl.visibility = View.GONE}else{
holder.binding.imageUrl.visibility = View.GONE}else{ viewModel.uiScope.launch {
youtubeViewModel.uiScope.launch { bindImage(holder.binding.imageUrl,item.albumArtURL, source)
bindImage(holder.binding.imageUrl,
"https://i.ytimg.com/vi/${item.albumArt.absolutePath.substringAfterLast("/")
.substringBeforeLast(".")}/hqdefault.jpg"
,
Source.YouTube
)
} }
} }
@ -79,15 +75,26 @@ class YoutubeTrackListAdapter(private val youtubeViewModel :YoutubeViewModel): L
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh) holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
rotateAnim(it) rotateAnim(it)
item.downloaded = DownloadStatus.Downloading item.downloaded = DownloadStatus.Downloading
youtubeViewModel.uiScope.launch { when(source){
val itemList = mutableListOf<TrackDetails>() Source.Spotify -> {
itemList.add(item) viewModel.uiScope.launch {
YTDownloadHelper.downloadYTTracks( DownloadHelper.downloadAllTracks(
youtubeViewModel.folderType, viewModel.folderType,
youtubeViewModel.subFolder, viewModel.subFolder,
itemList listOf(item)
) )
} }
}
Source.YouTube -> {
viewModel.uiScope.launch {
YTDownloadHelper.downloadYTTracks(
viewModel.folderType,
viewModel.subFolder,
listOf(item)
)
}
}
}
notifyItemChanged(position)//start showing anim! notifyItemChanged(position)//start showing anim!
} }
} }
@ -97,8 +104,15 @@ class YoutubeTrackListAdapter(private val youtubeViewModel :YoutubeViewModel): L
holder.binding.artist.text = "${item.artists.get(0)}..." holder.binding.artist.text = "${item.artists.get(0)}..."
holder.binding.duration.text = "${item.durationSec/60} minutes, ${item.durationSec%60} sec" holder.binding.duration.text = "${item.durationSec/60} minutes, ${item.durationSec%60} sec"
} }
class ViewHolder(val binding: TrackListItemBinding) : RecyclerView.ViewHolder(binding.root)
fun submitList(list: MutableList<TrackDetails>?, source: Source) {
super.submitList(list)
this.source = source
} }
class YouTubeTrackDiffCallback: DiffUtil.ItemCallback<TrackDetails>(){ }
class TrackDiffCallback: DiffUtil.ItemCallback<TrackDetails>(){
override fun areItemsTheSame(oldItem: TrackDetails, newItem: TrackDetails): Boolean { override fun areItemsTheSame(oldItem: TrackDetails, newItem: TrackDetails): Boolean {
return oldItem.title == newItem.title return oldItem.title == newItem.title
} }

View File

@ -20,6 +20,7 @@ package com.shabinder.spotiflyer.ui.gaana
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -29,8 +30,14 @@ import androidx.lifecycle.ViewModelProvider
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.networking.GaanaInterface import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.YoutubeMusicApi import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class GaanaFragment : Fragment() { class GaanaFragment : Fragment() {
@ -39,6 +46,7 @@ class GaanaFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi @Inject lateinit var youtubeMusicApi: YoutubeMusicApi
private lateinit var viewModel: GaanaViewModel private lateinit var viewModel: GaanaViewModel
private lateinit var adapter: TrackListAdapter
@Inject lateinit var gaanaInterface: GaanaInterface @Inject lateinit var gaanaInterface: GaanaInterface
private var intentFilter: IntentFilter? = null private var intentFilter: IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null private var updateUIReceiver: BroadcastReceiver? = null
@ -49,6 +57,63 @@ class GaanaFragment : Fragment() {
): View? { ): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment, container, false) binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment, container, false)
viewModel = ViewModelProvider(this).get(GaanaViewModel::class.java) viewModel = ViewModelProvider(this).get(GaanaViewModel::class.java)
adapter = TrackListAdapter(viewModel)
val gaanaLink = GaanaFragmentArgs.fromBundle(requireArguments()).link.substringAfter("gaana.com/")
//Link Schema: https://gaana.com/type/link
val link = gaanaLink.substringAfterLast('/', "error")
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
Log.i("Gaana Fragment", "$type : $link")
when{
type == "Error" || link == "Error" -> {
showMessage("Please Check Your Link!")
Provider.mainActivity.onBackPressed()
}
else -> {
viewModel.gaanaSearch(type,link)
binding.btnDownloadAll.setOnClickListener {
if(!isOnline()){
showNoConnectionAlert()
return@setOnClickListener
}
binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.visibility = View.VISIBLE
rotateAnim(binding.downloadingFab)
for (track in viewModel.trackList.value!!){
if(track.downloaded != DownloadStatus.Downloaded){
track.downloaded = DownloadStatus.Downloading
adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
}
}
showMessage("Processing!")
sharedViewModel.uiScope.launch(Dispatchers.Default){
val urlList = arrayListOf<String>()
viewModel.trackList.value?.forEach { urlList.add(it.albumArtURL) }
//Appending Source
urlList.add("spotify")
loadAllImages(
requireActivity(),
urlList
)
}
viewModel.uiScope.launch {
val finalList = viewModel.trackList.value
if(finalList.isNullOrEmpty())showMessage("Not Downloading Any Song")
DownloadHelper.downloadAllTracks(
viewModel.folderType,
viewModel.subFolder,
finalList ?: listOf(),
)
}
}
}
}
return binding.root return binding.root
} }
} }

View File

@ -18,7 +18,18 @@
package com.shabinder.spotiflyer.ui.gaana package com.shabinder.spotiflyer.ui.gaana
import androidx.hilt.lifecycle.ViewModelInject import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.database.DatabaseDAO import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.utils.BaseViewModel
class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : ViewModel() class GaanaViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : BaseViewModel(){
override var folderType:String = ""
override var subFolder:String = ""
var gaanaInterface : GaanaInterface? = null
fun gaanaSearch(type:String,link:String){
}
}

View File

@ -65,19 +65,60 @@ class MainFragment : Fragment() {
return@setOnClickListener return@setOnClickListener
} }
val link = binding.linkSearch.text.toString() val link = binding.linkSearch.text.toString()
if (link.contains("spotify",true)){ when{
//SPOTIFY
link.contains("spotify",true) -> {
if(sharedViewModel.spotifyService.value == null){//Authentication pending!! if(sharedViewModel.spotifyService.value == null){//Authentication pending!!
(activity as MainActivity).authenticateSpotify() (activity as MainActivity).authenticateSpotify()
} }
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSpotifyFragment(link)) findNavController().navigate(MainFragmentDirections.actionMainFragmentToSpotifyFragment(link))
}else if(link.contains("youtube.com",true) || link.contains("youtu.be",true) ){ }
//YOUTUBE
link.contains("youtube.com",true) || link.contains("youtu.be",true) -> {
findNavController().navigate(MainFragmentDirections.actionMainFragmentToYoutubeFragment(link)) findNavController().navigate(MainFragmentDirections.actionMainFragmentToYoutubeFragment(link))
}else showMessage("Link is Not Valid",true) }
//GAANA
link.contains("gaana",true) -> {
findNavController().navigate(MainFragmentDirections.actionMainFragmentToGaanaFragment(link))
}
else -> showMessage("Link is Not Valid",true)
}
} }
handleIntent() handleIntent()
return binding.root return binding.root
} }
/**
* Handle Intent If there is any!
**/
private fun handleIntent() {
sharedViewModel.intentString.observe(viewLifecycleOwner,{ it?.let {
sharedViewModel.uiScope.launch(Dispatchers.IO) {
//Wait for any Authentication to Finish ,
// this Wait prevents from multiple Authentication Requests
Thread.sleep(1000)
if(sharedViewModel.spotifyService.value == null){
//Not Authenticated Yet
Provider.mainActivity.authenticateSpotify()
while (sharedViewModel.spotifyService.value == null) {
//Waiting for Authentication to Finish
Thread.sleep(1000)
}
}
withContext(Dispatchers.Main){
binding.linkSearch.setText(sharedViewModel.intentString.value)
binding.btnSearch.performClick()
//Intent Consumed
sharedViewModel.intentString.value = null
}
}
}
})
}
private fun initializeAll() { private fun initializeAll() {
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java) mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
@ -94,44 +135,14 @@ class MainFragment : Fragment() {
} }
} }
/**
* Implementing buttons
**/
private fun historyButton() { private fun historyButton() {
binding.btnHistory.setOnClickListener { binding.btnHistory.setOnClickListener {
findNavController().navigate(MainFragmentDirections.actionMainFragmentToDownloadRecord()) findNavController().navigate(MainFragmentDirections.actionMainFragmentToDownloadRecord())
} }
} }
/**
* Handle Intent If there is any!
**/
private fun handleIntent() {
sharedViewModel.intentString.observe(viewLifecycleOwner,{
if(it != ""){
sharedViewModel.uiScope.launch(Dispatchers.IO) {
//Wait for any Authentication to Finish ,
// this Wait prevents from multiple Authentication Requests
Thread.sleep(1000)
if(sharedViewModel.spotifyService.value == null){
//Not Authenticated Yet
Provider.activity.authenticateSpotify()
while (sharedViewModel.spotifyService.value == null) {
//Waiting for Authentication to Finish
Thread.sleep(1000)
}
}
withContext(Dispatchers.Main){
binding.linkSearch.setText(sharedViewModel.intentString.value)
binding.btnSearch.performClick()
sharedViewModel.intentString.value = ""
}
}
}
})
}
/**
* Implementing buttons
**/
private fun openSpotifyButton() { private fun openSpotifyButton() {
val manager: PackageManager = requireActivity().packageManager val manager: PackageManager = requireActivity().packageManager
try { try {

View File

@ -23,7 +23,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -41,13 +40,12 @@ 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
import com.shabinder.spotiflyer.networking.YoutubeMusicApi import com.shabinder.spotiflyer.networking.YoutubeMusicApi
import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.defaultDir 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 java.io.File
import javax.inject.Inject import javax.inject.Inject
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -58,7 +56,7 @@ class SpotifyFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi @Inject lateinit var youtubeMusicApi: YoutubeMusicApi
private lateinit var viewModel: SpotifyViewModel private lateinit var viewModel: SpotifyViewModel
private lateinit var adapter:SpotifyTrackListAdapter private lateinit var adapter:TrackListAdapter
private var intentFilter:IntentFilter? = null private var intentFilter:IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null private var updateUIReceiver: BroadcastReceiver? = null
@ -73,26 +71,29 @@ class SpotifyFragment : Fragment() {
initializeLiveDataObservers() initializeLiveDataObservers()
initializeBroadcast() initializeBroadcast()
val args = SpotifyFragmentArgs.fromBundle(requireArguments()) val spotifyLink = SpotifyFragmentArgs.fromBundle(requireArguments()).link.substringAfter("open.spotify.com/")
val spotifyLink = args.link
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?') val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/') val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
Log.i("Fragment", "$type : $link") Log.i("Spotify Fragment", "$type : $link")
if(sharedViewModel.spotifyService.value == null){//Authentication pending!! if(sharedViewModel.spotifyService.value == null){//Authentication pending!!
(activity as MainActivity).authenticateSpotify() (activity as MainActivity).authenticateSpotify()
} }
if (type == "Error" || link == "Error") {//Incorrect Link
when{
type == "Error" || link == "Error" -> {
showMessage("Please Check Your Link!") showMessage("Please Check Your Link!")
}else if(spotifyLink.contains("open.spotify",true)){//Link Validation!! mainActivity.onBackPressed()
}
else -> {
if(type == "episode" || type == "show"){//TODO Implementation if(type == "episode" || type == "show"){//TODO Implementation
showMessage("Implementing Soon, Stay Tuned!") showMessage("Implementing Soon, Stay Tuned!")
} }
else{ else{
viewModel.spotifySearch(type,link) viewModel.spotifySearch(type,link)
if(type=="album")adapter.isAlbum = true
binding.btnDownloadAll.setOnClickListener { binding.btnDownloadAll.setOnClickListener {
if(!isOnline()){ if(!isOnline()){
@ -112,7 +113,7 @@ class SpotifyFragment : Fragment() {
showMessage("Processing!") showMessage("Processing!")
sharedViewModel.uiScope.launch(Dispatchers.Default){ sharedViewModel.uiScope.launch(Dispatchers.Default){
val urlList = arrayListOf<String>() val urlList = arrayListOf<String>()
viewModel.trackList.value?.forEach { urlList.add(it.album?.images?.get(0)?.url.toString()) } viewModel.trackList.value?.forEach { urlList.add(it.albumArtURL) }
//Appending Source //Appending Source
urlList.add("spotify") urlList.add("spotify")
loadAllImages( loadAllImages(
@ -121,23 +122,7 @@ class SpotifyFragment : Fragment() {
) )
} }
viewModel.uiScope.launch { viewModel.uiScope.launch {
val finalList = viewModel.trackList.value?.map{ val finalList = viewModel.trackList.value
val artistsList = mutableListOf<String>()
it.artists?.forEach { artist -> artistsList.add(artist!!.name!!) }
TrackDetails(
title = it.name.toString(),
artists = artistsList,
durationSec = (it.duration_ms/1000).toInt(),
albumArt = File(
Environment.getExternalStorageDirectory(),
defaultDir +".Images/" + (it.album?.images?.get(0)?.url.toString()).substringAfterLast('/') + ".jpeg"),
albumName = it.album?.name,
year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}",
trackUrl = it.href,
source = Source.Spotify
)
}
if(finalList.isNullOrEmpty())showMessage("Not Downloading Any Song") if(finalList.isNullOrEmpty())showMessage("Not Downloading Any Song")
DownloadHelper.downloadAllTracks( DownloadHelper.downloadAllTracks(
viewModel.folderType, viewModel.folderType,
@ -148,6 +133,8 @@ class SpotifyFragment : Fragment() {
} }
} }
} }
}
return binding.root return binding.root
} }
@ -160,7 +147,7 @@ class SpotifyFragment : Fragment() {
sharedViewModel.spotifyService.observe(viewLifecycleOwner, { sharedViewModel.spotifyService.observe(viewLifecycleOwner, {
viewModel.spotifyService = it viewModel.spotifyService = it
}) })
adapter = SpotifyTrackListAdapter(viewModel) adapter = TrackListAdapter(viewModel)
DownloadHelper.youtubeMusicApi = youtubeMusicApi DownloadHelper.youtubeMusicApi = youtubeMusicApi
DownloadHelper.sharedViewModel = sharedViewModel DownloadHelper.sharedViewModel = sharedViewModel
DownloadHelper.statusBar = binding.statusBar DownloadHelper.statusBar = binding.statusBar
@ -211,7 +198,7 @@ class SpotifyFragment : Fragment() {
if (intent != null){ if (intent != null){
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track") val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
trackDetails?.let { trackDetails?.let {
val position: Int = viewModel.trackList.value?.map { it.name }?.indexOf(trackDetails.title) ?: -1 val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position") Log.i("Track","Download Completed Intent :$position")
if(position != -1) { if(position != -1) {
val track = viewModel.trackList.value?.get(position) val track = viewModel.trackList.value?.get(position)

View File

@ -17,34 +17,30 @@
package com.shabinder.spotiflyer.ui.spotify package com.shabinder.spotiflyer.ui.spotify
import android.os.Environment
import android.util.Log import android.util.Log
import androidx.hilt.lifecycle.ViewModelInject import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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.spotify.* import com.shabinder.spotiflyer.models.spotify.*
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.utils.BaseViewModel
import com.shabinder.spotiflyer.utils.Provider
import com.shabinder.spotiflyer.utils.finalOutputDir import com.shabinder.spotiflyer.utils.finalOutputDir
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : BaseViewModel(){
ViewModel(){
override var folderType:String = ""
override var subFolder:String = ""
var folderType:String = ""
var subFolder:String = ""
var trackList = MutableLiveData<MutableList<Track>>()
private val loading = "Loading"
var title = MutableLiveData<String>().apply { value = loading }
var coverUrl = MutableLiveData<String>().apply { value = loading }
var spotifyService : SpotifyService? = null var spotifyService : SpotifyService? = null
private var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun spotifySearch(type:String,link: String){ fun spotifySearch(type:String,link: String){
when (type) { when (type) {
"track" -> { "track" -> {
@ -56,7 +52,7 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
trackObject.downloaded = DownloadStatus.Downloaded trackObject.downloaded = DownloadStatus.Downloaded
} }
tempTrackList.add(trackObject) tempTrackList.add(trackObject)
trackList.value = tempTrackList trackList.value = tempTrackList.toTrackDetailsList()
title.value = trackObject.name title.value = trackObject.name
coverUrl.value = trackObject.album!!.images?.get(0)!!.url!! coverUrl.value = trackObject.album!!.images?.get(0)!!.url!!
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
@ -86,7 +82,7 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
it.album = Album(images = listOf(Image(url = albumObject.images?.get(0)?.url))) it.album = Album(images = listOf(Image(url = albumObject.images?.get(0)?.url)))
tempTrackList.add(it) tempTrackList.add(it)
} }
trackList.value = tempTrackList trackList.value = tempTrackList.toTrackDetailsList()
title.value = albumObject?.name title.value = albumObject?.name
coverUrl.value = albumObject?.images?.get(0)?.url coverUrl.value = albumObject?.images?.get(0)?.url
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
@ -129,7 +125,7 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
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 trackList.value = tempTrackList.toTrackDetailsList()
title.value = playlistObject?.name title.value = playlistObject?.name
coverUrl.value = playlistObject?.images?.get(0)?.url.toString() coverUrl.value = playlistObject?.images?.get(0)?.url.toString()
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
@ -152,6 +148,26 @@ 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,
durationSec = (it.duration_ms/1000).toInt(),
albumArt = File(
Environment.getExternalStorageDirectory(),
Provider.defaultDir +".Images/" + (it.album?.images?.get(0)?.url.toString()).substringAfterLast('/') + ".jpeg"),
albumName = it.album?.name,
year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}",
trackUrl = it.href,
downloaded = it.downloaded,
source = Source.Spotify,
albumArtURL = it.album?.images?.get(0)?.url.toString()
)
}.toMutableList()
private suspend fun getTrackDetails(trackLink:String): Track?{ private suspend fun getTrackDetails(trackLink:String): Track?{
Log.i("Requesting","https://api.spotify.com/v1/tracks/$trackLink") Log.i("Requesting","https://api.spotify.com/v1/tracks/$trackLink")
return spotifyService?.getTrack(trackLink)?.value return spotifyService?.getTrack(trackLink)?.value
@ -168,10 +184,4 @@ class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
Log.i("Requesting","https://api.spotify.com/v1/playlists/$link/tracks?offset=$offset&limit=$limit") Log.i("Requesting","https://api.spotify.com/v1/playlists/$link/tracks?offset=$offset&limit=$limit")
return spotifyService?.getPlaylistTracks(link, offset, limit)?.value return spotifyService?.getPlaylistTracks(link, offset, limit)?.value
} }
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
} }

View File

@ -37,7 +37,7 @@ import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
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
import com.shabinder.spotiflyer.recyclerView.YoutubeTrackListAdapter 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
@ -51,7 +51,7 @@ class YoutubeFragment : Fragment() {
private lateinit var viewModel: YoutubeViewModel private lateinit var viewModel: YoutubeViewModel
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var ytDownloader: YoutubeDownloader @Inject lateinit var ytDownloader: YoutubeDownloader
private lateinit var adapter : YoutubeTrackListAdapter private lateinit var adapter : TrackListAdapter
private var intentFilter: IntentFilter? = null private var intentFilter: IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null private var updateUIReceiver: BroadcastReceiver? = null
private val sampleDomain2 = "youtu.be" private val sampleDomain2 = "youtu.be"
@ -64,7 +64,7 @@ class YoutubeFragment : Fragment() {
binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment,container,false) binding = DataBindingUtil.inflate(inflater,R.layout.track_list_fragment,container,false)
viewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java) viewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
adapter = YoutubeTrackListAdapter(viewModel) adapter = TrackListAdapter(viewModel)
binding.trackList.adapter = adapter binding.trackList.adapter = adapter
initializeLiveDataObservers() initializeLiveDataObservers()
@ -108,16 +108,16 @@ class YoutubeFragment : Fragment() {
rotateAnim(binding.downloadingFab) rotateAnim(binding.downloadingFab)
for (track in viewModel.ytTrackList.value?: listOf()){ for (track in viewModel.trackList.value?: listOf()){
if(track.downloaded != DownloadStatus.Downloaded){ if(track.downloaded != DownloadStatus.Downloaded){
track.downloaded = DownloadStatus.Downloading track.downloaded = DownloadStatus.Downloading
adapter.notifyItemChanged(viewModel.ytTrackList.value!!.indexOf(track)) adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
} }
} }
showMessage("Processing!") showMessage("Processing!")
sharedViewModel.uiScope.launch(Dispatchers.Default){ sharedViewModel.uiScope.launch(Dispatchers.Default){
val urlList = arrayListOf<String>() val urlList = arrayListOf<String>()
viewModel.ytTrackList.value?.forEach { urlList.add("https://i.ytimg.com/vi/${it.albumArt.absolutePath.substringAfterLast("/") viewModel.trackList.value?.forEach { urlList.add("https://i.ytimg.com/vi/${it.albumArt.absolutePath.substringAfterLast("/")
.substringBeforeLast(".")}/hqdefault.jpg")} .substringBeforeLast(".")}/hqdefault.jpg")}
//Appending Source //Appending Source
urlList.add("youtube") urlList.add("youtube")
@ -130,7 +130,7 @@ class YoutubeFragment : Fragment() {
YTDownloadHelper.downloadYTTracks( YTDownloadHelper.downloadYTTracks(
type = viewModel.folderType, type = viewModel.folderType,
subFolder = viewModel.subFolder, subFolder = viewModel.subFolder,
tracks = viewModel.ytTrackList.value ?: listOf() tracks = viewModel.trackList.value ?: listOf()
) )
} }
} }
@ -150,13 +150,13 @@ class YoutubeFragment : Fragment() {
if (intent != null){ if (intent != null){
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track") val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
trackDetails?.let { trackDetails?.let {
val position: Int = viewModel.ytTrackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1 val position: Int = viewModel.trackList.value?.map { it.title }?.indexOf(trackDetails.title) ?: -1
Log.i("Track","Download Completed Intent :$position") Log.i("Track","Download Completed Intent :$position")
if(position != -1) { if(position != -1) {
val track = viewModel.ytTrackList.value?.get(position) val track = viewModel.trackList.value?.get(position)
track?.let{ track?.let{
it.downloaded = DownloadStatus.Downloaded it.downloaded = DownloadStatus.Downloaded
viewModel.ytTrackList.value?.set(position, it) viewModel.trackList.value?.set(position, it)
adapter.notifyItemChanged(position) adapter.notifyItemChanged(position)
checkIfAllDownloaded() checkIfAllDownloaded()
} }
@ -174,7 +174,7 @@ class YoutubeFragment : Fragment() {
} }
private fun checkIfAllDownloaded() { private fun checkIfAllDownloaded() {
if(!viewModel.ytTrackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){ if(!viewModel.trackList.value!!.any { it.downloaded != DownloadStatus.Downloaded }){
//All Tracks Downloaded //All Tracks Downloaded
binding.btnDownloadAll.visibility = View.GONE binding.btnDownloadAll.visibility = View.GONE
binding.downloadingFab.apply{ binding.downloadingFab.apply{
@ -195,7 +195,7 @@ class YoutubeFragment : Fragment() {
/** /**
* TrackList Binding Observer! * TrackList Binding Observer!
**/ **/
viewModel.ytTrackList.observe(viewLifecycleOwner, { viewModel.trackList.observe(viewLifecycleOwner, {
adapter.submitList(it) adapter.submitList(it)
}) })

View File

@ -21,38 +21,31 @@ import android.annotation.SuppressLint
import android.os.Environment import android.os.Environment
import android.util.Log import android.util.Log
import androidx.hilt.lifecycle.ViewModelInject import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.YoutubeDownloader
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.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.utils.BaseViewModel
import com.shabinder.spotiflyer.utils.Provider.defaultDir import com.shabinder.spotiflyer.utils.Provider.defaultDir
import com.shabinder.spotiflyer.utils.finalOutputDir import com.shabinder.spotiflyer.utils.finalOutputDir
import com.shabinder.spotiflyer.utils.removeIllegalChars import com.shabinder.spotiflyer.utils.removeIllegalChars
import com.shabinder.spotiflyer.utils.showMessage import com.shabinder.spotiflyer.utils.showMessage
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : ViewModel(){ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : BaseViewModel(){
/* /*
* 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"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg" * Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */ * */
val ytTrackList = MutableLiveData<MutableList<TrackDetails>>() override var folderType = "YT_Downloads"
private val loading = "Loading" override var subFolder = ""
var title = MutableLiveData<String>().apply { value = "\"Loading!\"" }
var coverUrl = MutableLiveData<String>().apply { value = loading }
val folderType = "YT_Downloads"
var subFolder = ""
private var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun getYTPlaylist(searchId:String, ytDownloader:YoutubeDownloader){ fun getYTPlaylist(searchId:String, ytDownloader:YoutubeDownloader){
try{ try{
@ -67,7 +60,7 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
title.postValue( title.postValue(
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name} if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
) )
ytTrackList.postValue(videos.map { this@YoutubeViewModel.trackList.postValue(videos.map {
TrackDetails( TrackDetails(
title = it.title(), title = it.title(),
artists = listOf(it.author().toString()), artists = listOf(it.author().toString()),
@ -77,13 +70,13 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
defaultDir + ".Images/" + it.videoId() + ".jpeg" defaultDir + ".Images/" + it.videoId() + ".jpeg"
), ),
source = Source.YouTube, source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (File( downloaded = if (File(
finalOutputDir( finalOutputDir(
itemName = it.title(), itemName = it.title(),
type = folderType, type = folderType,
subFolder = subFolder subFolder = subFolder
) )).exists()
).exists()
) )
DownloadStatus.Downloaded DownloadStatus.Downloaded
else { else {
@ -120,7 +113,7 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
val detail = video?.details() val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: "" val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
Log.i("YT View Model",detail.toString()) Log.i("YT View Model",detail.toString())
ytTrackList.postValue( this@YoutubeViewModel.trackList.postValue(
listOf( listOf(
TrackDetails( TrackDetails(
title = name, title = name,
@ -128,9 +121,10 @@ class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO
durationSec = detail?.lengthSeconds()?:0, durationSec = detail?.lengthSeconds()?:0,
albumArt = File( albumArt = File(
Environment.getExternalStorageDirectory(), Environment.getExternalStorageDirectory(),
defaultDir +".Images/" + searchId + ".jpeg" "$defaultDir.Images/$searchId.jpeg"
), ),
source = Source.YouTube source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
) )
).toMutableList() ).toMutableList()
) )

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.utils
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.models.TrackDetails
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
abstract class BaseViewModel:ViewModel() {
abstract var folderType:String
abstract var subFolder:String
open val trackList = MutableLiveData<MutableList<TrackDetails>>()
private val viewModelJob:CompletableJob = Job()
open val uiScope = CoroutineScope(Dispatchers.Default + viewModelJob)
private val loading = "Loading!"
open var title = MutableLiveData<String>().apply { value = loading }
open var coverUrl = MutableLiveData<String>().apply { value = loading }
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

View File

@ -49,7 +49,7 @@ import javax.inject.Singleton
@Module @Module
object Provider { object Provider {
val activity: 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
@ -68,7 +68,7 @@ object Provider {
@Provides @Provides
@Singleton @Singleton
fun provideUpi():EasyUpiPayment { fun provideUpi():EasyUpiPayment {
return EasyUpiPayment.Builder(activity) return EasyUpiPayment.Builder(mainActivity)
.setPayeeVpa("technoshab@paytm") .setPayeeVpa("technoshab@paytm")
.setPayeeName("Shabinder Singh") .setPayeeName("Shabinder Singh")
.setTransactionId("UNIQUE_TRANSACTION_ID") .setTransactionId("UNIQUE_TRANSACTION_ID")

View File

@ -41,13 +41,12 @@ import com.google.android.material.snackbar.Snackbar
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.models.DownloadObject import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.utils.Provider.activity
import com.shabinder.spotiflyer.utils.Provider.defaultDir import com.shabinder.spotiflyer.utils.Provider.defaultDir
import com.shabinder.spotiflyer.utils.Provider.mainActivity
import com.shabinder.spotiflyer.worker.ForegroundService import com.shabinder.spotiflyer.worker.ForegroundService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -77,7 +76,7 @@ fun finalOutputDir(itemName:String? = null,type:String, subFolder:String?=null,e
fun isOnline(): Boolean { fun isOnline(): Boolean {
var result = false var result = false
val connectivityManager = val connectivityManager =
activity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
connectivityManager?.let { connectivityManager?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply { it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
@ -90,7 +89,7 @@ fun isOnline(): Boolean {
} }
} else { } else {
val netInfo = val netInfo =
(activity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo (mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
result = netInfo != null && netInfo.isConnected result = netInfo != null && netInfo.isConnected
} }
} }
@ -100,12 +99,12 @@ fun isOnline(): Boolean {
fun showMessage(message: String, long: Boolean = false){ fun showMessage(message: String, long: Boolean = false){
CoroutineScope(Dispatchers.Main).launch{ CoroutineScope(Dispatchers.Main).launch{
Snackbar.make( Snackbar.make(
activity.snackBarAnchor, mainActivity.snackBarAnchor,
message, message,
if (long) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT if (long) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT
).also { snackbar -> ).apply {
snackbar.setAction("Ok") { setAction("Ok") {
snackbar.dismiss() dismiss()
} }
}.show() }.show()
} }
@ -126,7 +125,7 @@ fun rotateAnim(view: View){
fun showNoConnectionAlert(){ fun showNoConnectionAlert(){
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
activity.apply { mainActivity.apply {
MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)
.setTitle(resources.getString(R.string.title)) .setTitle(resources.getString(R.string.title))
.setMessage(resources.getString(R.string.supporting_text)) .setMessage(resources.getString(R.string.supporting_text))
@ -187,13 +186,10 @@ fun bindImage(imgView: ImageView, imgUrl: String?,source: Source?) {
} }
// the File to save , append increasing numeric counter to prevent files from getting overwritten. // the File to save , append increasing numeric counter to prevent files from getting overwritten.
resource?.copyTo(file) resource?.copyTo(file)
withContext(Dispatchers.Main){
Glide.with(imgView) Glide.with(imgView)
.load(file) .load(file)
.placeholder(R.drawable.ic_song_placeholder) .placeholder(R.drawable.ic_song_placeholder)
.into(imgView) .into(imgView)
// Log.i("Glide","imageSaved")
}
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }

View File

@ -22,15 +22,6 @@
android:id="@+id/navigation" android:id="@+id/navigation"
app:startDestination="@id/mainFragment"> app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/spotifyFragment"
android:name="com.shabinder.spotiflyer.ui.spotify.SpotifyFragment"
android:label="main_fragment"
tools:layout="@layout/track_list_fragment" >
<argument
android:name="link"
app:argType="string" />
</fragment>
<fragment <fragment
android:id="@+id/mainFragment" android:id="@+id/mainFragment"
android:name="com.shabinder.spotiflyer.ui.mainfragment.MainFragment" android:name="com.shabinder.spotiflyer.ui.mainfragment.MainFragment"
@ -51,16 +42,11 @@
app:destination="@id/downloadRecord" app:destination="@id/downloadRecord"
app:enterAnim="@android:anim/slide_in_left" app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right" /> app:exitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_mainFragment_to_gaanaFragment"
app:destination="@id/gaanaFragment" />
</fragment> </fragment>
<fragment
android:id="@+id/youtubeFragment"
android:name="com.shabinder.spotiflyer.ui.youtube.YoutubeFragment"
android:label="YoutubeFragment"
tools:layout="@layout/track_list_fragment">
<argument
android:name="link"
app:argType="string" />
</fragment>
<fragment <fragment
android:id="@+id/downloadRecord" android:id="@+id/downloadRecord"
android:name="com.shabinder.spotiflyer.ui.downloadrecord.DownloadRecordFragment" android:name="com.shabinder.spotiflyer.ui.downloadrecord.DownloadRecordFragment"
@ -76,5 +62,35 @@
app:destination="@id/youtubeFragment" app:destination="@id/youtubeFragment"
app:enterAnim="@android:anim/slide_in_left" app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"/> app:exitAnim="@android:anim/slide_out_right"/>
<action
android:id="@+id/action_downloadRecord_to_gaanaFragment"
app:destination="@id/gaanaFragment" />
</fragment>
<fragment
android:id="@+id/spotifyFragment"
android:name="com.shabinder.spotiflyer.ui.spotify.SpotifyFragment"
android:label="main_fragment"
tools:layout="@layout/track_list_fragment" >
<argument
android:name="link"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/youtubeFragment"
android:name="com.shabinder.spotiflyer.ui.youtube.YoutubeFragment"
android:label="YoutubeFragment"
tools:layout="@layout/track_list_fragment">
<argument
android:name="link"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/gaanaFragment"
android:name="com.shabinder.spotiflyer.ui.gaana.GaanaFragment"
android:label="GaanaFragment"
tools:layout="@layout/track_list_fragment">
<argument
android:name="link"
app:argType="string" />
</fragment> </fragment>
</navigation> </navigation>