Modularisation, DI, and fixes

This commit is contained in:
shabinder 2021-01-03 03:16:29 +05:30
parent 86fd6a5123
commit 748a60adad
23 changed files with 868 additions and 751 deletions

View File

@ -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() {
}
}
}
}
}*/

View File

@ -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) {

View 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
}

View File

@ -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 {

View File

@ -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

View File

@ -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
)
}
}

View File

@ -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

View File

@ -17,25 +17,41 @@
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(
suspend fun queryGaana(
fullLink: String,
):PlatformQueryResult?{
):PlatformQueryResult?{
//Link Schema: https://gaana.com/type/link
val gaanaLink = fullLink.substringAfter("gaana.com/")
@ -52,18 +68,14 @@ suspend fun queryGaana(
}
return gaanaSearch(
type,
link,
sharedViewModel.gaanaInterface,
sharedViewModel.databaseDAO,
link
)
}
}
suspend fun gaanaSearch(
private suspend fun gaanaSearch(
type:String,
link:String,
gaanaInterface: GaanaInterface,
databaseDAO: DatabaseDAO,
): PlatformQueryResult {
): PlatformQueryResult {
val result = PlatformQueryResult(
folderType = "",
subFolder = link,
@ -82,7 +94,8 @@ suspend fun gaanaSearch(
finalOutputDir(
it.track_title,
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -113,7 +126,8 @@ suspend fun gaanaSearch(
finalOutputDir(
track.track_title,
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -145,7 +159,8 @@ suspend fun gaanaSearch(
finalOutputDir(
track.track_title,
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -185,7 +200,8 @@ suspend fun gaanaSearch(
finalOutputDir(
track.track_title,
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -209,18 +225,18 @@ suspend fun gaanaSearch(
else -> {//TODO Handle Error}
}
}
queryActiveTracks()
queryActiveTracks(ctx)
return result
}
}
}
private fun List<GaanaTrack>.toTrackDetailsList(type:String , subFolder:String) = this.map {
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"),
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 }}",
@ -228,6 +244,7 @@ private fun List<GaanaTrack>.toTrackDetailsList(type:String , subFolder:String)
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
source = Source.Gaana,
albumArtURL = it.artworkLink,
outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a")
outputFile = finalOutputDir(it.track_title,type, subFolder,defaultDir,".m4a")
)
}
}

View File

@ -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,18 +32,37 @@ 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?{
@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()
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, sharedViewModel.gaanaInterface)
spotifyLink = resolveLink(spotifyLink)
}
@ -63,18 +84,14 @@ suspend fun querySpotify(fullLink: String):PlatformQueryResult?{
return spotifySearch(
type,
link,
sharedViewModel.spotifyService,
sharedViewModel.databaseDAO
link
)
}
}
suspend fun spotifySearch(
private suspend fun spotifySearch(
type:String,
link: String,
spotifyService: SpotifyService,
databaseDAO: DatabaseDAO
): PlatformQueryResult {
link: String
): PlatformQueryResult {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
@ -93,7 +110,8 @@ suspend fun spotifySearch(
finalOutputDir(
it.name.toString(),
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -126,7 +144,8 @@ suspend fun spotifySearch(
finalOutputDir(
it.name.toString(),
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -178,7 +197,8 @@ suspend fun spotifySearch(
finalOutputDir(
it1.name.toString(),
folderType,
subFolder
subFolder,
defaultDir
)
).exists()
) {//Download Already Present!!
@ -224,31 +244,29 @@ suspend fun spotifySearch(
}
}
}
queryActiveTracks()
queryActiveTracks(ctx)
return result
}
}
/*
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&amp;_branch_match_id=862039436205270630
* */
@WorkerThread
fun resolveLink(
url:String,
gaanaInterface: GaanaInterface
):String {
/*
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&amp;_branch_match_id=862039436205270630
* */
private fun resolveLink(
url:String
):String {
val response = gaanaInterface.getResponse(url).execute().body()?.string().toString()
val regex = """https://open\.spotify\.com.+\w""".toRegex()
return regex.find(response)?.value.toString()
}
}
private fun List<Track>.toTrackDetailsList(type:String, subFolder:String) = this.map {
private fun List<Track>.toTrackDetailsList(type:String, subFolder:String) = this.map {
TrackDetails(
title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms/1000).toInt(),
albumArt = File(
Provider.imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg"),
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()}",
@ -256,6 +274,7 @@ private fun List<Track>.toTrackDetailsList(type:String, subFolder:String) = this
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")
outputFile = finalOutputDir(it.name.toString(),type, subFolder,defaultDir,".m4a")
)
}
}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.downloadHelper
package com.shabinder.spotiflyer.providers
import android.annotation.SuppressLint
import com.beust.klaxon.JsonArray

View File

@ -18,41 +18,56 @@
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?{
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
playlistId
)
}else{//Given Link is of a Video
var searchId = "error"
@ -64,22 +79,18 @@ suspend fun queryYoutube(fullLink: String): PlatformQueryResult?{
}
return if(searchId != "error") {
getYTTrack(
searchId,
sharedViewModel.ytDownloader,
sharedViewModel.databaseDAO
searchId
)
}else{
showDialog("Your Youtube Link is not of a Video!!")
null
}
}
}
}
suspend fun getYTPlaylist(
searchId: String,
ytDownloader: YoutubeDownloader,
databaseDAO: DatabaseDAO,
):PlatformQueryResult?{
private suspend fun getYTPlaylist(
searchId: String
):PlatformQueryResult?{
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
@ -107,16 +118,15 @@ suspend fun getYTPlaylist(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArt = File(
imageDir() + it.videoId() + ".jpeg"
),
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
subFolder = subFolder,
defaultDir
)
).exists()
)
@ -124,7 +134,7 @@ suspend fun getYTPlaylist(
else {
DownloadStatus.NotDownloaded
},
outputFile = finalOutputDir(it.title(), folderType, subFolder, ".m4a"),
outputFile = finalOutputDir(it.title(), folderType, subFolder, defaultDir,".m4a"),
videoID = it.videoId()
)
}
@ -146,7 +156,7 @@ suspend fun getYTPlaylist(
)
)
}
queryActiveTracks()
queryActiveTracks(ctx)
} catch (e: Exception) {
e.printStackTrace()
showDialog("An Error Occurred While Processing!")
@ -154,14 +164,12 @@ suspend fun getYTPlaylist(
}
return if(result.title.isNotBlank()) result
else null
}
}
@SuppressLint("DefaultLocale")
suspend fun getYTTrack(
@SuppressLint("DefaultLocale")
private suspend fun getYTTrack(
searchId:String,
ytDownloader: YoutubeDownloader,
databaseDAO: DatabaseDAO
):PlatformQueryResult? {
):PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
@ -184,14 +192,15 @@ suspend fun getYTTrack(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArt = File(imageDir(), "$searchId.jpeg"),
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
subFolder = subFolder,
defaultDir = defaultDir
)
).exists()
)
@ -199,7 +208,7 @@ suspend fun getYTTrack(
else {
DownloadStatus.NotDownloaded
},
outputFile = finalOutputDir(name, folderType, subFolder, ".m4a"),
outputFile = finalOutputDir(name, folderType, subFolder, defaultDir,".m4a"),
videoID = searchId
)
)
@ -220,7 +229,7 @@ suspend fun getYTTrack(
)
)
}
queryActiveTracks()
queryActiveTracks(ctx)
} catch (e: Exception) {
e.printStackTrace()
showDialog("An Error Occurred While Processing!")
@ -228,4 +237,5 @@ suspend fun getYTTrack(
}
return if(result.title.isNotBlank()) result
else null
}
}

View File

@ -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

View File

@ -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)
}
},

View File

@ -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() {

View File

@ -1,9 +1,7 @@
package com.shabinder.spotiflyer.ui.tracklist
import android.content.Context
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@ -14,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.AmbientContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@ -25,14 +24,13 @@ import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.models.DownloadStatus
import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.providers.GaanaProvider
import com.shabinder.spotiflyer.providers.SpotifyProvider
import com.shabinder.spotiflyer.providers.YoutubeProvider
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent
import com.shabinder.spotiflyer.providers.queryGaana
import com.shabinder.spotiflyer.providers.querySpotify
import com.shabinder.spotiflyer.providers.queryYoutube
import com.shabinder.spotiflyer.ui.utils.calculateDominantColor
import com.shabinder.spotiflyer.utils.downloadTracks
import com.shabinder.spotiflyer.utils.log
import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog
import dev.chrisbanes.accompanist.coil.CoilImage
@ -45,6 +43,9 @@ import kotlinx.coroutines.*
fun TrackList(
fullLink: String,
navController: NavController,
spotifyProvider: SpotifyProvider,
gaanaProvider: GaanaProvider,
youtubeProvider: YoutubeProvider,
modifier: Modifier = Modifier
){
val coroutineScope = rememberCoroutineScope()
@ -60,13 +61,16 @@ fun TrackList(
* Using SharedViewModel's Link as NAVIGATION's Arg is buggy for links.
* */
//SPOTIFY
sharedViewModel.link.contains("spotify",true) -> querySpotify(sharedViewModel.link)
sharedViewModel.link.contains("spotify",true) ->
spotifyProvider.querySpotify(sharedViewModel.link)
//YOUTUBE
sharedViewModel.link.contains("youtube.com",true) || sharedViewModel.link.contains("youtu.be",true) -> queryYoutube(sharedViewModel.link)
sharedViewModel.link.contains("youtube.com",true) || sharedViewModel.link.contains("youtu.be",true) ->
youtubeProvider.queryYoutube(sharedViewModel.link)
//GAANA
sharedViewModel.link.contains("gaana",true) -> queryGaana(sharedViewModel.link)
sharedViewModel.link.contains("gaana",true) ->
gaanaProvider.queryGaana(sharedViewModel.link)
else -> {
showDialog("Link is Not Valid")
@ -83,6 +87,7 @@ fun TrackList(
sharedViewModel.updateTrackList(result?.trackList ?: listOf())
result?.let{
val ctx = AmbientContext.current
Box(modifier = modifier.fillMaxSize()){
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
@ -94,7 +99,7 @@ fun TrackList(
TrackCard(
track = item,
onDownload = {
downloadTracks(arrayListOf(item))
downloadTracks(arrayListOf(item),ctx)
sharedViewModel.updateTrackStatus(index,DownloadStatus.Queued)
},
)
@ -106,7 +111,7 @@ fun TrackList(
onClick = {
val finalList = sharedViewModel.trackList.filter{it.downloaded == DownloadStatus.NotDownloaded}
if (finalList.isNullOrEmpty()) showDialog("Not Downloading Any Song")
else downloadTracks(finalList as ArrayList<TrackDetails>)
else downloadTracks(finalList as ArrayList<TrackDetails>,ctx)
for (track in sharedViewModel.trackList) {
if (track.downloaded == DownloadStatus.NotDownloaded) {
track.downloaded = DownloadStatus.Queued
@ -126,12 +131,14 @@ fun CoverImage(
scope: CoroutineScope,
modifier: Modifier = Modifier,
) {
val ctx = AmbientContext.current
Column(
modifier.padding(vertical = 8.dp).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
val imgUri = coverURL.toUri().buildUpon().scheme("https").build()
CoilImage(
data = coverURL,
data = imgUri,
contentScale = ContentScale.Crop,
loading = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
modifier = Modifier
@ -149,7 +156,7 @@ fun CoverImage(
)
}
scope.launch {
updateGradient(coverURL)
updateGradient(coverURL, ctx)
}
}
@ -187,8 +194,8 @@ fun TrackCard(
verticalAlignment = Alignment.Bottom,
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
){
Text("${track.artists.firstOrNull()}...",fontSize = 13.sp)
Text("${track.durationSec/60} minutes, ${track.durationSec%60} sec",fontSize = 13.sp)
Text("${track.artists.firstOrNull()}...",fontSize = 12.sp,maxLines = 1)
Text("${track.durationSec/60} min, ${track.durationSec%60} sec",fontSize = 12.sp,maxLines = 1,overflow = TextOverflow.Ellipsis)
}
}
when(track.downloaded){
@ -216,7 +223,7 @@ fun TrackCard(
}
}
suspend fun updateGradient(imageURL:String){
calculateDominantColor(imageURL)?.color
suspend fun updateGradient(imageURL:String,ctx:Context){
calculateDominantColor(imageURL,ctx)?.color
?.let { sharedViewModel.updateGradientColor(it) }
}

View File

@ -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

View File

@ -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
/**

View File

@ -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)
}

View File

@ -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

View File

@ -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,

View File

@ -17,58 +17,50 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
/**
* mainActivity Instance to use whereEver Needed , as Its God Activity.
* (i.e, almost Active Throughout App's Lifecycle )
*/
val mainActivity
get() = MainActivity.getInstance()
val sharedViewModel
get() = MainActivity.getSharedViewModel()
fun loadAllImages( images:List<String>? = null,source: Source, context: Context? = mainActivity) {
fun loadAllImages( images:List<String>? = null,source: Source, context: Context) {
val serviceIntent = Intent(context, ForegroundService::class.java)
images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList<String>) }
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
context.let { ContextCompat.startForegroundService(it, serviceIntent) }
}
fun downloadTracks(
trackList: ArrayList<TrackDetails>,
context: Context? = mainActivity
context: Context
) {
if(!trackList.isNullOrEmpty()){
loadAllImages(
trackList.map { it.albumArtURL },
trackList.first().source
trackList.first().source,
context
)
val serviceIntent = Intent(context, ForegroundService::class.java)
serviceIntent.putParcelableArrayListExtra("object",trackList)
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
context.let { ContextCompat.startForegroundService(it, serviceIntent) }
}
}
fun queryActiveTracks(context:Context? = mainActivity) {
fun queryActiveTracks(context:Context?) {
val serviceIntent = Intent(context, ForegroundService::class.java).apply {
action = "query"
}
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
}
fun finalOutputDir(itemName:String ,type:String, subFolder:String,extension:String = ".mp3"): String{
return Provider.defaultDir + removeIllegalChars(type) + File.separator +
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/")
}

View File

@ -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

View File

@ -5,6 +5,8 @@
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColor">@color/white</item>
<item name="android:background">@android:color/black</item>
<item name="android:backgroundTint">@android:color/black</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>