mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2025-02-16 19:17:55 +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.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.github.javiersantos.appupdater.enums.UpdateFrom
|
||||||
import com.razorpay.Checkout
|
import com.razorpay.Checkout
|
||||||
import com.razorpay.PaymentResultListener
|
import com.razorpay.PaymentResultListener
|
||||||
|
import com.shabinder.spotiflyer.di.Directories
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
||||||
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||||
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.ui.colorOffWhite
|
import com.shabinder.spotiflyer.ui.colorOffWhite
|
||||||
import com.shabinder.spotiflyer.utils.*
|
import com.shabinder.spotiflyer.utils.*
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.tonyodev.fetch2.Status
|
import com.tonyodev.fetch2.Status
|
||||||
|
import dagger.hilt.EntryPoints
|
||||||
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
|
||||||
@ -54,13 +52,12 @@ import javax.inject.Inject
|
|||||||
* This is App's God Activity
|
* This is App's God Activity
|
||||||
* */
|
* */
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity(), PaymentResultListener {
|
class MainActivity: AppCompatActivity(), PaymentResultListener {
|
||||||
|
|
||||||
private lateinit var navController: NavHostController
|
private lateinit var navController: NavHostController
|
||||||
private lateinit var updateUIReceiver: BroadcastReceiver
|
private lateinit var updateUIReceiver: BroadcastReceiver
|
||||||
private lateinit var queryReceiver: BroadcastReceiver
|
private lateinit var queryReceiver: BroadcastReceiver
|
||||||
@Inject lateinit var moshi: Moshi
|
@Inject lateinit var directories: Directories
|
||||||
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -72,15 +69,14 @@ class MainActivity : AppCompatActivity(), PaymentResultListener {
|
|||||||
ComposeLearnTheme {
|
ComposeLearnTheme {
|
||||||
Providers(AmbientContentColor provides colorOffWhite) {
|
Providers(AmbientContentColor provides colorOffWhite) {
|
||||||
ProvideWindowInsets {
|
ProvideWindowInsets {
|
||||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.7f)
|
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.65f)
|
||||||
navController = rememberNavController()
|
navController = rememberNavController()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
||||||
color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
||||||
startYPercentage = 1f,
|
startYPercentage = 0.29f,
|
||||||
endYPercentage = 0f,
|
endYPercentage = 0f,
|
||||||
fixedHeight = 700f,
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// Draw a scrim over the status bar which matches the app bar
|
// Draw a scrim over the status bar which matches the app bar
|
||||||
@ -92,7 +88,13 @@ class MainActivity : AppCompatActivity(), PaymentResultListener {
|
|||||||
backgroundColor = appBarColor,
|
backgroundColor = appBarColor,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
ComposeNavigation(navController)
|
ComposeNavigation(
|
||||||
|
this@MainActivity,
|
||||||
|
navController,
|
||||||
|
sharedViewModel.spotifyProvider,
|
||||||
|
sharedViewModel.gaanaProvider,
|
||||||
|
sharedViewModel.youtubeProvider,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,7 +108,7 @@ class MainActivity : AppCompatActivity(), PaymentResultListener {
|
|||||||
requestStoragePermission()
|
requestStoragePermission()
|
||||||
disableDozeMode()
|
disableDozeMode()
|
||||||
checkIfLatestVersion()
|
checkIfLatestVersion()
|
||||||
createDirectories()
|
createDirectories(directories.defaultDir(),directories.imageDir())
|
||||||
handleIntentFromExternalActivity()
|
handleIntentFromExternalActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,6 +298,7 @@ fun AppBar(
|
|||||||
|
|
||||||
|
|
||||||
//@Preview(showBackground = true)
|
//@Preview(showBackground = true)
|
||||||
|
/*
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultPreview() {
|
fun DefaultPreview() {
|
||||||
ComposeLearnTheme {
|
ComposeLearnTheme {
|
||||||
@ -315,4 +318,4 @@ fun DefaultPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
@ -18,18 +18,22 @@
|
|||||||
package com.shabinder.spotiflyer
|
package com.shabinder.spotiflyer
|
||||||
|
|
||||||
import android.content.Intent
|
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.compose.ui.graphics.Color
|
||||||
import androidx.hilt.lifecycle.ViewModelInject
|
import androidx.hilt.lifecycle.ViewModelInject
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
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.ui.colorPrimaryDark
|
||||||
import com.shabinder.spotiflyer.utils.log
|
import com.shabinder.spotiflyer.utils.log
|
||||||
import com.tonyodev.fetch2.Status
|
import com.tonyodev.fetch2.Status
|
||||||
@ -38,7 +42,10 @@ class SharedViewModel @ViewModelInject constructor(
|
|||||||
val databaseDAO: DatabaseDAO,
|
val databaseDAO: DatabaseDAO,
|
||||||
val spotifyService: SpotifyService,
|
val spotifyService: SpotifyService,
|
||||||
val gaanaInterface : GaanaInterface,
|
val gaanaInterface : GaanaInterface,
|
||||||
val ytDownloader: YoutubeDownloader
|
val ytDownloader: YoutubeDownloader,
|
||||||
|
val gaanaProvider: GaanaProvider,
|
||||||
|
val spotifyProvider: SpotifyProvider,
|
||||||
|
val youtubeProvider: YoutubeProvider
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
var isAuthenticated by mutableStateOf(false)
|
var isAuthenticated by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
@ -110,7 +117,7 @@ class SharedViewModel @ViewModelInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gradientColor by mutableStateOf(colorPrimaryDark)
|
var gradientColor by mutableStateOf(Color.Transparent)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun updateGradientColor(color: Color) {
|
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 android.util.Base64
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.shabinder.spotiflyer.App
|
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.networking.*
|
||||||
|
import com.shabinder.spotiflyer.utils.NetworkInterceptor
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -21,32 +17,13 @@ import okhttp3.Request
|
|||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||||
import java.io.File
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@Module
|
@Module
|
||||||
object Provider {
|
object NetworkUtilProvider {
|
||||||
|
|
||||||
//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
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@ -54,12 +31,6 @@ object Provider {
|
|||||||
return YoutubeDownloader()
|
return YoutubeDownloader()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun getTokenStore(
|
|
||||||
@ApplicationContext appContext: Context,
|
|
||||||
spotifyServiceTokenRequest: SpotifyServiceTokenRequest):TokenStore = TokenStore(appContext,spotifyServiceTokenRequest)
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun getSpotifyService(authInterceptor: SpotifyAuthInterceptor,okHttpClient: OkHttpClient.Builder,moshi: Moshi) :SpotifyService{
|
fun getSpotifyService(authInterceptor: SpotifyAuthInterceptor,okHttpClient: OkHttpClient.Builder,moshi: Moshi) :SpotifyService{
|
||||||
@ -122,10 +93,6 @@ object Provider {
|
|||||||
return retrofit.create(YoutubeMusicApi::class.java)
|
return retrofit.create(YoutubeMusicApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun getNetworkInterceptor():NetworkInterceptor = NetworkInterceptor()
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun okHttpClient(networkInterceptor: NetworkInterceptor): OkHttpClient.Builder {
|
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.NavHostController
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.*
|
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.home.Home
|
||||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeNavigation(navController: NavHostController) {
|
fun ComposeNavigation(
|
||||||
|
mainActivity: MainActivity,
|
||||||
|
navController: NavHostController,
|
||||||
|
spotifyProvider: SpotifyProvider,
|
||||||
|
gaanaProvider: GaanaProvider,
|
||||||
|
youtubeProvider: YoutubeProvider
|
||||||
|
) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = "home"
|
startDestination = "home"
|
||||||
@ -19,7 +28,7 @@ fun ComposeNavigation(navController: NavHostController) {
|
|||||||
|
|
||||||
//HomeScreen - Starting Point
|
//HomeScreen - Starting Point
|
||||||
composable("home") {
|
composable("home") {
|
||||||
Home(navController = navController)
|
Home(navController = navController, mainActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Spotify Screen
|
//Spotify Screen
|
||||||
@ -30,7 +39,10 @@ fun ComposeNavigation(navController: NavHostController) {
|
|||||||
) {
|
) {
|
||||||
TrackList(
|
TrackList(
|
||||||
fullLink = it.arguments?.getString("link") ?: "error",
|
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.models.spotify.Token
|
||||||
import com.shabinder.spotiflyer.utils.TokenStore
|
import com.shabinder.spotiflyer.utils.TokenStore
|
||||||
import com.shabinder.spotiflyer.utils.log
|
import com.shabinder.spotiflyer.utils.log
|
||||||
import com.shabinder.spotiflyer.utils.showDialog
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -17,217 +17,234 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.providers
|
package com.shabinder.spotiflyer.providers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
|
import com.shabinder.spotiflyer.di.DefaultDir
|
||||||
|
import com.shabinder.spotiflyer.di.ImageDir
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
|
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
import com.shabinder.spotiflyer.utils.*
|
import com.shabinder.spotiflyer.utils.finalOutputDir
|
||||||
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
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(
|
suspend fun queryGaana(
|
||||||
fullLink: String,
|
fullLink: String,
|
||||||
):PlatformQueryResult?{
|
):PlatformQueryResult?{
|
||||||
|
|
||||||
//Link Schema: https://gaana.com/type/link
|
//Link Schema: https://gaana.com/type/link
|
||||||
val gaanaLink = fullLink.substringAfter("gaana.com/")
|
val gaanaLink = fullLink.substringAfter("gaana.com/")
|
||||||
|
|
||||||
val link = gaanaLink.substringAfterLast('/', "error")
|
val link = gaanaLink.substringAfterLast('/', "error")
|
||||||
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
|
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
|
||||||
|
|
||||||
log("Gaana Fragment", "$type : $link")
|
log("Gaana Fragment", "$type : $link")
|
||||||
|
|
||||||
//Error
|
//Error
|
||||||
if (type == "Error" || link == "Error"){
|
if (type == "Error" || link == "Error"){
|
||||||
showDialog("Please Check Your Link!")
|
showDialog("Please Check Your Link!")
|
||||||
return null
|
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}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
queryActiveTracks()
|
return gaanaSearch(
|
||||||
return result
|
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
|
package com.shabinder.spotiflyer.providers
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
|
import com.shabinder.spotiflyer.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.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
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.models.spotify.Track
|
||||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
suspend fun querySpotify(fullLink: String):PlatformQueryResult?{
|
@Singleton
|
||||||
var spotifyLink =
|
class SpotifyProvider @Inject constructor(
|
||||||
"https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim()
|
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")) {
|
suspend fun querySpotify(fullLink: String):PlatformQueryResult?{
|
||||||
//Very Rare instance
|
var spotifyLink =
|
||||||
spotifyLink = resolveLink(spotifyLink, sharedViewModel.gaanaInterface)
|
"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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun spotifySearch(
|
||||||
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
type:String,
|
||||||
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
|
link: String
|
||||||
|
): PlatformQueryResult {
|
||||||
log("Spotify Fragment", "$type : $link")
|
val result = PlatformQueryResult(
|
||||||
|
folderType = "",
|
||||||
if (type == "Error" || link == "Error") {
|
subFolder = "",
|
||||||
showDialog("Please Check Your Link!")
|
title = "",
|
||||||
return null
|
coverUrl = "",
|
||||||
}
|
trackList = listOf(),
|
||||||
|
Source.Spotify
|
||||||
if (type == "episode" || type == "show") {
|
)
|
||||||
//TODO Implementation
|
with(result) {
|
||||||
showDialog("Implementing Soon, Stay Tuned!")
|
when (type) {
|
||||||
return null
|
"track" -> {
|
||||||
}
|
spotifyService.getTrack(link).value?.also {
|
||||||
|
folderType = "Tracks"
|
||||||
return spotifySearch(
|
subFolder = ""
|
||||||
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 ->
|
|
||||||
if (File(
|
if (File(
|
||||||
finalOutputDir(
|
finalOutputDir(
|
||||||
it1.name.toString(),
|
it.name.toString(),
|
||||||
folderType,
|
folderType,
|
||||||
subFolder
|
subFolder,
|
||||||
|
defaultDir
|
||||||
)
|
)
|
||||||
).exists()
|
).exists()
|
||||||
) {//Download Already Present!!
|
) {//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) {
|
"album" -> {
|
||||||
//Check For More Tracks If available
|
val albumObject = spotifyService.getAlbum(link).value
|
||||||
val moreTracks =
|
folderType = "Albums"
|
||||||
spotifyService.getPlaylistTracks(link, offset = tempTrackList.size).value
|
subFolder = albumObject?.name.toString()
|
||||||
moreTracks?.items?.forEach {
|
albumObject?.tracks?.items?.forEach {
|
||||||
it.track?.let { it1 -> tempTrackList.add(it1) }
|
if (File(
|
||||||
}
|
finalOutputDir(
|
||||||
moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
|
it.name.toString(),
|
||||||
}
|
folderType,
|
||||||
log("Total Tracks Fetched", tempTrackList.size.toString())
|
subFolder,
|
||||||
trackList = tempTrackList.toTrackDetailsList(folderType, subFolder)
|
defaultDir
|
||||||
title = playlistObject?.name.toString()
|
)
|
||||||
coverUrl = playlistObject?.images?.elementAtOrNull(1)?.url
|
).exists()
|
||||||
?: playlistObject?.images?.firstOrNull()?.url.toString()
|
) {//Download Already Present!!
|
||||||
withContext(Dispatchers.IO) {
|
it.downloaded = DownloadStatus.Downloaded
|
||||||
databaseDAO.insert(
|
}
|
||||||
DownloadRecord(
|
it.album = Album(
|
||||||
type = "Playlist",
|
images = listOf(
|
||||||
name = title,
|
Image(
|
||||||
link = "https://open.spotify.com/$type/$link",
|
url = albumObject.images?.elementAtOrNull(1)?.url
|
||||||
coverUrl = coverUrl,
|
?: albumObject.images?.elementAtOrNull(0)?.url
|
||||||
totalFiles = tempTrackList.size,
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
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,
|
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
|
||||||
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630
|
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630
|
||||||
* */
|
* */
|
||||||
@WorkerThread
|
private fun resolveLink(
|
||||||
fun resolveLink(
|
url:String
|
||||||
url:String,
|
):String {
|
||||||
gaanaInterface: GaanaInterface
|
val response = gaanaInterface.getResponse(url).execute().body()?.string().toString()
|
||||||
):String {
|
val regex = """https://open\.spotify\.com.+\w""".toRegex()
|
||||||
val response = gaanaInterface.getResponse(url).execute().body()?.string().toString()
|
return regex.find(response)?.value.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 {
|
private fun List<Track>.toTrackDetailsList(type:String, subFolder:String) = this.map {
|
||||||
TrackDetails(
|
TrackDetails(
|
||||||
title = it.name.toString(),
|
title = it.name.toString(),
|
||||||
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
||||||
durationSec = (it.duration_ms/1000).toInt(),
|
durationSec = (it.duration_ms/1000).toInt(),
|
||||||
albumArt = File(
|
albumArt = File(
|
||||||
Provider.imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg"),
|
imageDir + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg"),
|
||||||
albumName = it.album?.name,
|
albumName = it.album?.name,
|
||||||
year = it.album?.release_date,
|
year = it.album?.release_date,
|
||||||
comment = "Genres:${it.album?.genres?.joinToString()}",
|
comment = "Genres:${it.album?.genres?.joinToString()}",
|
||||||
trackUrl = it.href,
|
trackUrl = it.href,
|
||||||
downloaded = it.downloaded,
|
downloaded = it.downloaded,
|
||||||
source = Source.Spotify,
|
source = Source.Spotify,
|
||||||
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(),
|
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(),
|
||||||
outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a")
|
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/>.
|
* 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 android.annotation.SuppressLint
|
||||||
import com.beust.klaxon.JsonArray
|
import com.beust.klaxon.JsonArray
|
@ -18,214 +18,224 @@
|
|||||||
package com.shabinder.spotiflyer.providers
|
package com.shabinder.spotiflyer.providers
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
|
import com.shabinder.spotiflyer.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.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.utils.*
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/*
|
@Singleton
|
||||||
* YT Album Art Schema
|
class YoutubeProvider @Inject constructor(
|
||||||
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
|
private val directories: Directories,
|
||||||
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
|
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 val defaultDir
|
||||||
private const val sampleDomain1 = "youtube.com"
|
get() = directories.defaultDir()
|
||||||
|
private val imageDir
|
||||||
|
get() = directories.imageDir()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sending a Result as null = Some Error Occurred!
|
* Sending a Result as null = Some Error Occurred!
|
||||||
* */
|
* */
|
||||||
suspend fun queryYoutube(fullLink: String): PlatformQueryResult?{
|
suspend fun queryYoutube(fullLink: String): PlatformQueryResult?{
|
||||||
val link = fullLink.removePrefix("https://").removePrefix("http://")
|
val link = fullLink.removePrefix("https://").removePrefix("http://")
|
||||||
if(link.contains("playlist",true) || link.contains("list",true)){
|
if(link.contains("playlist",true) || link.contains("list",true)){
|
||||||
// Given Link is of a Playlist
|
// Given Link is of a Playlist
|
||||||
log("YT Play",link)
|
log("YT Play",link)
|
||||||
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
|
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
|
||||||
return getYTPlaylist(
|
return getYTPlaylist(
|
||||||
playlistId,
|
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")
|
||||||
|
}
|
||||||
|
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
|
with(result) {
|
||||||
var searchId = "error"
|
try {
|
||||||
if(link.contains(sampleDomain1,true) ){
|
log("YT Playlist", searchId)
|
||||||
searchId = link.substringAfterLast("=","error")
|
val playlist = ytDownloader.getPlaylist(searchId)
|
||||||
}
|
val playlistDetails = playlist.details()
|
||||||
if(link.contains(sampleDomain2,true) ){
|
val name = playlistDetails.title()
|
||||||
searchId = link.substringAfterLast("/","error")
|
subFolder = removeIllegalChars(name)
|
||||||
}
|
val videos = playlist.videos()
|
||||||
return if(searchId != "error") {
|
|
||||||
getYTTrack(
|
|
||||||
searchId,
|
|
||||||
sharedViewModel.ytDownloader,
|
|
||||||
sharedViewModel.databaseDAO
|
|
||||||
)
|
|
||||||
}else{
|
|
||||||
showDialog("Your Youtube Link is not of a Video!!")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getYTPlaylist(
|
coverUrl = "https://i.ytimg.com/vi/${
|
||||||
searchId: String,
|
videos.firstOrNull()?.videoId()
|
||||||
ytDownloader: YoutubeDownloader,
|
}/hqdefault.jpg"
|
||||||
databaseDAO: DatabaseDAO,
|
title = name
|
||||||
):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/${
|
trackList = videos.map {
|
||||||
videos.firstOrNull()?.videoId()
|
TrackDetails(
|
||||||
}/hqdefault.jpg"
|
title = it.title(),
|
||||||
title = name
|
artists = listOf(it.author().toString()),
|
||||||
|
durationSec = it.lengthSeconds(),
|
||||||
trackList = videos.map {
|
albumArt = File(imageDir + it.videoId() + ".jpeg"),
|
||||||
TrackDetails(
|
source = Source.YouTube,
|
||||||
title = it.title(),
|
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
|
||||||
artists = listOf(it.author().toString()),
|
downloaded = if (File(
|
||||||
durationSec = it.lengthSeconds(),
|
finalOutputDir(
|
||||||
albumArt = File(
|
itemName = it.title(),
|
||||||
imageDir() + it.videoId() + ".jpeg"
|
type = folderType,
|
||||||
),
|
subFolder = subFolder,
|
||||||
source = Source.YouTube,
|
defaultDir
|
||||||
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
|
)
|
||||||
downloaded = if (File(
|
).exists()
|
||||||
finalOutputDir(
|
)
|
||||||
itemName = it.title(),
|
DownloadStatus.Downloaded
|
||||||
type = folderType,
|
else {
|
||||||
subFolder = subFolder
|
DownloadStatus.NotDownloaded
|
||||||
)
|
|
||||||
).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",
|
outputFile = finalOutputDir(it.title(), folderType, subFolder, defaultDir,".m4a"),
|
||||||
coverUrl = "https://i.ytimg.com/vi/${
|
videoID = it.videoId()
|
||||||
videos.firstOrNull()?.videoId()
|
|
||||||
}/hqdefault.jpg",
|
|
||||||
totalFiles = videos.size,
|
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
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
|
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
|
package com.shabinder.spotiflyer.ui
|
||||||
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.Typography
|
import androidx.compose.material.Typography
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.TextStyle
|
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 androidx.compose.ui.unit.sp
|
||||||
import com.shabinder.spotiflyer.R
|
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.Icons
|
||||||
import androidx.compose.material.icons.outlined.History
|
import androidx.compose.material.icons.outlined.History
|
||||||
import androidx.compose.material.icons.outlined.Info
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.viewinterop.viewModel
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.AmbientContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.viewinterop.viewModel
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.razorpay.Checkout
|
import com.razorpay.Checkout
|
||||||
|
import com.shabinder.spotiflyer.MainActivity
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||||
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.*
|
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 dev.chrisbanes.accompanist.coil.CoilImage
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
fun Home(navController: NavController, mainActivity: MainActivity, modifier: Modifier = Modifier) {
|
||||||
val viewModel: HomeViewModel = viewModel()
|
val viewModel: HomeViewModel = viewModel()
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
@ -65,7 +73,7 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
when(viewModel.selectedCategory){
|
when(viewModel.selectedCategory){
|
||||||
HomeCategory.About -> AboutColumn()
|
HomeCategory.About -> AboutColumn(mainActivity)
|
||||||
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +85,8 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutColumn(modifier: Modifier = Modifier) {
|
fun AboutColumn(mainActivity: MainActivity,modifier: Modifier = Modifier) {
|
||||||
|
val ctx = AmbientContext.current
|
||||||
ScrollableColumn(modifier.fillMaxSize(),contentPadding = PaddingValues(16.dp)) {
|
ScrollableColumn(modifier.fillMaxSize(),contentPadding = PaddingValues(16.dp)) {
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
@ -94,17 +103,17 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = vectorResource(id = R.drawable.ic_spotify_logo), tint = Color.Unspecified,
|
imageVector = vectorResource(id = R.drawable.ic_spotify_logo), tint = Color.Unspecified,
|
||||||
modifier = Modifier.clickable(
|
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))
|
Spacer(modifier = modifier.padding(start = 24.dp))
|
||||||
Icon(imageVector = vectorResource(id = R.drawable.ic_gaana ),tint = Color.Unspecified,
|
Icon(imageVector = vectorResource(id = R.drawable.ic_gaana ),tint = Color.Unspecified,
|
||||||
modifier = Modifier.clickable(
|
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))
|
Spacer(modifier = modifier.padding(start = 24.dp))
|
||||||
Icon(imageVector = vectorResource(id = R.drawable.ic_youtube),tint = Color.Unspecified,
|
Icon(imageVector = vectorResource(id = R.drawable.ic_youtube),tint = Color.Unspecified,
|
||||||
modifier = Modifier.clickable(
|
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))
|
Spacer(modifier = Modifier.padding(top = 6.dp))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically,
|
Row(verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth().clickable(
|
modifier = Modifier.fillMaxWidth().clickable(
|
||||||
onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer") })
|
onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer",ctx) })
|
||||||
.padding(vertical = 6.dp)
|
.padding(vertical = 6.dp)
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = vectorResource(id = R.drawable.ic_github ),tint = Color.LightGray)
|
Icon(imageVector = vectorResource(id = R.drawable.ic_github ),tint = Color.LightGray)
|
||||||
@ -141,7 +150,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
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
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Rounded.Flag.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
Icon(Icons.Rounded.Flag.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||||
@ -159,7 +168,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||||
.clickable(onClick = { startPayment() }),
|
.clickable(onClick = { startPayment(mainActivity) }),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Rounded.CardGiftcard.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
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)
|
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||||
mainActivity.startActivity(shareIntent)
|
ctx.startActivity(shareIntent)
|
||||||
}),
|
}),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@ -245,6 +254,7 @@ fun HistoryColumn(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
||||||
|
val ctx = AmbientContext.current
|
||||||
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
|
||||||
val imgUri = item.coverUrl.toUri().buildUpon().scheme("https").build()
|
val imgUri = item.coverUrl.toUri().buildUpon().scheme("https").build()
|
||||||
CoilImage(
|
CoilImage(
|
||||||
@ -270,14 +280,14 @@ fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
|||||||
Image(
|
Image(
|
||||||
imageVector = vectorResource(id = R.drawable.ic_share_open),
|
imageVector = vectorResource(id = R.drawable.ic_share_open),
|
||||||
modifier = Modifier.clickable(onClick = {
|
modifier = Modifier.clickable(onClick = {
|
||||||
if(!isOnline()) showDialog("Check Your Internet Connection")
|
if(!isOnline(ctx)) showDialog("Check Your Internet Connection")
|
||||||
else navController.navigateToTrackList(item.link)
|
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
|
* You need to pass current activity in order to let Razorpay create CheckoutActivity
|
||||||
* */
|
* */
|
||||||
@ -365,7 +375,7 @@ fun SearchPanel(
|
|||||||
navController: NavController,
|
navController: NavController,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
){
|
){
|
||||||
|
val ctx = AmbientContext.current
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = modifier.padding(top = 16.dp)
|
modifier = modifier.padding(top = 16.dp)
|
||||||
@ -395,7 +405,7 @@ fun SearchPanel(
|
|||||||
onClick = {
|
onClick = {
|
||||||
if(link.isBlank()) showDialog("Enter A Link!")
|
if(link.isBlank()) showDialog("Enter A Link!")
|
||||||
else{
|
else{
|
||||||
if(!isOnline()) showDialog("Check Your Internet Connection")
|
if(!isOnline(ctx)) showDialog("Check Your Internet Connection")
|
||||||
else navController.navigateToTrackList(link)
|
else navController.navigateToTrackList(link)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package com.shabinder.spotiflyer.ui.home
|
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.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
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() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package com.shabinder.spotiflyer.ui.tracklist
|
package com.shabinder.spotiflyer.ui.tracklist
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.Image
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
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.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.AmbientContext
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
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.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
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.SpotiFlyerTypography
|
||||||
import com.shabinder.spotiflyer.ui.colorAccent
|
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.ui.utils.calculateDominantColor
|
||||||
import com.shabinder.spotiflyer.utils.downloadTracks
|
import com.shabinder.spotiflyer.utils.downloadTracks
|
||||||
import com.shabinder.spotiflyer.utils.log
|
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||||
import com.shabinder.spotiflyer.utils.showDialog
|
import com.shabinder.spotiflyer.utils.showDialog
|
||||||
import dev.chrisbanes.accompanist.coil.CoilImage
|
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||||
@ -45,6 +43,9 @@ import kotlinx.coroutines.*
|
|||||||
fun TrackList(
|
fun TrackList(
|
||||||
fullLink: String,
|
fullLink: String,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
|
spotifyProvider: SpotifyProvider,
|
||||||
|
gaanaProvider: GaanaProvider,
|
||||||
|
youtubeProvider: YoutubeProvider,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
){
|
){
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
@ -60,13 +61,16 @@ fun TrackList(
|
|||||||
* Using SharedViewModel's Link as NAVIGATION's Arg is buggy for links.
|
* Using SharedViewModel's Link as NAVIGATION's Arg is buggy for links.
|
||||||
* */
|
* */
|
||||||
//SPOTIFY
|
//SPOTIFY
|
||||||
sharedViewModel.link.contains("spotify",true) -> querySpotify(sharedViewModel.link)
|
sharedViewModel.link.contains("spotify",true) ->
|
||||||
|
spotifyProvider.querySpotify(sharedViewModel.link)
|
||||||
|
|
||||||
//YOUTUBE
|
//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
|
//GAANA
|
||||||
sharedViewModel.link.contains("gaana",true) -> queryGaana(sharedViewModel.link)
|
sharedViewModel.link.contains("gaana",true) ->
|
||||||
|
gaanaProvider.queryGaana(sharedViewModel.link)
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
showDialog("Link is Not Valid")
|
showDialog("Link is Not Valid")
|
||||||
@ -83,6 +87,7 @@ fun TrackList(
|
|||||||
sharedViewModel.updateTrackList(result?.trackList ?: listOf())
|
sharedViewModel.updateTrackList(result?.trackList ?: listOf())
|
||||||
|
|
||||||
result?.let{
|
result?.let{
|
||||||
|
val ctx = AmbientContext.current
|
||||||
Box(modifier = modifier.fillMaxSize()){
|
Box(modifier = modifier.fillMaxSize()){
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
@ -94,7 +99,7 @@ fun TrackList(
|
|||||||
TrackCard(
|
TrackCard(
|
||||||
track = item,
|
track = item,
|
||||||
onDownload = {
|
onDownload = {
|
||||||
downloadTracks(arrayListOf(item))
|
downloadTracks(arrayListOf(item),ctx)
|
||||||
sharedViewModel.updateTrackStatus(index,DownloadStatus.Queued)
|
sharedViewModel.updateTrackStatus(index,DownloadStatus.Queued)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -106,7 +111,7 @@ fun TrackList(
|
|||||||
onClick = {
|
onClick = {
|
||||||
val finalList = sharedViewModel.trackList.filter{it.downloaded == DownloadStatus.NotDownloaded}
|
val finalList = sharedViewModel.trackList.filter{it.downloaded == DownloadStatus.NotDownloaded}
|
||||||
if (finalList.isNullOrEmpty()) showDialog("Not Downloading Any Song")
|
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) {
|
for (track in sharedViewModel.trackList) {
|
||||||
if (track.downloaded == DownloadStatus.NotDownloaded) {
|
if (track.downloaded == DownloadStatus.NotDownloaded) {
|
||||||
track.downloaded = DownloadStatus.Queued
|
track.downloaded = DownloadStatus.Queued
|
||||||
@ -126,12 +131,14 @@ fun CoverImage(
|
|||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val ctx = AmbientContext.current
|
||||||
Column(
|
Column(
|
||||||
modifier.padding(vertical = 8.dp).fillMaxWidth(),
|
modifier.padding(vertical = 8.dp).fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
val imgUri = coverURL.toUri().buildUpon().scheme("https").build()
|
||||||
CoilImage(
|
CoilImage(
|
||||||
data = coverURL,
|
data = imgUri,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
loading = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
|
loading = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -149,7 +156,7 @@ fun CoverImage(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateGradient(coverURL)
|
updateGradient(coverURL, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +194,8 @@ fun TrackCard(
|
|||||||
verticalAlignment = Alignment.Bottom,
|
verticalAlignment = Alignment.Bottom,
|
||||||
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
|
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
|
||||||
){
|
){
|
||||||
Text("${track.artists.firstOrNull()}...",fontSize = 13.sp)
|
Text("${track.artists.firstOrNull()}...",fontSize = 12.sp,maxLines = 1)
|
||||||
Text("${track.durationSec/60} minutes, ${track.durationSec%60} sec",fontSize = 13.sp)
|
Text("${track.durationSec/60} min, ${track.durationSec%60} sec",fontSize = 12.sp,maxLines = 1,overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when(track.downloaded){
|
when(track.downloaded){
|
||||||
@ -216,7 +223,7 @@ fun TrackCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateGradient(imageURL:String){
|
suspend fun updateGradient(imageURL:String,ctx:Context){
|
||||||
calculateDominantColor(imageURL)?.color
|
calculateDominantColor(imageURL,ctx)?.color
|
||||||
?.let { sharedViewModel.updateGradientColor(it) }
|
?.let { sharedViewModel.updateGradientColor(it) }
|
||||||
}
|
}
|
@ -17,27 +17,14 @@
|
|||||||
package com.shabinder.spotiflyer.ui.utils
|
package com.shabinder.spotiflyer.ui.utils
|
||||||
|
|
||||||
import android.content.Context
|
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.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.graphics.Color
|
||||||
import androidx.compose.ui.platform.AmbientContext
|
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import androidx.palette.graphics.Palette
|
import androidx.palette.graphics.Palette
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.request.SuccessResult
|
import coil.request.SuccessResult
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import com.shabinder.spotiflyer.utils.mainActivity
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@ -45,9 +32,9 @@ import kotlinx.coroutines.withContext
|
|||||||
data class DominantColors(val color: Color, val onColor: Color)
|
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
|
// 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
|
// First we want to sort the list by the color's population
|
||||||
.sortedByDescending { swatch -> swatch.population }
|
.sortedByDescending { swatch -> swatch.population }
|
||||||
// Then we want to find the first valid color
|
// 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.draw.drawBehind
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.shabinder.spotiflyer.utils.log
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.shabinder.spotiflyer.utils
|
package com.shabinder.spotiflyer.utils
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
@ -53,23 +54,23 @@ fun YoutubeVideo.getData(): Format?{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun openPlatform(packageName:String, websiteAddress:String){
|
fun openPlatform(packageName:String, websiteAddress:String,context: Context){
|
||||||
val manager: PackageManager = mainActivity.packageManager
|
val manager: PackageManager = context.packageManager
|
||||||
try {
|
try {
|
||||||
val intent = manager.getLaunchIntentForPackage(packageName)
|
val intent = manager.getLaunchIntentForPackage(packageName)
|
||||||
?: throw PackageManager.NameNotFoundException()
|
?: throw PackageManager.NameNotFoundException()
|
||||||
intent.addCategory(Intent.CATEGORY_LAUNCHER)
|
intent.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
mainActivity.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
val uri: Uri =
|
val uri: Uri =
|
||||||
Uri.parse(websiteAddress)
|
Uri.parse(websiteAddress)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
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 uri = Uri.parse(websiteAddress)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
mainActivity.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
@ -17,18 +17,23 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.utils
|
package com.shabinder.spotiflyer.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Protocol
|
import okhttp3.Protocol
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
const val NoInternetErrorCode = 222
|
const val NoInternetErrorCode = 222
|
||||||
|
|
||||||
class NetworkInterceptor: Interceptor {
|
class NetworkInterceptor @Inject constructor(
|
||||||
|
@ApplicationContext private val ctx : Context
|
||||||
|
) : Interceptor {
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
log("Network Requesting Debug",chain.request().url.toString())
|
log("Network Requesting Debug",chain.request().url.toString())
|
||||||
|
|
||||||
return if (!isOnline()){
|
return if (!isOnline(ctx)){
|
||||||
//No Internet Connection
|
//No Internet Connection
|
||||||
showDialog()
|
showDialog()
|
||||||
//Lets Stop the Incoming Request and send Dummy Response
|
//Lets Stop the Incoming Request and send Dummy Response
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package com.shabinder.spotiflyer.utils
|
package com.shabinder.spotiflyer.utils
|
||||||
|
|
||||||
import android.content.Context
|
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.edit
|
||||||
import androidx.datastore.preferences.core.preferencesKey
|
import androidx.datastore.preferences.core.preferencesKey
|
||||||
import androidx.datastore.preferences.createDataStore
|
import androidx.datastore.preferences.createDataStore
|
||||||
@ -12,7 +10,6 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class TokenStore (
|
class TokenStore (
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -17,58 +17,50 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
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
|
val sharedViewModel
|
||||||
get() = MainActivity.getSharedViewModel()
|
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)
|
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>) }
|
||||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
context.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadTracks(
|
fun downloadTracks(
|
||||||
trackList: ArrayList<TrackDetails>,
|
trackList: ArrayList<TrackDetails>,
|
||||||
context: Context? = mainActivity
|
context: Context
|
||||||
) {
|
) {
|
||||||
if(!trackList.isNullOrEmpty()){
|
if(!trackList.isNullOrEmpty()){
|
||||||
loadAllImages(
|
loadAllImages(
|
||||||
trackList.map { it.albumArtURL },
|
trackList.map { it.albumArtURL },
|
||||||
trackList.first().source
|
trackList.first().source,
|
||||||
|
context
|
||||||
)
|
)
|
||||||
val serviceIntent = Intent(context, ForegroundService::class.java)
|
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||||
serviceIntent.putParcelableArrayListExtra("object",trackList)
|
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 {
|
val serviceIntent = Intent(context, ForegroundService::class.java).apply {
|
||||||
action = "query"
|
action = "query"
|
||||||
}
|
}
|
||||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
}
|
}
|
||||||
|
fun finalOutputDir(itemName:String ,type:String, subFolder:String,defaultDir:String,extension:String = ".mp3" ): String =
|
||||||
fun finalOutputDir(itemName:String ,type:String, subFolder:String,extension:String = ".mp3"): String{
|
defaultDir + removeIllegalChars(type) + File.separator +
|
||||||
return Provider.defaultDir + removeIllegalChars(type) + File.separator +
|
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + File.separator} +
|
||||||
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + File.separator} +
|
removeIllegalChars(itemName) + extension
|
||||||
removeIllegalChars(itemName) + extension
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Util. Function To Check Connection Status
|
* Util. Function To Check Connection Status
|
||||||
* */
|
* */
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
fun isOnline(): Boolean {
|
fun isOnline(ctx:Context): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
val connectivityManager =
|
val connectivityManager =
|
||||||
mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||||
connectivityManager?.let {
|
connectivityManager?.let {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
|
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
|
||||||
@ -81,7 +73,7 @@ fun isOnline(): Boolean {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val netInfo =
|
val netInfo =
|
||||||
(mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
|
(ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
|
||||||
result = netInfo != null && netInfo.isConnected
|
result = netInfo != null && netInfo.isConnected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +84,7 @@ fun isOnline(): Boolean {
|
|||||||
fun showDialog(title:String? = null, message: String? = null,response: String = "Ok"){
|
fun showDialog(title:String? = null, message: String? = null,response: String = "Ok"){
|
||||||
//TODO
|
//TODO
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
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
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createDirectories() {
|
fun createDirectories(defaultDir:String,imageDir:String) {
|
||||||
createDirectory(Provider.defaultDir)
|
createDirectory(defaultDir)
|
||||||
createDirectory(Provider.imageDir())
|
createDirectory(imageDir)
|
||||||
createDirectory(Provider.defaultDir + "Tracks/")
|
createDirectory(defaultDir + "Tracks/")
|
||||||
createDirectory(Provider.defaultDir + "Albums/")
|
createDirectory(defaultDir + "Albums/")
|
||||||
createDirectory(Provider.defaultDir + "Playlists/")
|
createDirectory(defaultDir + "Playlists/")
|
||||||
createDirectory(Provider.defaultDir + "YT_Downloads/")
|
createDirectory(defaultDir + "YT_Downloads/")
|
||||||
}
|
}
|
||||||
|
@ -41,18 +41,18 @@ import com.arthenica.mobileffmpeg.FFmpeg
|
|||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.mpatric.mp3agic.Mp3File
|
import com.mpatric.mp3agic.Mp3File
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.downloadHelper.getYTTracks
|
import com.shabinder.spotiflyer.di.Directories
|
||||||
import com.shabinder.spotiflyer.downloadHelper.sortByBestMatch
|
import com.shabinder.spotiflyer.providers.getYTTracks
|
||||||
|
import com.shabinder.spotiflyer.providers.sortByBestMatch
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
||||||
import com.shabinder.spotiflyer.networking.makeJsonBody
|
import com.shabinder.spotiflyer.networking.makeJsonBody
|
||||||
import com.shabinder.spotiflyer.utils.*
|
import com.shabinder.spotiflyer.utils.*
|
||||||
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
|
||||||
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
|
||||||
import com.tonyodev.fetch2.*
|
import com.tonyodev.fetch2.*
|
||||||
import com.tonyodev.fetch2core.DownloadBlock
|
import com.tonyodev.fetch2core.DownloadBlock
|
||||||
|
import dagger.hilt.EntryPoints
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
@ -81,13 +81,16 @@ class ForegroundService : Service(){
|
|||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var isServiceStarted = false
|
private var isServiceStarted = false
|
||||||
private var messageList = mutableListOf("", "", "", "","")
|
private var messageList = mutableListOf("", "", "", "","")
|
||||||
private val imageDir:String
|
|
||||||
get() = imageDir(this)
|
|
||||||
private lateinit var cancelIntent:PendingIntent
|
private lateinit var cancelIntent:PendingIntent
|
||||||
private lateinit var fetch:Fetch
|
private lateinit var fetch:Fetch
|
||||||
private lateinit var downloadManager : DownloadManager
|
private lateinit var downloadManager : DownloadManager
|
||||||
@Inject lateinit var ytDownloader: YoutubeDownloader
|
@Inject lateinit var ytDownloader: YoutubeDownloader
|
||||||
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
|
@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
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
<item name="android:textColor">@color/white</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:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user