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
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.PowerManager
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -17,15 +24,25 @@ import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.vectorResource
import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.shabinder.spotiflyer.navigation.ComposeNavigation
import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
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 dev.chrisbanes.accompanist.insets.ProvideWindowInsets
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 com.shabinder.spotiflyer.utils.showDialog as showDialog1
/*
* This is App's God Activity
@ -34,13 +51,15 @@ import javax.inject.Inject
class MainActivity : AppCompatActivity() {
private var spotifyService : SpotifyService? = null
@Inject lateinit var moshi: Moshi
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
// This app draws behind the system bars, so we want to handle fitting system windows
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ComposeLearnTheme {
ProvideWindowInsets {
@ -60,8 +79,103 @@ class MainActivity : AppCompatActivity() {
}
}
}
initialize()
}
private fun initialize() {
authenticateSpotify()
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{

View File

@ -17,11 +17,21 @@
package com.shabinder.spotiflyer
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
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 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 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
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.navArgument
import androidx.navigation.compose.rememberNavController
import androidx.navigation.compose.*
import androidx.navigation.compose.popUpTo
import com.shabinder.spotiflyer.ui.home.Home
import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana
import com.shabinder.spotiflyer.ui.platforms.spotify.Spotify
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
fun ComposeNavigation() {
@ -31,7 +33,7 @@ fun ComposeNavigation() {
arguments = listOf(navArgument("link") { type = NavType.StringType })
) {
Spotify(
link = it.arguments?.getString("link") ?: "error",
fullLink = it.arguments?.getString("link") ?: "error",
navController = navController
)
}
@ -43,7 +45,7 @@ fun ComposeNavigation() {
arguments = listOf(navArgument("link") { type = NavType.StringType })
) {
Gaana(
link = it.arguments?.getString("link") ?: "error",
fullLink = it.arguments?.getString("link") ?: "error",
navController = navController
)
}
@ -55,9 +57,42 @@ fun ComposeNavigation() {
arguments = listOf(navArgument("link") { type = NavType.StringType })
) {
Youtube(
link = it.arguments?.getString("link") ?: "error",
fullLink = it.arguments?.getString("link") ?: "error",
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
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.models.TrackDetails
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
abstract class TrackListViewModel:ViewModel() {
abstract var folderType: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!"
open var title = MutableLiveData<String>().apply { value = loading }
open var coverUrl = MutableLiveData<String>()
open var title = MutableStateFlow(loading)
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.navigation.NavController
import androidx.navigation.compose.navigate
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.navigation.navigateToPlatform
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent
import com.shabinder.spotiflyer.ui.colorPrimary
import com.shabinder.spotiflyer.utils.mainActivity
import com.shabinder.spotiflyer.utils.openPlatform
import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog
@Composable
fun Home(navController: NavController, modifier: Modifier = Modifier) {
@ -289,7 +294,7 @@ fun SearchPanel(
)
OutlinedButton(
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)))
){
Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp))

View File

@ -1,8 +1,45 @@
package com.shabinder.spotiflyer.ui.platforms.gaana
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavController
import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.launch
@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
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 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
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
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
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
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
get() = MainActivity.getInstance()
val sharedViewModel
get() = MainActivity.getSharedViewModel()
fun loadAllImages(context: Context? = mainActivity, images:List<String>? = null,source: Source) {
val serviceIntent = Intent(context, ForegroundService::class.java)
images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList<String>) }