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