mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
Added Providers.
This commit is contained in:
parent
dcfca42c40
commit
86431dbc1c
@ -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{
|
||||||
|
@ -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>()
|
||||||
}
|
}
|
@ -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>
|
||||||
|
)
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
|
||||||
}
|
}
|
@ -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))
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@ -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")
|
||||||
|
)
|
||||||
|
}
|
@ -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()
|
|
||||||
}
|
|
@ -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&_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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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")
|
||||||
|
)
|
||||||
|
}
|
@ -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()
|
|
||||||
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
@ -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!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>) }
|
||||||
|
Loading…
Reference in New Issue
Block a user