mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 20:57:54 +01:00
Modularisation, DI, and fixes
This commit is contained in:
parent
86fd6a5123
commit
748a60adad
@ -15,8 +15,6 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -34,16 +32,16 @@ import com.github.javiersantos.appupdater.enums.Display
|
||||
import com.github.javiersantos.appupdater.enums.UpdateFrom
|
||||
import com.razorpay.Checkout
|
||||
import com.razorpay.PaymentResultListener
|
||||
import com.shabinder.spotiflyer.di.Directories
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
||||
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
|
||||
import com.shabinder.spotiflyer.ui.appNameStyle
|
||||
import com.shabinder.spotiflyer.ui.colorOffWhite
|
||||
import com.shabinder.spotiflyer.utils.*
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tonyodev.fetch2.Status
|
||||
import dagger.hilt.EntryPoints
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
|
||||
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
||||
@ -54,13 +52,12 @@ import javax.inject.Inject
|
||||
* This is App's God Activity
|
||||
* */
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), PaymentResultListener {
|
||||
class MainActivity: AppCompatActivity(), PaymentResultListener {
|
||||
|
||||
private lateinit var navController: NavHostController
|
||||
private lateinit var updateUIReceiver: BroadcastReceiver
|
||||
private lateinit var queryReceiver: BroadcastReceiver
|
||||
@Inject lateinit var moshi: Moshi
|
||||
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
|
||||
@Inject lateinit var directories: Directories
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -72,15 +69,14 @@ class MainActivity : AppCompatActivity(), PaymentResultListener {
|
||||
ComposeLearnTheme {
|
||||
Providers(AmbientContentColor provides colorOffWhite) {
|
||||
ProvideWindowInsets {
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.7f)
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.65f)
|
||||
navController = rememberNavController()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
||||
color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
||||
startYPercentage = 1f,
|
||||
startYPercentage = 0.29f,
|
||||
endYPercentage = 0f,
|
||||
fixedHeight = 700f,
|
||||
)
|
||||
) {
|
||||
// Draw a scrim over the status bar which matches the app bar
|
||||
@ -92,7 +88,13 @@ class MainActivity : AppCompatActivity(), PaymentResultListener {
|
||||
backgroundColor = appBarColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
ComposeNavigation(navController)
|
||||
ComposeNavigation(
|
||||
this@MainActivity,
|
||||
navController,
|
||||
sharedViewModel.spotifyProvider,
|
||||
sharedViewModel.gaanaProvider,
|
||||
sharedViewModel.youtubeProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,7 +108,7 @@ class MainActivity : AppCompatActivity(), PaymentResultListener {
|
||||
requestStoragePermission()
|
||||
disableDozeMode()
|
||||
checkIfLatestVersion()
|
||||
createDirectories()
|
||||
createDirectories(directories.defaultDir(),directories.imageDir())
|
||||
handleIntentFromExternalActivity()
|
||||
}
|
||||
|
||||
@ -296,6 +298,7 @@ fun AppBar(
|
||||
|
||||
|
||||
//@Preview(showBackground = true)
|
||||
/*
|
||||
@Composable
|
||||
fun DefaultPreview() {
|
||||
ComposeLearnTheme {
|
||||
@ -315,4 +318,4 @@ fun DefaultPreview() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
@ -18,18 +18,22 @@
|
||||
package com.shabinder.spotiflyer
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||
import com.shabinder.spotiflyer.providers.GaanaProvider
|
||||
import com.shabinder.spotiflyer.providers.SpotifyProvider
|
||||
import com.shabinder.spotiflyer.providers.YoutubeProvider
|
||||
import com.shabinder.spotiflyer.ui.colorPrimaryDark
|
||||
import com.shabinder.spotiflyer.utils.log
|
||||
import com.tonyodev.fetch2.Status
|
||||
@ -38,7 +42,10 @@ class SharedViewModel @ViewModelInject constructor(
|
||||
val databaseDAO: DatabaseDAO,
|
||||
val spotifyService: SpotifyService,
|
||||
val gaanaInterface : GaanaInterface,
|
||||
val ytDownloader: YoutubeDownloader
|
||||
val ytDownloader: YoutubeDownloader,
|
||||
val gaanaProvider: GaanaProvider,
|
||||
val spotifyProvider: SpotifyProvider,
|
||||
val youtubeProvider: YoutubeProvider
|
||||
) : ViewModel() {
|
||||
var isAuthenticated by mutableStateOf(false)
|
||||
private set
|
||||
@ -110,7 +117,7 @@ class SharedViewModel @ViewModelInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
var gradientColor by mutableStateOf(colorPrimaryDark)
|
||||
var gradientColor by mutableStateOf(Color.Transparent)
|
||||
private set
|
||||
|
||||
fun updateGradientColor(color: Color) {
|
||||
|
13
app/src/main/java/com/shabinder/spotiflyer/di/Directories.kt
Normal file
13
app/src/main/java/com/shabinder/spotiflyer/di/Directories.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package com.shabinder.spotiflyer.di
|
||||
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface Directories {
|
||||
@DefaultDir fun defaultDir():String
|
||||
@ImageDir fun imageDir():String
|
||||
}
|
@ -1,19 +1,15 @@
|
||||
package com.shabinder.spotiflyer.utils
|
||||
package com.shabinder.spotiflyer.di
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.util.Base64
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.shabinder.spotiflyer.App
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
|
||||
import com.shabinder.spotiflyer.networking.*
|
||||
import com.shabinder.spotiflyer.utils.NetworkInterceptor
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
@ -21,32 +17,13 @@ import okhttp3.Request
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object Provider {
|
||||
|
||||
//Default Directory to save Media in their Own Categorized Folders
|
||||
@Suppress("DEPRECATION")// We Do Have Media Access (But Just Media in Media Directory,Not Anything Else)
|
||||
val defaultDir = Environment.getExternalStorageDirectory().toString() + File.separator +
|
||||
Environment.DIRECTORY_MUSIC + File.separator +
|
||||
"SpotiFlyer"+ File.separator
|
||||
|
||||
//Default Cache Directory to save Album Art to use them for writing in Media Later
|
||||
fun imageDir(ctx: Context = mainActivity): String = ctx
|
||||
.externalCacheDir?.absolutePath + File.separator +
|
||||
".Images" + File.separator
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun databaseDAO(@ApplicationContext appContext: Context): DatabaseDAO {
|
||||
return DownloadRecordDatabase.getInstance(appContext).databaseDAO
|
||||
}
|
||||
object NetworkUtilProvider {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ -54,12 +31,6 @@ object Provider {
|
||||
return YoutubeDownloader()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun getTokenStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
spotifyServiceTokenRequest: SpotifyServiceTokenRequest):TokenStore = TokenStore(appContext,spotifyServiceTokenRequest)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun getSpotifyService(authInterceptor: SpotifyAuthInterceptor,okHttpClient: OkHttpClient.Builder,moshi: Moshi) :SpotifyService{
|
||||
@ -122,10 +93,6 @@ object Provider {
|
||||
return retrofit.create(YoutubeMusicApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun getNetworkInterceptor():NetworkInterceptor = NetworkInterceptor()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun okHttpClient(networkInterceptor: NetworkInterceptor): OkHttpClient.Builder {
|
@ -0,0 +1,67 @@
|
||||
package com.shabinder.spotiflyer.di
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
|
||||
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||
import com.shabinder.spotiflyer.utils.TokenStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.EntryPoints
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import java.io.File
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object UtilProvider {
|
||||
|
||||
//Default Cache Directory to save Album Art to use them for writing in Media Later
|
||||
//Gets Cleaned After Service Destroy
|
||||
@Provides
|
||||
@Singleton
|
||||
@ImageDir fun imageDir(@ApplicationContext appContext: Context):String =
|
||||
appContext.cacheDir.absolutePath + File.separator
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Suppress("DEPRECATION")
|
||||
//Default Directory to save Media in their Own Categorized Folders
|
||||
@DefaultDir fun defaultDir(@ApplicationContext appContext: Context):String =
|
||||
appContext.externalMediaDirs[0].absolutePath +
|
||||
File.separator
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun databaseDAO(@ApplicationContext appContext: Context): DatabaseDAO {
|
||||
return DownloadRecordDatabase.getInstance(appContext).databaseDAO
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDirectories(@ApplicationContext appContext: Context):Directories = EntryPoints.get(appContext, Directories::class.java)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun getTokenStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
spotifyServiceTokenRequest: SpotifyServiceTokenRequest
|
||||
): TokenStore = TokenStore(appContext,spotifyServiceTokenRequest)
|
||||
}
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DefaultDir
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ImageDir
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class FinalOutputDir
|
@ -5,13 +5,22 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.*
|
||||
import androidx.navigation.compose.popUpTo
|
||||
import com.shabinder.spotiflyer.MainActivity
|
||||
import com.shabinder.spotiflyer.providers.GaanaProvider
|
||||
import com.shabinder.spotiflyer.providers.SpotifyProvider
|
||||
import com.shabinder.spotiflyer.providers.YoutubeProvider
|
||||
import com.shabinder.spotiflyer.ui.home.Home
|
||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
|
||||
@Composable
|
||||
fun ComposeNavigation(navController: NavHostController) {
|
||||
fun ComposeNavigation(
|
||||
mainActivity: MainActivity,
|
||||
navController: NavHostController,
|
||||
spotifyProvider: SpotifyProvider,
|
||||
gaanaProvider: GaanaProvider,
|
||||
youtubeProvider: YoutubeProvider
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "home"
|
||||
@ -19,7 +28,7 @@ fun ComposeNavigation(navController: NavHostController) {
|
||||
|
||||
//HomeScreen - Starting Point
|
||||
composable("home") {
|
||||
Home(navController = navController)
|
||||
Home(navController = navController, mainActivity)
|
||||
}
|
||||
|
||||
//Spotify Screen
|
||||
@ -30,7 +39,10 @@ fun ComposeNavigation(navController: NavHostController) {
|
||||
) {
|
||||
TrackList(
|
||||
fullLink = it.arguments?.getString("link") ?: "error",
|
||||
navController = navController
|
||||
navController = navController,
|
||||
spotifyProvider,
|
||||
gaanaProvider,
|
||||
youtubeProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ import androidx.compose.runtime.setValue
|
||||
import com.shabinder.spotiflyer.models.spotify.Token
|
||||
import com.shabinder.spotiflyer.utils.TokenStore
|
||||
import com.shabinder.spotiflyer.utils.log
|
||||
import com.shabinder.spotiflyer.utils.showDialog
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
@ -17,217 +17,234 @@
|
||||
|
||||
package com.shabinder.spotiflyer.providers
|
||||
|
||||
import android.content.Context
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.di.DefaultDir
|
||||
import com.shabinder.spotiflyer.di.ImageDir
|
||||
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.*
|
||||
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 com.shabinder.spotiflyer.utils.showDialog
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
|
||||
@Singleton
|
||||
class GaanaProvider @Inject constructor(
|
||||
@DefaultDir private val defaultDir: String,
|
||||
@ImageDir private val imageDir: String,
|
||||
private val gaanaInterface: GaanaInterface,
|
||||
private val databaseDAO: DatabaseDAO,
|
||||
@ApplicationContext private val ctx : Context
|
||||
){
|
||||
private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
|
||||
|
||||
suspend fun queryGaana(
|
||||
fullLink: String,
|
||||
):PlatformQueryResult?{
|
||||
suspend fun queryGaana(
|
||||
fullLink: String,
|
||||
):PlatformQueryResult?{
|
||||
|
||||
//Link Schema: https://gaana.com/type/link
|
||||
val gaanaLink = fullLink.substringAfter("gaana.com/")
|
||||
//Link Schema: https://gaana.com/type/link
|
||||
val gaanaLink = fullLink.substringAfter("gaana.com/")
|
||||
|
||||
val link = gaanaLink.substringAfterLast('/', "error")
|
||||
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
|
||||
val link = gaanaLink.substringAfterLast('/', "error")
|
||||
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
|
||||
|
||||
log("Gaana Fragment", "$type : $link")
|
||||
log("Gaana Fragment", "$type : $link")
|
||||
|
||||
//Error
|
||||
if (type == "Error" || link == "Error"){
|
||||
showDialog("Please Check Your Link!")
|
||||
return null
|
||||
}
|
||||
return gaanaSearch(
|
||||
type,
|
||||
link,
|
||||
sharedViewModel.gaanaInterface,
|
||||
sharedViewModel.databaseDAO,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun gaanaSearch(
|
||||
type:String,
|
||||
link:String,
|
||||
gaanaInterface: GaanaInterface,
|
||||
databaseDAO: DatabaseDAO,
|
||||
): PlatformQueryResult {
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = link,
|
||||
title = link,
|
||||
coverUrl = gaanaPlaceholderImageUrl,
|
||||
trackList = listOf(),
|
||||
Source.Gaana
|
||||
)
|
||||
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}
|
||||
}
|
||||
//Error
|
||||
if (type == "Error" || link == "Error"){
|
||||
showDialog("Please Check Your Link!")
|
||||
return null
|
||||
}
|
||||
queryActiveTracks()
|
||||
return result
|
||||
return gaanaSearch(
|
||||
type,
|
||||
link
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun gaanaSearch(
|
||||
type:String,
|
||||
link:String,
|
||||
): PlatformQueryResult {
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = link,
|
||||
title = link,
|
||||
coverUrl = gaanaPlaceholderImageUrl,
|
||||
trackList = listOf(),
|
||||
Source.Gaana
|
||||
)
|
||||
with(result) {
|
||||
when (type) {
|
||||
"song" -> {
|
||||
gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
|
||||
folderType = "Tracks"
|
||||
subFolder = ""
|
||||
if (File(
|
||||
finalOutputDir(
|
||||
it.track_title,
|
||||
folderType,
|
||||
subFolder,
|
||||
defaultDir
|
||||
)
|
||||
).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,
|
||||
defaultDir
|
||||
)
|
||||
).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,
|
||||
defaultDir
|
||||
)
|
||||
).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,
|
||||
defaultDir
|
||||
)
|
||||
).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(ctx)
|
||||
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,defaultDir,".m4a")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
)
|
||||
}
|
@ -17,10 +17,12 @@
|
||||
|
||||
package com.shabinder.spotiflyer.providers
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.compose.runtime.Composable
|
||||
import android.content.Context
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.di.DefaultDir
|
||||
import com.shabinder.spotiflyer.di.Directories
|
||||
import com.shabinder.spotiflyer.di.ImageDir
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
@ -30,232 +32,249 @@ 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 com.shabinder.spotiflyer.utils.finalOutputDir
|
||||
import com.shabinder.spotiflyer.utils.log
|
||||
import com.shabinder.spotiflyer.utils.queryActiveTracks
|
||||
import com.shabinder.spotiflyer.utils.showDialog
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
suspend fun querySpotify(fullLink: String):PlatformQueryResult?{
|
||||
var spotifyLink =
|
||||
"https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim()
|
||||
@Singleton
|
||||
class SpotifyProvider @Inject constructor(
|
||||
private val directories: Directories,
|
||||
private val spotifyService: SpotifyService,
|
||||
private val gaanaInterface: GaanaInterface,
|
||||
private val databaseDAO: DatabaseDAO,
|
||||
@ApplicationContext private val ctx : Context
|
||||
) {
|
||||
private val defaultDir
|
||||
get() = directories.defaultDir()
|
||||
private val imageDir
|
||||
get() = directories.imageDir()
|
||||
|
||||
if (!spotifyLink.contains("open.spotify")) {
|
||||
//Very Rare instance
|
||||
spotifyLink = resolveLink(spotifyLink, sharedViewModel.gaanaInterface)
|
||||
suspend fun querySpotify(fullLink: String):PlatformQueryResult?{
|
||||
var spotifyLink =
|
||||
"https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim()
|
||||
|
||||
if (!spotifyLink.contains("open.spotify")) {
|
||||
//Very Rare instance
|
||||
spotifyLink = resolveLink(spotifyLink)
|
||||
}
|
||||
|
||||
|
||||
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
||||
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
|
||||
|
||||
log("Spotify Fragment", "$type : $link")
|
||||
|
||||
if (type == "Error" || link == "Error") {
|
||||
showDialog("Please Check Your Link!")
|
||||
return null
|
||||
}
|
||||
|
||||
if (type == "episode" || type == "show") {
|
||||
//TODO Implementation
|
||||
showDialog("Implementing Soon, Stay Tuned!")
|
||||
return null
|
||||
}
|
||||
|
||||
return spotifySearch(
|
||||
type,
|
||||
link
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
||||
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
|
||||
|
||||
log("Spotify Fragment", "$type : $link")
|
||||
|
||||
if (type == "Error" || link == "Error") {
|
||||
showDialog("Please Check Your Link!")
|
||||
return null
|
||||
}
|
||||
|
||||
if (type == "episode" || type == "show") {
|
||||
//TODO Implementation
|
||||
showDialog("Implementing Soon, Stay Tuned!")
|
||||
return null
|
||||
}
|
||||
|
||||
return spotifySearch(
|
||||
type,
|
||||
link,
|
||||
sharedViewModel.spotifyService,
|
||||
sharedViewModel.databaseDAO
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun spotifySearch(
|
||||
type:String,
|
||||
link: String,
|
||||
spotifyService: SpotifyService,
|
||||
databaseDAO: DatabaseDAO
|
||||
): PlatformQueryResult {
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.Spotify
|
||||
)
|
||||
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 ->
|
||||
private suspend fun spotifySearch(
|
||||
type:String,
|
||||
link: String
|
||||
): PlatformQueryResult {
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.Spotify
|
||||
)
|
||||
with(result) {
|
||||
when (type) {
|
||||
"track" -> {
|
||||
spotifyService.getTrack(link).value?.also {
|
||||
folderType = "Tracks"
|
||||
subFolder = ""
|
||||
if (File(
|
||||
finalOutputDir(
|
||||
it1.name.toString(),
|
||||
it.name.toString(),
|
||||
folderType,
|
||||
subFolder
|
||||
subFolder,
|
||||
defaultDir
|
||||
)
|
||||
).exists()
|
||||
) {//Download Already Present!!
|
||||
it1.downloaded = DownloadStatus.Downloaded
|
||||
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,
|
||||
)
|
||||
)
|
||||
}
|
||||
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,
|
||||
"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,
|
||||
defaultDir
|
||||
)
|
||||
).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,
|
||||
defaultDir
|
||||
)
|
||||
).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
|
||||
}
|
||||
}
|
||||
"episode" -> {//TODO
|
||||
}
|
||||
"show" -> {//TODO
|
||||
}
|
||||
else -> {
|
||||
//TODO Handle Error
|
||||
}
|
||||
}
|
||||
queryActiveTracks(ctx)
|
||||
return result
|
||||
}
|
||||
queryActiveTracks()
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
|
||||
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630
|
||||
* */
|
||||
@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()
|
||||
}
|
||||
/*
|
||||
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
|
||||
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630
|
||||
* */
|
||||
private 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()
|
||||
}
|
||||
|
||||
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")
|
||||
)
|
||||
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,defaultDir,".m4a")
|
||||
)
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.downloadHelper
|
||||
package com.shabinder.spotiflyer.providers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.beust.klaxon.JsonArray
|
@ -18,214 +18,224 @@
|
||||
package com.shabinder.spotiflyer.providers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.di.DefaultDir
|
||||
import com.shabinder.spotiflyer.di.Directories
|
||||
import com.shabinder.spotiflyer.di.ImageDir
|
||||
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 dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/*
|
||||
* 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"
|
||||
* */
|
||||
@Singleton
|
||||
class YoutubeProvider @Inject constructor(
|
||||
private val directories: Directories,
|
||||
private val ytDownloader: YoutubeDownloader,
|
||||
private val databaseDAO: DatabaseDAO,
|
||||
@ApplicationContext private val ctx : Context,
|
||||
) {
|
||||
/*
|
||||
* 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"
|
||||
* */
|
||||
private val sampleDomain2 = "youtu.be"
|
||||
private val sampleDomain1 = "youtube.com"
|
||||
|
||||
private const val sampleDomain2 = "youtu.be"
|
||||
private const val sampleDomain1 = "youtube.com"
|
||||
private val defaultDir
|
||||
get() = directories.defaultDir()
|
||||
private val imageDir
|
||||
get() = directories.imageDir()
|
||||
|
||||
/*
|
||||
/*
|
||||
* Sending a Result as null = Some Error Occurred!
|
||||
* */
|
||||
suspend fun queryYoutube(fullLink: String): PlatformQueryResult?{
|
||||
val link = fullLink.removePrefix("https://").removePrefix("http://")
|
||||
if(link.contains("playlist",true) || link.contains("list",true)){
|
||||
// Given Link is of a Playlist
|
||||
log("YT Play",link)
|
||||
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
|
||||
return getYTPlaylist(
|
||||
playlistId,
|
||||
sharedViewModel.ytDownloader,
|
||||
sharedViewModel.databaseDAO
|
||||
suspend fun queryYoutube(fullLink: String): PlatformQueryResult?{
|
||||
val link = fullLink.removePrefix("https://").removePrefix("http://")
|
||||
if(link.contains("playlist",true) || link.contains("list",true)){
|
||||
// Given Link is of a Playlist
|
||||
log("YT Play",link)
|
||||
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
|
||||
return getYTPlaylist(
|
||||
playlistId
|
||||
)
|
||||
}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")
|
||||
}
|
||||
return if(searchId != "error") {
|
||||
getYTTrack(
|
||||
searchId
|
||||
)
|
||||
}else{
|
||||
showDialog("Your Youtube Link is not of a Video!!")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getYTPlaylist(
|
||||
searchId: String
|
||||
):PlatformQueryResult?{
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.YouTube
|
||||
)
|
||||
}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")
|
||||
}
|
||||
return if(searchId != "error") {
|
||||
getYTTrack(
|
||||
searchId,
|
||||
sharedViewModel.ytDownloader,
|
||||
sharedViewModel.databaseDAO
|
||||
)
|
||||
}else{
|
||||
showDialog("Your Youtube Link is not of a Video!!")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
||||
suspend fun getYTPlaylist(
|
||||
searchId: String,
|
||||
ytDownloader: YoutubeDownloader,
|
||||
databaseDAO: DatabaseDAO,
|
||||
):PlatformQueryResult?{
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.YouTube
|
||||
)
|
||||
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
|
||||
|
||||
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
|
||||
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,
|
||||
defaultDir
|
||||
)
|
||||
).exists()
|
||||
)
|
||||
DownloadStatus.Downloaded
|
||||
else {
|
||||
DownloadStatus.NotDownloaded
|
||||
},
|
||||
link = "https://www.youtube.com/playlist?list=$searchId",
|
||||
coverUrl = "https://i.ytimg.com/vi/${
|
||||
videos.firstOrNull()?.videoId()
|
||||
}/hqdefault.jpg",
|
||||
totalFiles = videos.size,
|
||||
outputFile = finalOutputDir(it.title(), folderType, subFolder, defaultDir,".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(ctx)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
showDialog("An Error Occurred While Processing!")
|
||||
}
|
||||
queryActiveTracks()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
showDialog("An Error Occurred While Processing!")
|
||||
}
|
||||
}
|
||||
return if(result.title.isNotBlank()) result
|
||||
return if(result.title.isNotBlank()) result
|
||||
else null
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
suspend fun getYTTrack(
|
||||
searchId:String,
|
||||
ytDownloader: YoutubeDownloader,
|
||||
databaseDAO: DatabaseDAO
|
||||
):PlatformQueryResult? {
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.YouTube
|
||||
)
|
||||
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) {
|
||||
e.printStackTrace()
|
||||
showDialog("An Error Occurred While Processing!")
|
||||
}
|
||||
}
|
||||
return if(result.title.isNotBlank()) result
|
||||
else null
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private suspend fun getYTTrack(
|
||||
searchId:String,
|
||||
):PlatformQueryResult? {
|
||||
val result = PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.YouTube
|
||||
)
|
||||
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,
|
||||
defaultDir = defaultDir
|
||||
)
|
||||
).exists()
|
||||
)
|
||||
DownloadStatus.Downloaded
|
||||
else {
|
||||
DownloadStatus.NotDownloaded
|
||||
},
|
||||
outputFile = finalOutputDir(name, folderType, subFolder, defaultDir,".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(ctx)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
showDialog("An Error Occurred While Processing!")
|
||||
}
|
||||
}
|
||||
return if(result.title.isNotBlank()) result
|
||||
else null
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package com.shabinder.spotiflyer.ui
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.font.font
|
||||
import androidx.compose.ui.text.font.fontFamily
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.shabinder.spotiflyer.R
|
||||
|
||||
|
@ -14,14 +14,17 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.material.icons.rounded.CardGiftcard
|
||||
import androidx.compose.material.icons.rounded.Flag
|
||||
import androidx.compose.material.icons.rounded.InsertLink
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.viewinterop.viewModel
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.AmbientContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@ -29,21 +32,26 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.viewModel
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.NavController
|
||||
import com.razorpay.Checkout
|
||||
import com.shabinder.spotiflyer.MainActivity
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||
import com.shabinder.spotiflyer.ui.colorAccent
|
||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
||||
import com.shabinder.spotiflyer.utils.*
|
||||
import com.shabinder.spotiflyer.utils.isOnline
|
||||
import com.shabinder.spotiflyer.utils.openPlatform
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import com.shabinder.spotiflyer.utils.showDialog
|
||||
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||
import org.json.JSONObject
|
||||
|
||||
@Composable
|
||||
fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
||||
fun Home(navController: NavController, mainActivity: MainActivity, modifier: Modifier = Modifier) {
|
||||
val viewModel: HomeViewModel = viewModel()
|
||||
|
||||
Column(modifier = modifier) {
|
||||
@ -65,7 +73,7 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
||||
)
|
||||
|
||||
when(viewModel.selectedCategory){
|
||||
HomeCategory.About -> AboutColumn()
|
||||
HomeCategory.About -> AboutColumn(mainActivity)
|
||||
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
||||
}
|
||||
}
|
||||
@ -77,7 +85,8 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
||||
|
||||
|
||||
@Composable
|
||||
fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
fun AboutColumn(mainActivity: MainActivity,modifier: Modifier = Modifier) {
|
||||
val ctx = AmbientContext.current
|
||||
ScrollableColumn(modifier.fillMaxSize(),contentPadding = PaddingValues(16.dp)) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
@ -94,17 +103,17 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
Icon(
|
||||
imageVector = vectorResource(id = R.drawable.ic_spotify_logo), tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.spotify.music","http://open.spotify.com") })
|
||||
onClick = { openPlatform("com.spotify.music","http://open.spotify.com",ctx) })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 24.dp))
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_gaana ),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.gaana","http://gaana.com") })
|
||||
onClick = { openPlatform("com.gaana","http://gaana.com",ctx) })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 24.dp))
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_youtube),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.google.android.youtube","http://m.youtube.com") })
|
||||
onClick = { openPlatform("com.google.android.youtube","http://m.youtube.com",ctx) })
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -123,7 +132,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
Spacer(modifier = Modifier.padding(top = 6.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer") })
|
||||
onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer",ctx) })
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_github ),tint = Color.LightGray)
|
||||
@ -141,7 +150,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer") }),
|
||||
.clickable(onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer", ctx) }),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.Flag.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
@ -159,7 +168,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = { startPayment() }),
|
||||
.clickable(onClick = { startPayment(mainActivity) }),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.CardGiftcard.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
@ -185,7 +194,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
mainActivity.startActivity(shareIntent)
|
||||
ctx.startActivity(shareIntent)
|
||||
}),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@ -245,6 +254,7 @@ fun HistoryColumn(
|
||||
|
||||
@Composable
|
||||
fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
||||
val ctx = AmbientContext.current
|
||||
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
|
||||
val imgUri = item.coverUrl.toUri().buildUpon().scheme("https").build()
|
||||
CoilImage(
|
||||
@ -270,14 +280,14 @@ fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
||||
Image(
|
||||
imageVector = vectorResource(id = R.drawable.ic_share_open),
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
if(!isOnline()) showDialog("Check Your Internet Connection")
|
||||
if(!isOnline(ctx)) showDialog("Check Your Internet Connection")
|
||||
else navController.navigateToTrackList(item.link)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPayment() {
|
||||
private fun startPayment(mainActivity: MainActivity) {
|
||||
/*
|
||||
* You need to pass current activity in order to let Razorpay create CheckoutActivity
|
||||
* */
|
||||
@ -365,7 +375,7 @@ fun SearchPanel(
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier
|
||||
){
|
||||
|
||||
val ctx = AmbientContext.current
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.padding(top = 16.dp)
|
||||
@ -395,7 +405,7 @@ fun SearchPanel(
|
||||
onClick = {
|
||||
if(link.isBlank()) showDialog("Enter A Link!")
|
||||
else{
|
||||
if(!isOnline()) showDialog("Check Your Internet Connection")
|
||||
if(!isOnline(ctx)) showDialog("Check Your Internet Connection")
|
||||
else navController.navigateToTrackList(link)
|
||||
}
|
||||
},
|
||||
|
@ -1,16 +1,16 @@
|
||||
package com.shabinder.spotiflyer.ui.home
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
package com.shabinder.spotiflyer.ui.tracklist
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@ -14,6 +12,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.AmbientContext
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@ -25,14 +24,13 @@ import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
import com.shabinder.spotiflyer.providers.GaanaProvider
|
||||
import com.shabinder.spotiflyer.providers.SpotifyProvider
|
||||
import com.shabinder.spotiflyer.providers.YoutubeProvider
|
||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||
import com.shabinder.spotiflyer.ui.colorAccent
|
||||
import com.shabinder.spotiflyer.providers.queryGaana
|
||||
import com.shabinder.spotiflyer.providers.querySpotify
|
||||
import com.shabinder.spotiflyer.providers.queryYoutube
|
||||
import com.shabinder.spotiflyer.ui.utils.calculateDominantColor
|
||||
import com.shabinder.spotiflyer.utils.downloadTracks
|
||||
import com.shabinder.spotiflyer.utils.log
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import com.shabinder.spotiflyer.utils.showDialog
|
||||
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||
@ -45,6 +43,9 @@ import kotlinx.coroutines.*
|
||||
fun TrackList(
|
||||
fullLink: String,
|
||||
navController: NavController,
|
||||
spotifyProvider: SpotifyProvider,
|
||||
gaanaProvider: GaanaProvider,
|
||||
youtubeProvider: YoutubeProvider,
|
||||
modifier: Modifier = Modifier
|
||||
){
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@ -60,13 +61,16 @@ fun TrackList(
|
||||
* Using SharedViewModel's Link as NAVIGATION's Arg is buggy for links.
|
||||
* */
|
||||
//SPOTIFY
|
||||
sharedViewModel.link.contains("spotify",true) -> querySpotify(sharedViewModel.link)
|
||||
sharedViewModel.link.contains("spotify",true) ->
|
||||
spotifyProvider.querySpotify(sharedViewModel.link)
|
||||
|
||||
//YOUTUBE
|
||||
sharedViewModel.link.contains("youtube.com",true) || sharedViewModel.link.contains("youtu.be",true) -> queryYoutube(sharedViewModel.link)
|
||||
sharedViewModel.link.contains("youtube.com",true) || sharedViewModel.link.contains("youtu.be",true) ->
|
||||
youtubeProvider.queryYoutube(sharedViewModel.link)
|
||||
|
||||
//GAANA
|
||||
sharedViewModel.link.contains("gaana",true) -> queryGaana(sharedViewModel.link)
|
||||
sharedViewModel.link.contains("gaana",true) ->
|
||||
gaanaProvider.queryGaana(sharedViewModel.link)
|
||||
|
||||
else -> {
|
||||
showDialog("Link is Not Valid")
|
||||
@ -83,6 +87,7 @@ fun TrackList(
|
||||
sharedViewModel.updateTrackList(result?.trackList ?: listOf())
|
||||
|
||||
result?.let{
|
||||
val ctx = AmbientContext.current
|
||||
Box(modifier = modifier.fillMaxSize()){
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
@ -94,7 +99,7 @@ fun TrackList(
|
||||
TrackCard(
|
||||
track = item,
|
||||
onDownload = {
|
||||
downloadTracks(arrayListOf(item))
|
||||
downloadTracks(arrayListOf(item),ctx)
|
||||
sharedViewModel.updateTrackStatus(index,DownloadStatus.Queued)
|
||||
},
|
||||
)
|
||||
@ -106,7 +111,7 @@ fun TrackList(
|
||||
onClick = {
|
||||
val finalList = sharedViewModel.trackList.filter{it.downloaded == DownloadStatus.NotDownloaded}
|
||||
if (finalList.isNullOrEmpty()) showDialog("Not Downloading Any Song")
|
||||
else downloadTracks(finalList as ArrayList<TrackDetails>)
|
||||
else downloadTracks(finalList as ArrayList<TrackDetails>,ctx)
|
||||
for (track in sharedViewModel.trackList) {
|
||||
if (track.downloaded == DownloadStatus.NotDownloaded) {
|
||||
track.downloaded = DownloadStatus.Queued
|
||||
@ -126,12 +131,14 @@ fun CoverImage(
|
||||
scope: CoroutineScope,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val ctx = AmbientContext.current
|
||||
Column(
|
||||
modifier.padding(vertical = 8.dp).fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
val imgUri = coverURL.toUri().buildUpon().scheme("https").build()
|
||||
CoilImage(
|
||||
data = coverURL,
|
||||
data = imgUri,
|
||||
contentScale = ContentScale.Crop,
|
||||
loading = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
|
||||
modifier = Modifier
|
||||
@ -149,7 +156,7 @@ fun CoverImage(
|
||||
)
|
||||
}
|
||||
scope.launch {
|
||||
updateGradient(coverURL)
|
||||
updateGradient(coverURL, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,8 +194,8 @@ fun TrackCard(
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
|
||||
){
|
||||
Text("${track.artists.firstOrNull()}...",fontSize = 13.sp)
|
||||
Text("${track.durationSec/60} minutes, ${track.durationSec%60} sec",fontSize = 13.sp)
|
||||
Text("${track.artists.firstOrNull()}...",fontSize = 12.sp,maxLines = 1)
|
||||
Text("${track.durationSec/60} min, ${track.durationSec%60} sec",fontSize = 12.sp,maxLines = 1,overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
when(track.downloaded){
|
||||
@ -216,7 +223,7 @@ fun TrackCard(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateGradient(imageURL:String){
|
||||
calculateDominantColor(imageURL)?.color
|
||||
suspend fun updateGradient(imageURL:String,ctx:Context){
|
||||
calculateDominantColor(imageURL,ctx)?.color
|
||||
?.let { sharedViewModel.updateGradientColor(it) }
|
||||
}
|
@ -17,27 +17,14 @@
|
||||
package com.shabinder.spotiflyer.ui.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.collection.LruCache
|
||||
import androidx.compose.animation.animate
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.AmbientContext
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.palette.graphics.Palette
|
||||
import coil.Coil
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil.size.Scale
|
||||
import com.shabinder.spotiflyer.utils.mainActivity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@ -45,9 +32,9 @@ import kotlinx.coroutines.withContext
|
||||
data class DominantColors(val color: Color, val onColor: Color)
|
||||
|
||||
|
||||
suspend fun calculateDominantColor(url: String): DominantColors? {
|
||||
suspend fun calculateDominantColor(url: String,ctx:Context): DominantColors? {
|
||||
// we calculate the swatches in the image, and return the first valid color
|
||||
return calculateSwatchesInImage(mainActivity, url)
|
||||
return calculateSwatchesInImage(ctx, url)
|
||||
// First we want to sort the list by the color's population
|
||||
.sortedByDescending { swatch -> swatch.population }
|
||||
// Then we want to find the first valid color
|
||||
|
@ -26,7 +26,6 @@ import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.shabinder.spotiflyer.utils.log
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.shabinder.spotiflyer.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
@ -53,23 +54,23 @@ fun YoutubeVideo.getData(): Format?{
|
||||
}
|
||||
}
|
||||
}
|
||||
fun openPlatform(packageName:String, websiteAddress:String){
|
||||
val manager: PackageManager = mainActivity.packageManager
|
||||
fun openPlatform(packageName:String, websiteAddress:String,context: Context){
|
||||
val manager: PackageManager = context.packageManager
|
||||
try {
|
||||
val intent = manager.getLaunchIntentForPackage(packageName)
|
||||
?: throw PackageManager.NameNotFoundException()
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
mainActivity.startActivity(intent)
|
||||
context.startActivity(intent)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
val uri: Uri =
|
||||
Uri.parse(websiteAddress)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
mainActivity.startActivity(intent)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun openPlatform(websiteAddress:String){
|
||||
fun openPlatform(websiteAddress:String,context: Context){
|
||||
val uri = Uri.parse(websiteAddress)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
mainActivity.startActivity(intent)
|
||||
context.startActivity(intent)
|
||||
}
|
@ -17,18 +17,23 @@
|
||||
|
||||
package com.shabinder.spotiflyer.utils
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import javax.inject.Inject
|
||||
|
||||
const val NoInternetErrorCode = 222
|
||||
|
||||
class NetworkInterceptor: Interceptor {
|
||||
class NetworkInterceptor @Inject constructor(
|
||||
@ApplicationContext private val ctx : Context
|
||||
) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
log("Network Requesting Debug",chain.request().url.toString())
|
||||
|
||||
return if (!isOnline()){
|
||||
return if (!isOnline(ctx)){
|
||||
//No Internet Connection
|
||||
showDialog()
|
||||
//Lets Stop the Incoming Request and send Dummy Response
|
||||
|
@ -1,8 +1,6 @@
|
||||
package com.shabinder.spotiflyer.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.preferencesKey
|
||||
import androidx.datastore.preferences.createDataStore
|
||||
@ -12,7 +10,6 @@ import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class TokenStore (
|
||||
context: Context,
|
||||
|
@ -17,58 +17,50 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* mainActivity Instance to use whereEver Needed , as Its God Activity.
|
||||
* (i.e, almost Active Throughout App's Lifecycle )
|
||||
*/
|
||||
val mainActivity
|
||||
get() = MainActivity.getInstance()
|
||||
|
||||
val sharedViewModel
|
||||
get() = MainActivity.getSharedViewModel()
|
||||
|
||||
fun loadAllImages( images:List<String>? = null,source: Source, context: Context? = mainActivity) {
|
||||
fun loadAllImages( images:List<String>? = null,source: Source, context: Context) {
|
||||
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||
images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList<String>) }
|
||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||
context.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||
}
|
||||
|
||||
fun downloadTracks(
|
||||
trackList: ArrayList<TrackDetails>,
|
||||
context: Context? = mainActivity
|
||||
context: Context
|
||||
) {
|
||||
if(!trackList.isNullOrEmpty()){
|
||||
loadAllImages(
|
||||
trackList.map { it.albumArtURL },
|
||||
trackList.first().source
|
||||
trackList.first().source,
|
||||
context
|
||||
)
|
||||
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||
serviceIntent.putParcelableArrayListExtra("object",trackList)
|
||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||
context.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||
}
|
||||
}
|
||||
|
||||
fun queryActiveTracks(context:Context? = mainActivity) {
|
||||
fun queryActiveTracks(context:Context?) {
|
||||
val serviceIntent = Intent(context, ForegroundService::class.java).apply {
|
||||
action = "query"
|
||||
}
|
||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||
}
|
||||
|
||||
fun finalOutputDir(itemName:String ,type:String, subFolder:String,extension:String = ".mp3"): String{
|
||||
return Provider.defaultDir + removeIllegalChars(type) + File.separator +
|
||||
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + File.separator} +
|
||||
removeIllegalChars(itemName) + extension
|
||||
}
|
||||
fun finalOutputDir(itemName:String ,type:String, subFolder:String,defaultDir:String,extension:String = ".mp3" ): String =
|
||||
defaultDir + removeIllegalChars(type) + File.separator +
|
||||
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + File.separator} +
|
||||
removeIllegalChars(itemName) + extension
|
||||
|
||||
/**
|
||||
* Util. Function To Check Connection Status
|
||||
* */
|
||||
@Suppress("DEPRECATION")
|
||||
fun isOnline(): Boolean {
|
||||
fun isOnline(ctx:Context): Boolean {
|
||||
var result = false
|
||||
val connectivityManager =
|
||||
mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||
ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||
connectivityManager?.let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
|
||||
@ -81,7 +73,7 @@ fun isOnline(): Boolean {
|
||||
}
|
||||
} else {
|
||||
val netInfo =
|
||||
(mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
|
||||
(ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
|
||||
result = netInfo != null && netInfo.isConnected
|
||||
}
|
||||
}
|
||||
@ -92,7 +84,7 @@ fun isOnline(): Boolean {
|
||||
fun showDialog(title:String? = null, message: String? = null,response: String = "Ok"){
|
||||
//TODO
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
Toast.makeText(mainActivity,title ?: "No Internet",Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(MainActivity.getInstance(),title ?: "No Internet",Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,11 +153,11 @@ fun removeIllegalChars(fileName: String): String {
|
||||
return name
|
||||
}
|
||||
|
||||
fun createDirectories() {
|
||||
createDirectory(Provider.defaultDir)
|
||||
createDirectory(Provider.imageDir())
|
||||
createDirectory(Provider.defaultDir + "Tracks/")
|
||||
createDirectory(Provider.defaultDir + "Albums/")
|
||||
createDirectory(Provider.defaultDir + "Playlists/")
|
||||
createDirectory(Provider.defaultDir + "YT_Downloads/")
|
||||
fun createDirectories(defaultDir:String,imageDir:String) {
|
||||
createDirectory(defaultDir)
|
||||
createDirectory(imageDir)
|
||||
createDirectory(defaultDir + "Tracks/")
|
||||
createDirectory(defaultDir + "Albums/")
|
||||
createDirectory(defaultDir + "Playlists/")
|
||||
createDirectory(defaultDir + "YT_Downloads/")
|
||||
}
|
||||
|
@ -41,18 +41,18 @@ import com.arthenica.mobileffmpeg.FFmpeg
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.mpatric.mp3agic.Mp3File
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.downloadHelper.getYTTracks
|
||||
import com.shabinder.spotiflyer.downloadHelper.sortByBestMatch
|
||||
import com.shabinder.spotiflyer.di.Directories
|
||||
import com.shabinder.spotiflyer.providers.getYTTracks
|
||||
import com.shabinder.spotiflyer.providers.sortByBestMatch
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
import com.shabinder.spotiflyer.models.spotify.Source
|
||||
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
||||
import com.shabinder.spotiflyer.networking.makeJsonBody
|
||||
import com.shabinder.spotiflyer.utils.*
|
||||
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
||||
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||
import com.tonyodev.fetch2.*
|
||||
import com.tonyodev.fetch2core.DownloadBlock
|
||||
import dagger.hilt.EntryPoints
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.*
|
||||
import retrofit2.Call
|
||||
@ -81,13 +81,16 @@ class ForegroundService : Service(){
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private var isServiceStarted = false
|
||||
private var messageList = mutableListOf("", "", "", "","")
|
||||
private val imageDir:String
|
||||
get() = imageDir(this)
|
||||
private lateinit var cancelIntent:PendingIntent
|
||||
private lateinit var fetch:Fetch
|
||||
private lateinit var downloadManager : DownloadManager
|
||||
@Inject lateinit var ytDownloader: YoutubeDownloader
|
||||
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
|
||||
@Inject lateinit var directories:Directories
|
||||
private val defaultDir
|
||||
get() = directories.defaultDir()
|
||||
private val imageDir
|
||||
get() = directories.imageDir()
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? = null
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:background">@android:color/black</item>
|
||||
<item name="android:backgroundTint">@android:color/black</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user