Added Providers.

This commit is contained in:
shabinder 2020-12-30 15:16:21 +05:30
parent dcfca42c40
commit 86431dbc1c
16 changed files with 958 additions and 594 deletions

View File

@ -1,6 +1,13 @@
package com.shabinder.spotiflyer package com.shabinder.spotiflyer
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -17,15 +24,25 @@ import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.shabinder.spotiflyer.navigation.ComposeNavigation import com.shabinder.spotiflyer.navigation.ComposeNavigation
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
import com.shabinder.spotiflyer.ui.ComposeLearnTheme import com.shabinder.spotiflyer.ui.ComposeLearnTheme
import com.shabinder.spotiflyer.ui.appNameStyle import com.shabinder.spotiflyer.ui.appNameStyle
import com.shabinder.spotiflyer.utils.requestStoragePermission import com.shabinder.spotiflyer.utils.*
import com.squareup.moshi.Moshi
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
import dev.chrisbanes.accompanist.insets.statusBarsHeight import dev.chrisbanes.accompanist.insets.statusBarsHeight
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Inject import javax.inject.Inject
import com.shabinder.spotiflyer.utils.showDialog as showDialog1
/* /*
* This is App's God Activity * This is App's God Activity
@ -34,13 +51,15 @@ import javax.inject.Inject
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private var spotifyService : SpotifyService? = null private var spotifyService : SpotifyService? = null
@Inject lateinit var moshi: Moshi
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
// This app draws behind the system bars, so we want to handle fitting system windows // This app draws behind the system bars, so we want to handle fitting system windows
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
ComposeLearnTheme { ComposeLearnTheme {
ProvideWindowInsets { ProvideWindowInsets {
@ -60,8 +79,103 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
initialize()
}
private fun initialize() {
authenticateSpotify()
requestStoragePermission() requestStoragePermission()
disableDozeMode()
//checkIfLatestVersion()
createDirectories()
handleIntentFromExternalActivity()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
//Return to MainFragment For Further Processing of this Intent
handleIntentFromExternalActivity(intent)
}
@SuppressLint("BatteryLife")
fun disableDozeMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val pm =
this.getSystemService(Context.POWER_SERVICE) as PowerManager
val isIgnoringBatteryOptimizations = pm.isIgnoringBatteryOptimizations(packageName)
if (!isIgnoringBatteryOptimizations) {
val intent = Intent().apply{
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
data = Uri.parse("package:$packageName")
}
startActivityForResult(intent, 1233)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 1233) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val pm =
getSystemService(Context.POWER_SERVICE) as PowerManager
val isIgnoringBatteryOptimizations =
pm.isIgnoringBatteryOptimizations(packageName)
if (isIgnoringBatteryOptimizations) {
// Ignoring battery optimization
} else {
disableDozeMode()//Again Ask For Permission!!
}
}
}
}
/**
* Adding my own new Spotify Web Api Requests!
* */
private fun implementSpotifyService(token: String) {
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
httpClient.addInterceptor(Interceptor { chain ->
val request: Request =
chain.request().newBuilder().addHeader(
"Authorization",
"Bearer $token"
).build()
chain.proceed(request)
}).addInterceptor(NetworkInterceptor())
val retrofit = Retrofit.Builder()
.baseUrl("https://api.spotify.com/v1/")
.client(httpClient.build())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
spotifyService = retrofit.create(SpotifyService::class.java)
sharedViewModel.spotifyService.value = spotifyService
}
fun authenticateSpotify() {
sharedViewModel.viewModelScope.launch {
log("Spotify Authentication","Started")
val token = spotifyServiceTokenRequest.getToken()
token.value?.let {
showDialog1("Success: Spotify Token Acquired")
implementSpotifyService(it.access_token)
}
log("Spotify Token", token.value.toString())
}
}
private fun handleIntentFromExternalActivity(intent: Intent? = getIntent()) {
if (intent?.action == Intent.ACTION_SEND) {
if ("text/plain" == intent.type) {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
log("Intent Received", it)
sharedViewModel.intentString.value = it
}
}
}
} }
companion object{ companion object{

View File

@ -17,11 +17,21 @@
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.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
import dagger.hilt.android.scopes.ActivityRetainedScoped
class SharedViewModel : ViewModel() { @ActivityRetainedScoped
class SharedViewModel @ViewModelInject constructor(
val databaseDAO: DatabaseDAO,
val gaanaInterface : GaanaInterface,
val ytDownloader: YoutubeDownloader
) : ViewModel() {
var intentString = MutableLiveData<String>() var intentString = MutableLiveData<String>()
var spotifyService = MutableLiveData<SpotifyService>() var spotifyService = MutableLiveData<SpotifyService>()
} }

View File

@ -0,0 +1,9 @@
package com.shabinder.spotiflyer.models
data class PlatformQueryResult(
var folderType: String,
var subFolder: String,
var title: String,
var coverUrl: String,
var trackList: List<TrackDetails>
)

View File

@ -1,15 +1,17 @@
package com.shabinder.spotiflyer.navigation package com.shabinder.spotiflyer.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.*
import androidx.navigation.compose.composable import androidx.navigation.compose.popUpTo
import androidx.navigation.compose.navArgument
import androidx.navigation.compose.rememberNavController
import com.shabinder.spotiflyer.ui.home.Home import com.shabinder.spotiflyer.ui.home.Home
import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana
import com.shabinder.spotiflyer.ui.platforms.spotify.Spotify import com.shabinder.spotiflyer.ui.platforms.spotify.Spotify
import com.shabinder.spotiflyer.ui.platforms.youtube.Youtube import com.shabinder.spotiflyer.ui.platforms.youtube.Youtube
import com.shabinder.spotiflyer.utils.mainActivity
import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog
@Composable @Composable
fun ComposeNavigation() { fun ComposeNavigation() {
@ -31,7 +33,7 @@ fun ComposeNavigation() {
arguments = listOf(navArgument("link") { type = NavType.StringType }) arguments = listOf(navArgument("link") { type = NavType.StringType })
) { ) {
Spotify( Spotify(
link = it.arguments?.getString("link") ?: "error", fullLink = it.arguments?.getString("link") ?: "error",
navController = navController navController = navController
) )
} }
@ -43,7 +45,7 @@ fun ComposeNavigation() {
arguments = listOf(navArgument("link") { type = NavType.StringType }) arguments = listOf(navArgument("link") { type = NavType.StringType })
) { ) {
Gaana( Gaana(
link = it.arguments?.getString("link") ?: "error", fullLink = it.arguments?.getString("link") ?: "error",
navController = navController navController = navController
) )
} }
@ -55,9 +57,42 @@ fun ComposeNavigation() {
arguments = listOf(navArgument("link") { type = NavType.StringType }) arguments = listOf(navArgument("link") { type = NavType.StringType })
) { ) {
Youtube( Youtube(
link = it.arguments?.getString("link") ?: "error", fullLink = it.arguments?.getString("link") ?: "error",
navController = navController navController = navController
) )
} }
} }
} }
fun NavController.navigateToPlatform(link:String){
when{
//SPOTIFY
link.contains("spotify",true) -> {
if(sharedViewModel.spotifyService.value == null){//Authentication pending!!
mainActivity.authenticateSpotify()
}
this.navigateAndPopUpToHome("spotify/$link")
}
//YOUTUBE
link.contains("youtube.com",true) || link.contains("youtu.be",true) -> {
this.navigateAndPopUpToHome("youtube/$link")
}
//GAANA
link.contains("gaana",true) -> {
this.navigateAndPopUpToHome("gaana/$link")
}
else -> showDialog("Link is Not Valid")
}
}
fun NavController.navigateAndPopUpToHome(route:String, inclusive:Boolean = false,singleInstance:Boolean = true){
this.navigate(route){
launchSingleTop = singleInstance
popUpTo(route = "home"){
this.inclusive = inclusive
}
}
}

View File

@ -17,17 +17,24 @@
package com.shabinder.spotiflyer.ui.base package com.shabinder.spotiflyer.ui.base
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
abstract class TrackListViewModel:ViewModel() { abstract class TrackListViewModel:ViewModel() {
abstract var folderType:String abstract var folderType:String
abstract var subFolder:String abstract var subFolder:String
open val trackList = MutableLiveData<MutableList<TrackDetails>>() private val _trackList = MutableStateFlow<List<TrackDetails>>(mutableListOf())
open val trackList:StateFlow<List<TrackDetails>>
get() = _trackList
fun updateTrackList(list:List<TrackDetails>){
_trackList.value = list
}
private val loading = "Loading!" private val loading = "Loading!"
open var title = MutableLiveData<String>().apply { value = loading } open var title = MutableStateFlow(loading)
open var coverUrl = MutableLiveData<String>() open var coverUrl = MutableStateFlow(loading)
} }

View File

@ -29,11 +29,16 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.navigate import androidx.navigation.compose.navigate
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.navigation.navigateToPlatform
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent import com.shabinder.spotiflyer.ui.colorAccent
import com.shabinder.spotiflyer.ui.colorPrimary import com.shabinder.spotiflyer.ui.colorPrimary
import com.shabinder.spotiflyer.utils.mainActivity
import com.shabinder.spotiflyer.utils.openPlatform import com.shabinder.spotiflyer.utils.openPlatform
import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog
@Composable @Composable
fun Home(navController: NavController, modifier: Modifier = Modifier) { fun Home(navController: NavController, modifier: Modifier = Modifier) {
@ -289,7 +294,7 @@ fun SearchPanel(
) )
OutlinedButton( OutlinedButton(
modifier = Modifier.padding(12.dp).wrapContentWidth(), modifier = Modifier.padding(12.dp).wrapContentWidth(),
onClick = {navController.navigate("track_list/$link") }, onClick = {navController.navigateToPlatform(link)},
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent))) border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
){ ){
Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp)) Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp))

View File

@ -1,8 +1,45 @@
package com.shabinder.spotiflyer.ui.platforms.gaana package com.shabinder.spotiflyer.ui.platforms.gaana
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavController import androidx.navigation.NavController
import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.launch
@Composable @Composable
fun Gaana(link: String, navController: NavController,) { fun Gaana(
fullLink: String,
navController: NavController
) {
//Coroutine Scope Active till this Composable is Active
val coroutineScope = rememberCoroutineScope()
//Link Schema: https://gaana.com/type/link
val gaanaLink = fullLink.substringAfter("gaana.com/")
val link = gaanaLink.substringAfterLast('/', "error")
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
log("Gaana Fragment", "$type : $link")
//Error
if (type == "Error" || link == "Error"){
showDialog("Please Check Your Link!")
navController.popBackStack()
}
coroutineScope.launch {
val result = gaanaSearch(
type,
link,
sharedViewModel.gaanaInterface,
sharedViewModel.databaseDAO,
)
log("Gaana",result.toString())
log("Gaana Tracks",result.trackList.size.toString())
}
} }

View File

@ -0,0 +1,208 @@
/*
* 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.ui.platforms.gaana
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.utils.Provider.imageDir
import com.shabinder.spotiflyer.utils.finalOutputDir
import com.shabinder.spotiflyer.utils.queryActiveTracks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
private const val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
suspend fun gaanaSearch(
type:String,
link:String,
gaanaInterface: GaanaInterface,
databaseDAO: DatabaseDAO,
): PlatformQueryResult {
val result = PlatformQueryResult(
folderType = "",
subFolder = link,
title = link,
coverUrl = gaanaPlaceholderImageUrl,
trackList = listOf(),
)
with(result) {
when (type) {
"song" -> {
gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
folderType = "Tracks"
subFolder = ""
if (File(
finalOutputDir(
it.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
title = it.track_title
coverUrl = it.artworkLink
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Track",
name = title,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl,
totalFiles = 1,
)
)
}
}
}
"album" -> {
gaanaInterface.getGaanaAlbum(seokey = link).value?.also {
folderType = "Albums"
subFolder = link
it.tracks.forEach { track ->
if (File(
finalOutputDir(
track.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
}
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
title = link
coverUrl = it.custom_artworks.size_480p
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Album",
name = title,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl,
totalFiles = trackList.size,
)
)
}
}
}
"playlist" -> {
gaanaInterface.getGaanaPlaylist(seokey = link).value?.also {
folderType = "Playlists"
subFolder = link
it.tracks.forEach { track ->
if (File(
finalOutputDir(
track.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
}
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
title = link
//coverUrl.value = "TODO"
coverUrl = gaanaPlaceholderImageUrl
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Playlist",
name = title,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl,
totalFiles = it.tracks.size,
)
)
}
}
}
"artist" -> {
folderType = "Artist"
subFolder = link
coverUrl = gaanaPlaceholderImageUrl
val artistDetails =
gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull()
?.also {
title = it.name
coverUrl = it.artworkLink ?: gaanaPlaceholderImageUrl
}
gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also {
it.tracks.forEach { track ->
if (File(
finalOutputDir(
track.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
}
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Artist",
name = artistDetails?.name ?: link,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl,
totalFiles = trackList.size,
)
)
}
}
}
else -> {//TODO Handle Error}
}
}
queryActiveTracks()
return result
}
}
private fun List<GaanaTrack>.toTrackDetailsList(type:String , subFolder:String) = this.map {
TrackDetails(
title = it.track_title,
artists = it.artist.map { artist -> artist?.name.toString() },
durationSec = it.duration,
albumArt = File(
imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg"),
albumName = it.album_title,
year = it.release_date,
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",
trackUrl = it.lyrics_url,
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
source = Source.Gaana,
albumArtURL = it.artworkLink,
outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a")
)
}

View File

@ -1,204 +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.ui.platforms.gaana
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.viewModelScope
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.ui.base.TrackListViewModel
import com.shabinder.spotiflyer.utils.Provider.imageDir
import com.shabinder.spotiflyer.utils.finalOutputDir
import com.shabinder.spotiflyer.utils.queryActiveTracks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class GaanaViewModel @ViewModelInject constructor(
private val databaseDAO: DatabaseDAO,
private val gaanaInterface : GaanaInterface
) : TrackListViewModel(){
override var folderType:String = ""
override var subFolder:String = ""
private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
fun gaanaSearch(type:String,link:String){
viewModelScope.launch {
when (type) {
"song" -> {
gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
folderType = "Tracks"
subFolder = ""
if (File(
finalOutputDir(
it.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder)
title.value = it.track_title
coverUrl.value = it.artworkLink
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Track",
name = title.value!!,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl.value!!,
totalFiles = 1,
)
)
}
}
}
"album" -> {
gaanaInterface.getGaanaAlbum(seokey = link).value?.also {
folderType = "Albums"
subFolder = link
it.tracks.forEach { track ->
if (File(
finalOutputDir(
track.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
}
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
title.value = link
coverUrl.value = it.custom_artworks.size_480p
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Album",
name = title.value!!,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = trackList.value?.size ?: 0,
)
)
}
}
}
"playlist" -> {
gaanaInterface.getGaanaPlaylist(seokey = link).value?.also {
folderType = "Playlists"
subFolder = link
it.tracks.forEach { track ->
if (File(
finalOutputDir(
track.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
}
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
title.value = link
//coverUrl.value = "TODO"
coverUrl.value = gaanaPlaceholderImageUrl
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Playlist",
name = title.value.toString(),
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = it.tracks.size,
)
)
}
}
}
"artist" -> {
folderType = "Artist"
subFolder = link
val artistDetails =
gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull()
?.also {
title.value = it.name
coverUrl.value = it.artworkLink
}
gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also {
it.tracks.forEach { track ->
if (File(
finalOutputDir(
track.track_title,
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
}
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Artist",
name = artistDetails?.name ?: link,
link = "https://gaana.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = trackList.value?.size ?: 0,
)
)
}
}
}
}
queryActiveTracks()
}
}
private fun List<GaanaTrack>.toTrackDetailsList(type:String , subFolder:String) = this.map {
TrackDetails(
title = it.track_title,
artists = it.artist.map { artist -> artist?.name.toString() },
durationSec = it.duration,
albumArt = File(
imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg"),
albumName = it.album_title,
year = it.release_date,
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",
trackUrl = it.lyrics_url,
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
source = Source.Gaana,
albumArtURL = it.artworkLink,
outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a")
)
}.toMutableList()
}

View File

@ -1,8 +1,69 @@
package com.shabinder.spotiflyer.ui.platforms.spotify package com.shabinder.spotiflyer.ui.platforms.spotify
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.viewinterop.viewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController import androidx.navigation.NavController
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable @Composable
fun Spotify(link: String, navController: NavController,) { fun Spotify(fullLink: String, navController: NavController,) {
val source: Source = Source.Spotify
val coroutineScope = rememberCoroutineScope()
var spotifyLink =
"https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim()
log("Spotify Fragment Link", spotifyLink)
coroutineScope.launch(Dispatchers.Default) {
/*
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&amp;_branch_match_id=862039436205270630
* */
if (!spotifyLink.contains("open.spotify")) {
val resolvedLink = resolveLink(spotifyLink, sharedViewModel.gaanaInterface)
log("Spotify Resolved Link", resolvedLink)
spotifyLink = resolvedLink
}
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
log("Spotify Fragment", "$type : $link")
if (sharedViewModel.spotifyService.value == null) {//Authentication pending!!
if (isOnline()) mainActivity.authenticateSpotify()
}
if (type == "Error" || link == "Error") {
showDialog("Please Check Your Link!")
navController.popBackStack()
}
if (type == "episode" || type == "show") {
//TODO Implementation
showDialog("Implementing Soon, Stay Tuned!")
} else {
if (sharedViewModel.spotifyService.value == null){
//Authentication Still Pending
// TODO Better Implementation
showDialog("Authentication Failed")
navController.popBackStack()
}else{
val result = spotifySearch(
type,
link,
sharedViewModel.spotifyService.value!!,
sharedViewModel.databaseDAO
)
}
}
}
} }

View File

@ -0,0 +1,221 @@
/*
* 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.ui.platforms.spotify
import androidx.annotation.WorkerThread
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.models.TrackDetails
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.GaanaInterface
import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
suspend fun spotifySearch(
type:String,
link: String,
spotifyService: SpotifyService,
databaseDAO: DatabaseDAO
): PlatformQueryResult {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
)
with(result) {
when (type) {
"track" -> {
spotifyService.getTrack(link).value?.also {
folderType = "Tracks"
subFolder = ""
if (File(
finalOutputDir(
it.name.toString(),
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
title = it.name.toString()
coverUrl = (it.album?.images?.elementAtOrNull(1)?.url
?: it.album?.images?.elementAtOrNull(0)?.url).toString()
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Track",
name = title,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl,
totalFiles = 1,
)
)
}
}
}
"album" -> {
val albumObject = spotifyService.getAlbum(link).value
folderType = "Albums"
subFolder = albumObject?.name.toString()
albumObject?.tracks?.items?.forEach {
if (File(
finalOutputDir(
it.name.toString(),
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
it.album = Album(
images = listOf(
Image(
url = albumObject.images?.elementAtOrNull(1)?.url
?: albumObject.images?.elementAtOrNull(0)?.url
)
)
)
}
albumObject?.tracks?.items?.toTrackDetailsList(folderType, subFolder).let {
if (it.isNullOrEmpty()) {
//TODO Handle Error
showDialog("Error Fetching Album")
} else {
trackList = it
title = albumObject?.name.toString()
coverUrl = (albumObject?.images?.elementAtOrNull(1)?.url
?: albumObject?.images?.elementAtOrNull(0)?.url).toString()
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Album",
name = title,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl,
totalFiles = trackList.size,
)
)
}
}
}
}
"playlist" -> {
log("Spotify Service", spotifyService.toString())
val playlistObject = spotifyService.getPlaylist(link).value
folderType = "Playlists"
subFolder = playlistObject?.name.toString()
val tempTrackList = mutableListOf<Track>()
log("Tracks Fetched", playlistObject?.tracks?.items?.size.toString())
playlistObject?.tracks?.items?.forEach {
it.track?.let { it1 ->
if (File(
finalOutputDir(
it1.name.toString(),
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it1.downloaded = DownloadStatus.Downloaded
}
tempTrackList.add(it1)
}
}
var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank()
while (moreTracksAvailable) {
//Check For More Tracks If available
val moreTracks =
spotifyService.getPlaylistTracks(link, offset = tempTrackList.size).value
moreTracks?.items?.forEach {
it.track?.let { it1 -> tempTrackList.add(it1) }
}
moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
}
log("Total Tracks Fetched", tempTrackList.size.toString())
trackList = tempTrackList.toTrackDetailsList(folderType, subFolder)
title = playlistObject?.name.toString()
coverUrl = playlistObject?.images?.elementAtOrNull(1)?.url
?: playlistObject?.images?.firstOrNull()?.url.toString()
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Playlist",
name = title,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl,
totalFiles = tempTrackList.size,
)
)
}
}
"episode" -> {//TODO
}
"show" -> {//TODO
}
else -> {
//TODO Handle Error
}
}
}
queryActiveTracks()
return result
}
@WorkerThread
fun resolveLink(
url:String,
gaanaInterface: GaanaInterface
):String {
val response = gaanaInterface.getResponse(url).execute().body()?.string().toString()
val regex = """https://open\.spotify\.com.+\w""".toRegex()
return regex.find(response)?.value.toString()
}
private fun List<Track>.toTrackDetailsList(type:String, subFolder:String) = this.map {
TrackDetails(
title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms/1000).toInt(),
albumArt = File(
Provider.imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.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?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(),
outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a")
)
}

View File

@ -1,209 +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.ui.platforms.spotify
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.viewModelScope
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.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.GaanaInterface
import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.ui.base.TrackListViewModel
import com.shabinder.spotiflyer.utils.Provider.imageDir
import com.shabinder.spotiflyer.utils.finalOutputDir
import com.shabinder.spotiflyer.utils.log
import com.shabinder.spotiflyer.utils.queryActiveTracks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class SpotifyViewModel @ViewModelInject constructor(
private val databaseDAO: DatabaseDAO,
private val gaanaInterface : GaanaInterface
) : TrackListViewModel(){
override var folderType:String = ""
override var subFolder:String = ""
var spotifyService : SpotifyService? = null
fun resolveLink(url:String):String {
val response = gaanaInterface.getResponse(url).execute().body()?.string().toString()
val regex = """https://open\.spotify\.com.+\w""".toRegex()
return regex.find(response)?.value.toString()
}
fun spotifySearch(type:String,link: String){
viewModelScope.launch {
when (type) {
"track" -> {
spotifyService?.getTrack(link)?.value?.also {
folderType = "Tracks"
subFolder = ""
if (File(
finalOutputDir(
it.name.toString(),
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder)
title.value = it.name
coverUrl.value = it.album?.images?.elementAtOrNull(1)?.url
?: it.album?.images?.elementAtOrNull(0)?.url
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Track",
name = title.value.toString(),
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = 1,
)
)
}
}
}
"album" -> {
val albumObject = spotifyService?.getAlbum(link)?.value
folderType = "Albums"
subFolder = albumObject?.name.toString()
albumObject?.tracks?.items?.forEach {
if (File(
finalOutputDir(
it.name.toString(),
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
it.album = Album(
images = listOf(
Image(
url = albumObject.images?.elementAtOrNull(1)?.url
?: albumObject.images?.elementAtOrNull(0)?.url
)
)
)
}
trackList.value = albumObject?.tracks?.items?.toTrackDetailsList(folderType, subFolder)
title.value = albumObject?.name
coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url
?: albumObject?.images?.elementAtOrNull(0)?.url
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Album",
name = title.value.toString(),
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = trackList.value?.size ?: 0,
)
)
}
}
"playlist" -> {
log("Spotify Service",spotifyService.toString())
val playlistObject = spotifyService?.getPlaylist(link)?.value
folderType = "Playlists"
subFolder = playlistObject?.name.toString()
val tempTrackList = mutableListOf<Track>()
log("Tracks Fetched", playlistObject?.tracks?.items?.size.toString())
playlistObject?.tracks?.items?.forEach {
it.track?.let { it1 ->
if (File(
finalOutputDir(
it1.name.toString(),
folderType,
subFolder
)
).exists()
) {//Download Already Present!!
it1.downloaded = DownloadStatus.Downloaded
}
tempTrackList.add(it1)
}
}
var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank()
while (moreTracksAvailable) {
//Check For More Tracks If available
val moreTracks = spotifyService?.getPlaylistTracks(link, offset = tempTrackList.size)?.value
moreTracks?.items?.forEach {
it.track?.let { it1 -> tempTrackList.add(it1) }
}
moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
}
log("Total Tracks Fetched", tempTrackList.size.toString())
trackList.value = tempTrackList.toTrackDetailsList(folderType, subFolder)
title.value = playlistObject?.name
coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url
?: playlistObject?.images?.firstOrNull()?.url.toString()
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Playlist",
name = title.value.toString(),
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value.toString(),
totalFiles = tempTrackList.size,
)
)
}
}
"episode" -> {//TODO
}
"show" -> {//TODO
}
}
queryActiveTracks()
}
}
private fun List<Track>.toTrackDetailsList(type:String , subFolder:String) = this.map {
TrackDetails(
title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms/1000).toInt(),
albumArt = File(
imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.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?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(),
outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a")
)
}.toMutableList()
}

View File

@ -1,9 +1,52 @@
package com.shabinder.spotiflyer.ui.platforms.youtube package com.shabinder.spotiflyer.ui.platforms.youtube
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavController import androidx.navigation.NavController
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog
import kotlinx.coroutines.launch
private const val sampleDomain2 = "youtu.be"
private const val sampleDomain1 = "youtube.com"
@Composable @Composable
fun Youtube(link: String, navController: NavController,) { fun Youtube(fullLink: String, navController: NavController,) {
val source = Source.YouTube
//Coroutine Scope Active till this Composable is Active
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if(link.contains("playlist",true) || link.contains("list",true)){
// Given Link is of a Playlist
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
getYTPlaylist(
playlistId,
sharedViewModel.ytDownloader,
sharedViewModel.databaseDAO
)
}else{//Given Link is of a Video
var searchId = "error"
if(link.contains(sampleDomain1,true) ){
searchId = link.substringAfterLast("=","error")
}
if(link.contains(sampleDomain2,true) ){
searchId = link.substringAfterLast("/","error")
}
if(searchId != "error") {
getYTTrack(
searchId,
sharedViewModel.ytDownloader,
sharedViewModel.databaseDAO
)
}else{
showDialog("Your Youtube Link is not of a Video!!")
navController.popBackStack()
}
}
}
} }

View File

@ -0,0 +1,187 @@
/*
* 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.ui.platforms.youtube
import android.annotation.SuppressLint
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.imageDir
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
suspend fun getYTPlaylist(
searchId: String,
ytDownloader: YoutubeDownloader,
databaseDAO: DatabaseDAO,
):PlatformQueryResult{
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
)
with(result) {
try {
log("YT Playlist", searchId)
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details()
val name = playlistDetails.title()
subFolder = removeIllegalChars(name)
val videos = playlist.videos()
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId()
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArt = File(
imageDir() + it.videoId() + ".jpeg"
),
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (File(
finalOutputDir(
itemName = it.title(),
type = folderType,
subFolder = subFolder
)
).exists()
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFile = finalOutputDir(it.title(), folderType, subFolder, ".m4a"),
videoID = it.videoId()
)
}
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "PlayList",
name = if (name.length > 17) {
"${name.subSequence(0, 16)}..."
} else {
name
},
link = "https://www.youtube.com/playlist?list=$searchId",
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId()
}/hqdefault.jpg",
totalFiles = videos.size,
)
)
}
queryActiveTracks()
} catch (e: Exception) {
showDialog("An Error Occurred While Processing!")
}
}
return result
}
@SuppressLint("DefaultLocale")
suspend fun getYTTrack(
searchId:String,
ytDownloader: YoutubeDownloader,
databaseDAO: DatabaseDAO
):PlatformQueryResult {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
)
with(result) {
try {
log("YT Video", searchId)
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(), "", true)
?: detail?.title() ?: ""
log("YT View Model", detail.toString())
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArt = File(imageDir(), "$searchId.jpeg"),
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (File(
finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder
)
).exists()
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFile = finalOutputDir(name, folderType, subFolder, ".m4a"),
videoID = searchId
)
)
title = name
withContext(Dispatchers.IO) {
databaseDAO.insert(
DownloadRecord(
type = "Track",
name = if (name.length > 17) {
"${name.subSequence(0, 16)}..."
} else {
name
},
link = "https://www.youtube.com/watch?v=$searchId",
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
totalFiles = 1,
)
)
}
queryActiveTracks()
} catch (e: Exception) {
showDialog("An Error Occurred While Processing!")
}
}
return result
}

View File

@ -1,163 +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.ui.platforms.youtube
import android.annotation.SuppressLint
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.viewModelScope
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.ui.base.TrackListViewModel
import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.Provider.imageDir
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class YoutubeViewModel @ViewModelInject constructor(
private val databaseDAO: DatabaseDAO,
private val ytDownloader: YoutubeDownloader
) : TrackListViewModel(){
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
override var folderType = "YT_Downloads"
override var subFolder = ""
fun getYTPlaylist(searchId:String){
if(!isOnline())return
try{
viewModelScope.launch(Dispatchers.IO) {
log("YT Playlist",searchId)
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details()
val name = playlistDetails.title()
subFolder = removeIllegalChars(name)
val videos = playlist.videos()
coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg")
title.postValue(
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
)
this@YoutubeViewModel.trackList.postValue(videos.map {
TrackDetails(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArt = File(
imageDir() + it.videoId() + ".jpeg"
),
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (File(
finalOutputDir(
itemName = it.title(),
type = folderType,
subFolder = subFolder
)).exists()
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFile = finalOutputDir(it.title(),folderType, subFolder,".m4a"),
videoID = it.videoId()
)
}.toMutableList())
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "PlayList",
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
link = "https://www.youtube.com/playlist?list=$searchId",
coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg",
totalFiles = videos.size,
))
}
queryActiveTracks()
}
}catch (e:Exception){
showDialog("An Error Occurred While Processing!")
}
}
@SuppressLint("DefaultLocale")
fun getYTTrack(searchId:String) {
if(!isOnline())return
try{
viewModelScope.launch(Dispatchers.IO) {
log("YT Video",searchId)
val video = ytDownloader.getVideo(searchId)
coverUrl.postValue("https://i.ytimg.com/vi/$searchId/hqdefault.jpg")
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
log("YT View Model",detail.toString())
this@YoutubeViewModel.trackList.postValue(
listOf(
TrackDetails(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds()?:0,
albumArt = File(imageDir(),"$searchId.jpeg"),
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (File(
finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder
)).exists()
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFile = finalOutputDir(name,folderType, subFolder,".m4a"),
videoID = searchId
)
).toMutableList()
)
title.postValue(
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
)
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Track",
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
link = "https://www.youtube.com/watch?v=$searchId",
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
totalFiles = 1,
))
}
queryActiveTracks()
}
} catch (e:Exception){
showDialog("An Error Occurred While Processing!")
}
}
}

View File

@ -21,6 +21,9 @@ import java.io.File
val mainActivity val mainActivity
get() = MainActivity.getInstance() get() = MainActivity.getInstance()
val sharedViewModel
get() = MainActivity.getSharedViewModel()
fun loadAllImages(context: Context? = mainActivity, images:List<String>? = null,source: Source) { fun loadAllImages(context: Context? = mainActivity, images:List<String>? = null,source: Source) {
val serviceIntent = Intent(context, ForegroundService::class.java) val serviceIntent = Intent(context, ForegroundService::class.java)
images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList<String>) } images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList<String>) }