mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
Jetpack DataStore, Dependency Injection.
This commit is contained in:
parent
df5f7dd2c5
commit
5c0ec20e1b
@ -134,6 +134,9 @@ dependencies {
|
|||||||
implementation "dev.chrisbanes.accompanist:accompanist-coil:$coil_version"
|
implementation "dev.chrisbanes.accompanist:accompanist-coil:$coil_version"
|
||||||
implementation "dev.chrisbanes.accompanist:accompanist-insets:$coil_version"
|
implementation "dev.chrisbanes.accompanist:accompanist-insets:$coil_version"
|
||||||
|
|
||||||
|
//DataStore
|
||||||
|
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha05"
|
||||||
|
|
||||||
//Extras
|
//Extras
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
|
@ -41,10 +41,13 @@
|
|||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- Add your API key here -->
|
<!-- Add your API key here -->
|
||||||
|
@ -28,8 +28,9 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.example.jetcaster.util.verticalGradientScrim
|
import com.example.jetcaster.util.verticalGradientScrim
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Token
|
||||||
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
||||||
import com.shabinder.spotiflyer.navigation.navigateToPlatform
|
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||||
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
|
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
|
||||||
@ -40,7 +41,10 @@ import com.squareup.moshi.Moshi
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
|
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
|
||||||
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -69,14 +73,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
ComposeLearnTheme {
|
ComposeLearnTheme {
|
||||||
Providers(AmbientContentColor provides colorOffWhite) {
|
Providers(AmbientContentColor provides colorOffWhite) {
|
||||||
ProvideWindowInsets {
|
ProvideWindowInsets {
|
||||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.6f)
|
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.7f)
|
||||||
navController = rememberNavController()
|
navController = rememberNavController()
|
||||||
|
|
||||||
val gradientColor by sharedViewModel.gradientColor.collectAsState()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
||||||
color = gradientColor.copy(alpha = 0.38f),
|
color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
||||||
startYPercentage = 1f,
|
startYPercentage = 1f,
|
||||||
endYPercentage = 0f,
|
endYPercentage = 0f,
|
||||||
fixedHeight = 700f,
|
fixedHeight = 700f,
|
||||||
@ -101,7 +103,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
authenticateSpotify()
|
|
||||||
requestStoragePermission()
|
requestStoragePermission()
|
||||||
disableDozeMode()
|
disableDozeMode()
|
||||||
//checkIfLatestVersion()
|
//checkIfLatestVersion()
|
||||||
@ -147,50 +148,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding my own Spotify Web Api Requests!
|
|
||||||
* */
|
|
||||||
private fun implementSpotifyService(token: String) {
|
|
||||||
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
|
|
||||||
httpClient.addInterceptor(Interceptor { chain ->
|
|
||||||
val request: Request =
|
|
||||||
chain.request().newBuilder().addHeader(
|
|
||||||
"Authorization",
|
|
||||||
"Bearer $token"
|
|
||||||
).build()
|
|
||||||
chain.proceed(request)
|
|
||||||
}).addInterceptor(NetworkInterceptor())
|
|
||||||
|
|
||||||
val retrofit = Retrofit.Builder().run{
|
|
||||||
baseUrl("https://api.spotify.com/v1/")
|
|
||||||
client(httpClient.build())
|
|
||||||
addConverterFactory(MoshiConverterFactory.create(moshi))
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
sharedViewModel.spotifyService.value = retrofit.create(SpotifyService::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun authenticateSpotify() {
|
|
||||||
if(sharedViewModel.spotifyService.value == null){
|
|
||||||
sharedViewModel.viewModelScope.launch {
|
|
||||||
log("Spotify Authentication","Started")
|
|
||||||
val token = spotifyServiceTokenRequest.getToken()
|
|
||||||
token.value?.let {
|
|
||||||
showDialog1("Success: Spotify Token Acquired")
|
|
||||||
implementSpotifyService(it.access_token)
|
|
||||||
}
|
|
||||||
log("Spotify Token", token.value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleIntentFromExternalActivity(intent: Intent? = getIntent()) {
|
private fun handleIntentFromExternalActivity(intent: Intent? = getIntent()) {
|
||||||
if (intent?.action == Intent.ACTION_SEND) {
|
if (intent?.action == Intent.ACTION_SEND) {
|
||||||
if ("text/plain" == intent.type) {
|
if ("text/plain" == intent.type) {
|
||||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||||
log("Intent Received", it)
|
log("Intent Received", it)
|
||||||
navController.navigateToPlatform(it)
|
navController.navigateToTrackList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,41 +17,42 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer
|
package com.shabinder.spotiflyer
|
||||||
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.hilt.lifecycle.ViewModelInject
|
import androidx.hilt.lifecycle.ViewModelInject
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
|
||||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
|
||||||
import com.shabinder.spotiflyer.ui.colorPrimaryDark
|
import com.shabinder.spotiflyer.ui.colorPrimaryDark
|
||||||
import com.shabinder.spotiflyer.ui.home.HomeCategory
|
|
||||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
|
|
||||||
@ActivityRetainedScoped
|
@ActivityRetainedScoped
|
||||||
class SharedViewModel @ViewModelInject constructor(
|
class SharedViewModel @ViewModelInject constructor(
|
||||||
val databaseDAO: DatabaseDAO,
|
val databaseDAO: DatabaseDAO,
|
||||||
|
val spotifyService: SpotifyService,
|
||||||
val gaanaInterface : GaanaInterface,
|
val gaanaInterface : GaanaInterface,
|
||||||
val ytDownloader: YoutubeDownloader
|
val ytDownloader: YoutubeDownloader
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
var spotifyService = MutableStateFlow<SpotifyService?>(null)
|
var isAuthenticated by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
private val _gradientColor = MutableStateFlow(colorPrimaryDark)
|
fun authenticated(s:Boolean) {
|
||||||
val gradientColor : StateFlow<Color>
|
isAuthenticated = s
|
||||||
get() = _gradientColor
|
}
|
||||||
|
|
||||||
|
var gradientColor by mutableStateOf(colorPrimaryDark)
|
||||||
|
private set
|
||||||
|
|
||||||
fun updateGradientColor(color: Color) {
|
fun updateGradientColor(color: Color) {
|
||||||
_gradientColor.value = color
|
gradientColor = color
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetGradient() {
|
fun resetGradient() {
|
||||||
_gradientColor.value = colorPrimaryDark
|
gradientColor = colorPrimaryDark
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,12 @@
|
|||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
|
||||||
data class PlatformQueryResult(
|
data class PlatformQueryResult(
|
||||||
var folderType: String,
|
var folderType: String,
|
||||||
var subFolder: String,
|
var subFolder: String,
|
||||||
var title: String,
|
var title: String,
|
||||||
var coverUrl: String,
|
var coverUrl: String,
|
||||||
var trackList: List<TrackDetails>
|
var trackList: List<TrackDetails>,
|
||||||
|
var source: Source
|
||||||
)
|
)
|
@ -18,11 +18,12 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.squareup.moshi.Json
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Token(
|
data class Token(
|
||||||
var access_token:String,
|
var access_token:String?,
|
||||||
var token_type:String,
|
var token_type:String?,
|
||||||
var expires_in:Int
|
@Json(name = "expires_in") var expiry:Long?
|
||||||
): Parcelable
|
): Parcelable
|
@ -7,12 +7,7 @@ import androidx.navigation.NavType
|
|||||||
import androidx.navigation.compose.*
|
import androidx.navigation.compose.*
|
||||||
import androidx.navigation.compose.popUpTo
|
import androidx.navigation.compose.popUpTo
|
||||||
import com.shabinder.spotiflyer.ui.home.Home
|
import com.shabinder.spotiflyer.ui.home.Home
|
||||||
import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana
|
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
||||||
import com.shabinder.spotiflyer.ui.platforms.spotify.Spotify
|
|
||||||
import com.shabinder.spotiflyer.ui.platforms.youtube.Youtube
|
|
||||||
import com.shabinder.spotiflyer.utils.mainActivity
|
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
|
||||||
import com.shabinder.spotiflyer.utils.showDialog
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeNavigation(navController: NavHostController) {
|
fun ComposeNavigation(navController: NavHostController) {
|
||||||
@ -29,34 +24,10 @@ fun ComposeNavigation(navController: NavHostController) {
|
|||||||
//Spotify Screen
|
//Spotify Screen
|
||||||
//Argument `link` = Link of Track/Album/Playlist
|
//Argument `link` = Link of Track/Album/Playlist
|
||||||
composable(
|
composable(
|
||||||
"spotify/{link}",
|
"track_list/{link}",
|
||||||
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
||||||
) {
|
) {
|
||||||
Spotify(
|
TrackList(
|
||||||
fullLink = it.arguments?.getString("link") ?: "error",
|
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gaana Screen
|
|
||||||
//Argument `link` = Link of Track/Album/Playlist
|
|
||||||
composable(
|
|
||||||
"gaana/{link}",
|
|
||||||
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
|
||||||
) {
|
|
||||||
Gaana(
|
|
||||||
fullLink = it.arguments?.getString("link") ?: "error",
|
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Youtube Screen
|
|
||||||
//Argument `link` = Link of Track/Album/Playlist
|
|
||||||
composable(
|
|
||||||
"youtube/{link}",
|
|
||||||
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
|
||||||
) {
|
|
||||||
Youtube(
|
|
||||||
fullLink = it.arguments?.getString("link") ?: "error",
|
fullLink = it.arguments?.getString("link") ?: "error",
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
@ -64,34 +35,10 @@ fun ComposeNavigation(navController: NavHostController) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NavController.navigateToPlatform(link:String){
|
fun NavController.navigateToTrackList(link:String, singleInstance: Boolean = true, inclusive:Boolean = false) {
|
||||||
when{
|
navigate("track_list/$link") {
|
||||||
//SPOTIFY
|
|
||||||
link.contains("spotify",true) -> {
|
|
||||||
if(sharedViewModel.spotifyService.value == null){//Authentication pending!!
|
|
||||||
mainActivity.authenticateSpotify()
|
|
||||||
}
|
|
||||||
this.navigateAndPopUpToHome("spotify/$link")
|
|
||||||
}
|
|
||||||
|
|
||||||
//YOUTUBE
|
|
||||||
link.contains("youtube.com",true) || link.contains("youtu.be",true) -> {
|
|
||||||
this.navigateAndPopUpToHome("youtube/$link")
|
|
||||||
}
|
|
||||||
|
|
||||||
//GAANA
|
|
||||||
link.contains("gaana",true) -> {
|
|
||||||
this.navigateAndPopUpToHome("gaana/$link")
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> showDialog("Link is Not Valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NavController.navigateAndPopUpToHome(route:String, inclusive:Boolean = false,singleInstance:Boolean = true){
|
|
||||||
this.navigate(route){
|
|
||||||
launchSingleTop = singleInstance
|
launchSingleTop = singleInstance
|
||||||
popUpTo(route = "home"){
|
popUpTo(route = "home") {
|
||||||
this.inclusive = inclusive
|
this.inclusive = inclusive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.shabinder.spotiflyer.networking
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
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
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interceptor which adds authorization token in header.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class SpotifyAuthInterceptor @Inject constructor(
|
||||||
|
private val tokenStore: TokenStore,
|
||||||
|
) : Interceptor {
|
||||||
|
/*
|
||||||
|
* Local Copy for Token
|
||||||
|
* Live Throughout Session
|
||||||
|
* */
|
||||||
|
private var token by mutableStateOf<Token?>(null)
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
if(token?.expiry?:0 < System.currentTimeMillis()/1000){
|
||||||
|
//Token Expired time to fetch New One
|
||||||
|
runBlocking { token = tokenStore.getToken() }
|
||||||
|
log("Spotify Auth",token.toString())
|
||||||
|
}
|
||||||
|
val authRequest = chain.request().newBuilder().
|
||||||
|
addHeader("Authorization", "Bearer ${token?.access_token}").build()
|
||||||
|
return chain.proceed(authRequest)
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.spotiflyer.ui.platforms.gaana
|
package com.shabinder.spotiflyer.providers
|
||||||
|
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
@ -25,15 +25,39 @@ import com.shabinder.spotiflyer.models.TrackDetails
|
|||||||
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
|
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
|
import com.shabinder.spotiflyer.utils.*
|
||||||
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||||
import com.shabinder.spotiflyer.utils.finalOutputDir
|
|
||||||
import com.shabinder.spotiflyer.utils.queryActiveTracks
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private const val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
|
private const val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
|
||||||
|
|
||||||
|
suspend fun queryGaana(
|
||||||
|
fullLink: String,
|
||||||
|
):PlatformQueryResult?{
|
||||||
|
|
||||||
|
//Link Schema: https://gaana.com/type/link
|
||||||
|
val gaanaLink = fullLink.substringAfter("gaana.com/")
|
||||||
|
|
||||||
|
val link = gaanaLink.substringAfterLast('/', "error")
|
||||||
|
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
|
||||||
|
|
||||||
|
log("Gaana Fragment", "$type : $link")
|
||||||
|
|
||||||
|
//Error
|
||||||
|
if (type == "Error" || link == "Error"){
|
||||||
|
showDialog("Please Check Your Link!")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return gaanaSearch(
|
||||||
|
type,
|
||||||
|
link,
|
||||||
|
sharedViewModel.gaanaInterface,
|
||||||
|
sharedViewModel.databaseDAO,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun gaanaSearch(
|
suspend fun gaanaSearch(
|
||||||
type:String,
|
type:String,
|
||||||
link:String,
|
link:String,
|
||||||
@ -46,6 +70,7 @@ suspend fun gaanaSearch(
|
|||||||
title = link,
|
title = link,
|
||||||
coverUrl = gaanaPlaceholderImageUrl,
|
coverUrl = gaanaPlaceholderImageUrl,
|
||||||
trackList = listOf(),
|
trackList = listOf(),
|
||||||
|
Source.Gaana
|
||||||
)
|
)
|
||||||
with(result) {
|
with(result) {
|
||||||
when (type) {
|
when (type) {
|
@ -15,9 +15,10 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.spotiflyer.ui.platforms.spotify
|
package com.shabinder.spotiflyer.providers
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
@ -34,6 +35,40 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
suspend fun spotifySearch(
|
||||||
type:String,
|
type:String,
|
||||||
link: String,
|
link: String,
|
||||||
@ -46,6 +81,7 @@ suspend fun spotifySearch(
|
|||||||
title = "",
|
title = "",
|
||||||
coverUrl = "",
|
coverUrl = "",
|
||||||
trackList = listOf(),
|
trackList = listOf(),
|
||||||
|
Source.Spotify
|
||||||
)
|
)
|
||||||
with(result) {
|
with(result) {
|
||||||
when (type) {
|
when (type) {
|
||||||
@ -192,6 +228,10 @@ suspend fun spotifySearch(
|
|||||||
return result
|
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
|
@WorkerThread
|
||||||
fun resolveLink(
|
fun resolveLink(
|
||||||
url:String,
|
url:String,
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.spotiflyer.ui.platforms.youtube
|
package com.shabinder.spotiflyer.providers
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
@ -37,6 +37,43 @@ import java.io.File
|
|||||||
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
|
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
|
||||||
* */
|
* */
|
||||||
|
|
||||||
|
private const val sampleDomain2 = "youtu.be"
|
||||||
|
private const val sampleDomain1 = "youtube.com"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
|
||||||
|
return getYTPlaylist(
|
||||||
|
playlistId,
|
||||||
|
sharedViewModel.ytDownloader,
|
||||||
|
sharedViewModel.databaseDAO
|
||||||
|
)
|
||||||
|
}else{//Given Link is of a Video
|
||||||
|
var searchId = "error"
|
||||||
|
if(link.contains(sampleDomain1,true) ){
|
||||||
|
searchId = link.substringAfterLast("=","error")
|
||||||
|
}
|
||||||
|
if(link.contains(sampleDomain2,true) ){
|
||||||
|
searchId = link.substringAfterLast("/","error")
|
||||||
|
}
|
||||||
|
return if(searchId != "error") {
|
||||||
|
getYTTrack(
|
||||||
|
searchId,
|
||||||
|
sharedViewModel.ytDownloader,
|
||||||
|
sharedViewModel.databaseDAO
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
showDialog("Your Youtube Link is not of a Video!!")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getYTPlaylist(
|
suspend fun getYTPlaylist(
|
||||||
searchId: String,
|
searchId: String,
|
||||||
ytDownloader: YoutubeDownloader,
|
ytDownloader: YoutubeDownloader,
|
||||||
@ -48,6 +85,7 @@ suspend fun getYTPlaylist(
|
|||||||
title = "",
|
title = "",
|
||||||
coverUrl = "",
|
coverUrl = "",
|
||||||
trackList = listOf(),
|
trackList = listOf(),
|
||||||
|
Source.YouTube
|
||||||
)
|
)
|
||||||
with(result) {
|
with(result) {
|
||||||
try {
|
try {
|
||||||
@ -127,6 +165,7 @@ suspend fun getYTTrack(
|
|||||||
title = "",
|
title = "",
|
||||||
coverUrl = "",
|
coverUrl = "",
|
||||||
trackList = listOf(),
|
trackList = listOf(),
|
||||||
|
Source.YouTube
|
||||||
)
|
)
|
||||||
with(result) {
|
with(result) {
|
||||||
try {
|
try {
|
@ -15,8 +15,6 @@ import androidx.compose.material.icons.outlined.History
|
|||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.viewinterop.viewModel
|
import androidx.compose.ui.viewinterop.viewModel
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -34,14 +32,13 @@ import androidx.core.net.toUri
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
import com.shabinder.spotiflyer.navigation.navigateToPlatform
|
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||||
import com.shabinder.spotiflyer.ui.colorAccent
|
import com.shabinder.spotiflyer.ui.colorAccent
|
||||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
import com.shabinder.spotiflyer.ui.colorPrimary
|
||||||
import com.shabinder.spotiflyer.utils.openPlatform
|
import com.shabinder.spotiflyer.utils.openPlatform
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||||
import dev.chrisbanes.accompanist.glide.GlideImage
|
import dev.chrisbanes.accompanist.glide.GlideImage
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
||||||
@ -49,26 +46,23 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
|
|
||||||
val link by viewModel.link.collectAsState()
|
AuthenticationBanner(sharedViewModel.isAuthenticated,modifier)
|
||||||
|
|
||||||
AuthenticationBanner(viewModel,modifier)
|
|
||||||
|
|
||||||
SearchPanel(
|
SearchPanel(
|
||||||
link,
|
viewModel.link,
|
||||||
viewModel::updateLink,
|
viewModel::updateLink,
|
||||||
navController,
|
navController,
|
||||||
modifier
|
modifier
|
||||||
)
|
)
|
||||||
val selectedCategory by viewModel.selectedCategory.collectAsState()
|
|
||||||
|
|
||||||
HomeTabBar(
|
HomeTabBar(
|
||||||
selectedCategory,
|
viewModel.selectedCategory,
|
||||||
HomeCategory.values(),
|
HomeCategory.values(),
|
||||||
viewModel::selectCategory,
|
viewModel::selectCategory,
|
||||||
modifier
|
modifier
|
||||||
)
|
)
|
||||||
|
|
||||||
when(selectedCategory){
|
when(viewModel.selectedCategory){
|
||||||
HomeCategory.About -> AboutColumn()
|
HomeCategory.About -> AboutColumn()
|
||||||
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
||||||
}
|
}
|
||||||
@ -212,11 +206,9 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryColumn(
|
fun HistoryColumn(
|
||||||
downloadRecordList: StateFlow<List<DownloadRecord>>,
|
list: List<DownloadRecord>,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val list by downloadRecordList.collectAsState()
|
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
content = {
|
content = {
|
||||||
@ -254,16 +246,15 @@ fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
|||||||
}
|
}
|
||||||
Image(
|
Image(
|
||||||
imageVector = vectorResource(id = R.drawable.ic_share_open),
|
imageVector = vectorResource(id = R.drawable.ic_share_open),
|
||||||
modifier = Modifier.clickable(onClick = { navController.navigateToPlatform(item.link) })
|
modifier = Modifier.clickable(onClick = { navController.navigateToTrackList(item.link) })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthenticationBanner(viewModel: HomeViewModel, modifier: Modifier) {
|
fun AuthenticationBanner(isAuthenticated: Boolean, modifier: Modifier) {
|
||||||
val authenticationStatus by viewModel.isAuthenticating.collectAsState()
|
|
||||||
|
|
||||||
if (authenticationStatus) {
|
if (!isAuthenticated) {
|
||||||
// TODO show a progress indicator or similar
|
// TODO show a progress indicator or similar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,7 +312,7 @@ fun SearchPanel(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = modifier.padding(top = 16.dp,)
|
modifier = modifier.padding(top = 16.dp)
|
||||||
){
|
){
|
||||||
TextField(
|
TextField(
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
@ -346,7 +337,7 @@ fun SearchPanel(
|
|||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.padding(12.dp).wrapContentWidth(),
|
modifier = Modifier.padding(12.dp).wrapContentWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigateToPlatform(link)
|
navController.navigateToTrackList(link)
|
||||||
},
|
},
|
||||||
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
|
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
|
||||||
){
|
){
|
||||||
@ -354,6 +345,8 @@ fun SearchPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeCategoryTabIndicator(
|
fun HomeCategoryTabIndicator(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -2,56 +2,40 @@ package com.shabinder.spotiflyer.ui.home
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
private val _link = MutableStateFlow("")
|
var link by mutableStateOf("")
|
||||||
val link:StateFlow<String>
|
private set
|
||||||
get() = _link
|
|
||||||
|
|
||||||
fun updateLink(s:String) {
|
fun updateLink(s:String) {
|
||||||
_link.value = s
|
link = s
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _isAuthenticating = MutableStateFlow(true)
|
var selectedCategory by mutableStateOf(HomeCategory.About)
|
||||||
val isAuthenticating:StateFlow<Boolean>
|
private set
|
||||||
get() = _isAuthenticating
|
|
||||||
|
|
||||||
fun authenticated(s:Boolean) {
|
|
||||||
_isAuthenticating.value = s
|
|
||||||
}
|
|
||||||
|
|
||||||
private val _selectedCategory = MutableStateFlow(HomeCategory.About)
|
|
||||||
val selectedCategory :StateFlow<HomeCategory>
|
|
||||||
get() = _selectedCategory
|
|
||||||
|
|
||||||
fun selectCategory(s:HomeCategory) {
|
fun selectCategory(s:HomeCategory) {
|
||||||
_selectedCategory.value = s
|
selectedCategory = s
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _downloadRecordList = MutableStateFlow<List<DownloadRecord>>(listOf())
|
var downloadRecordList by mutableStateOf<List<DownloadRecord>>(listOf())
|
||||||
val downloadRecordList: StateFlow<List<DownloadRecord>>
|
|
||||||
get() = _downloadRecordList
|
|
||||||
|
|
||||||
fun getDownloadRecordList() {
|
fun getDownloadRecordList() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
_downloadRecordList.value = sharedViewModel.databaseDAO.getRecord().toMutableList()
|
downloadRecordList = sharedViewModel.databaseDAO.getRecord()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
getDownloadRecordList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class HomeCategory {
|
enum class HomeCategory {
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
package com.shabinder.spotiflyer.ui.platforms.gaana
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
|
||||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
|
||||||
import com.shabinder.spotiflyer.utils.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Gaana(
|
|
||||||
fullLink: String,
|
|
||||||
navController: NavController
|
|
||||||
) {
|
|
||||||
val source = Source.Gaana
|
|
||||||
var result by remember { mutableStateOf<PlatformQueryResult?>(null) }
|
|
||||||
|
|
||||||
//Coroutine Scope Active till this Composable is Active
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
//Link Schema: https://gaana.com/type/link
|
|
||||||
val gaanaLink = fullLink.substringAfter("gaana.com/")
|
|
||||||
|
|
||||||
val link = gaanaLink.substringAfterLast('/', "error")
|
|
||||||
val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/')
|
|
||||||
|
|
||||||
log("Gaana Fragment", "$type : $link")
|
|
||||||
|
|
||||||
//Error
|
|
||||||
if (type == "Error" || link == "Error"){
|
|
||||||
showDialog("Please Check Your Link!")
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
result = gaanaSearch(
|
|
||||||
type,
|
|
||||||
link,
|
|
||||||
sharedViewModel.gaanaInterface,
|
|
||||||
sharedViewModel.databaseDAO,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
result?.let {
|
|
||||||
TrackList(
|
|
||||||
result = it,
|
|
||||||
source = source
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
package com.shabinder.spotiflyer.ui.platforms.spotify
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
|
||||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
|
||||||
import com.shabinder.spotiflyer.utils.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Spotify(fullLink: String, navController: NavController,) {
|
|
||||||
val source: Source = Source.Spotify
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
var result by remember { mutableStateOf<PlatformQueryResult?>(null) }
|
|
||||||
|
|
||||||
var spotifyLink =
|
|
||||||
"https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim()
|
|
||||||
log("Spotify Fragment Link", spotifyLink)
|
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.Default) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
|
|
||||||
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630
|
|
||||||
* */
|
|
||||||
if (!spotifyLink.contains("open.spotify")) {
|
|
||||||
val resolvedLink = resolveLink(spotifyLink, sharedViewModel.gaanaInterface)
|
|
||||||
log("Spotify Resolved Link", resolvedLink)
|
|
||||||
spotifyLink = resolvedLink
|
|
||||||
}
|
|
||||||
|
|
||||||
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
|
||||||
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
|
|
||||||
|
|
||||||
log("Spotify Fragment", "$type : $link")
|
|
||||||
|
|
||||||
if (sharedViewModel.spotifyService.value == null) {//Authentication pending!!
|
|
||||||
if (isOnline()) mainActivity.authenticateSpotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "Error" || link == "Error") {
|
|
||||||
showDialog("Please Check Your Link!")
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "episode" || type == "show") {
|
|
||||||
//TODO Implementation
|
|
||||||
showDialog("Implementing Soon, Stay Tuned!")
|
|
||||||
} else {
|
|
||||||
if (sharedViewModel.spotifyService.value == null){
|
|
||||||
//Authentication Still Pending
|
|
||||||
// TODO Better Implementation
|
|
||||||
showDialog("Authentication Failed")
|
|
||||||
navController.popBackStack()
|
|
||||||
}else{
|
|
||||||
result = spotifySearch(
|
|
||||||
type,
|
|
||||||
link,
|
|
||||||
sharedViewModel.spotifyService.value!!,
|
|
||||||
sharedViewModel.databaseDAO
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let {
|
|
||||||
log("Spotify",it.trackList.size.toString())
|
|
||||||
TrackList(
|
|
||||||
result = it,
|
|
||||||
source = source
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package com.shabinder.spotiflyer.ui.platforms.youtube
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
|
||||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
|
||||||
import com.shabinder.spotiflyer.utils.showDialog
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
private const val sampleDomain2 = "youtu.be"
|
|
||||||
private const val sampleDomain1 = "youtube.com"
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Youtube(fullLink: String, navController: NavController,) {
|
|
||||||
val source = Source.YouTube
|
|
||||||
var result by remember { mutableStateOf<PlatformQueryResult?>(null) }
|
|
||||||
//Coroutine Scope Active till this Composable is Active
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
val link = fullLink.removePrefix("https://").removePrefix("http://")
|
|
||||||
if(link.contains("playlist",true) || link.contains("list",true)){
|
|
||||||
// Given Link is of a Playlist
|
|
||||||
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&")
|
|
||||||
getYTPlaylist(
|
|
||||||
playlistId,
|
|
||||||
sharedViewModel.ytDownloader,
|
|
||||||
sharedViewModel.databaseDAO
|
|
||||||
)
|
|
||||||
}else{//Given Link is of a Video
|
|
||||||
var searchId = "error"
|
|
||||||
if(link.contains(sampleDomain1,true) ){
|
|
||||||
searchId = link.substringAfterLast("=","error")
|
|
||||||
}
|
|
||||||
if(link.contains(sampleDomain2,true) ){
|
|
||||||
searchId = link.substringAfterLast("/","error")
|
|
||||||
}
|
|
||||||
if(searchId != "error") {
|
|
||||||
result = getYTTrack(
|
|
||||||
searchId,
|
|
||||||
sharedViewModel.ytDownloader,
|
|
||||||
sharedViewModel.databaseDAO
|
|
||||||
)
|
|
||||||
}else{
|
|
||||||
showDialog("Your Youtube Link is not of a Video!!")
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let {
|
|
||||||
TrackList(
|
|
||||||
result = it,
|
|
||||||
source = source
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,6 +8,10 @@ import androidx.compose.material.Icon
|
|||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -19,14 +23,18 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.navigation.NavController
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
|
||||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||||
import com.shabinder.spotiflyer.ui.colorAccent
|
import com.shabinder.spotiflyer.ui.colorAccent
|
||||||
|
import com.shabinder.spotiflyer.providers.queryGaana
|
||||||
|
import com.shabinder.spotiflyer.providers.querySpotify
|
||||||
|
import com.shabinder.spotiflyer.providers.queryYoutube
|
||||||
import com.shabinder.spotiflyer.ui.utils.calculateDominantColor
|
import com.shabinder.spotiflyer.ui.utils.calculateDominantColor
|
||||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||||
|
import com.shabinder.spotiflyer.utils.showDialog
|
||||||
import dev.chrisbanes.accompanist.coil.CoilImage
|
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -36,28 +44,56 @@ import kotlinx.coroutines.launch
|
|||||||
**/
|
**/
|
||||||
@Composable
|
@Composable
|
||||||
fun TrackList(
|
fun TrackList(
|
||||||
result: PlatformQueryResult,
|
fullLink: String,
|
||||||
source: Source,
|
navController: NavController,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
){
|
){
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
Box(modifier = modifier.fillMaxSize()){
|
|
||||||
LazyColumn(
|
var result by remember(fullLink) { mutableStateOf<PlatformQueryResult?>(null) }
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
content = {
|
coroutineScope.launch {
|
||||||
item {
|
if(result == null){
|
||||||
CoverImage(result.title,result.coverUrl,coroutineScope)
|
result = when{
|
||||||
|
//SPOTIFY
|
||||||
|
fullLink.contains("spotify",true) -> querySpotify(fullLink)
|
||||||
|
|
||||||
|
//YOUTUBE
|
||||||
|
fullLink.contains("youtube.com",true) || fullLink.contains("youtu.be",true) -> queryYoutube(fullLink)
|
||||||
|
|
||||||
|
//GAANA
|
||||||
|
fullLink.contains("gaana",true) -> queryGaana(fullLink)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
showDialog("Link is Not Valid")
|
||||||
|
null
|
||||||
}
|
}
|
||||||
items(result.trackList) {
|
}
|
||||||
TrackCard(track = it)
|
}
|
||||||
}
|
//Error Occurred And Has Been Shown to User
|
||||||
},
|
if(result == null) navController.popBackStack()
|
||||||
modifier = Modifier.fillMaxSize(),
|
}
|
||||||
)
|
|
||||||
DownloadAllButton(
|
|
||||||
onClick = {},
|
result?.let{
|
||||||
modifier = Modifier.padding(bottom = 24.dp).align(Alignment.BottomCenter)
|
Box(modifier = modifier.fillMaxSize()){
|
||||||
)
|
LazyColumn(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
content = {
|
||||||
|
item {
|
||||||
|
CoverImage(it.title,it.coverUrl,coroutineScope)
|
||||||
|
}
|
||||||
|
items(it.trackList) {
|
||||||
|
TrackCard(track = it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
DownloadAllButton(
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier.padding(bottom = 24.dp).align(Alignment.BottomCenter)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +123,9 @@ fun CoverImage(
|
|||||||
//color = colorAccent,
|
//color = colorAccent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
scope.launch { updateGradient(coverURL) }
|
scope.launch {
|
||||||
|
updateGradient(coverURL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -26,7 +26,8 @@ const val NoInternetErrorCode = 222
|
|||||||
|
|
||||||
class NetworkInterceptor: Interceptor {
|
class NetworkInterceptor: Interceptor {
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
log("Network Requesting",chain.request().url.toString())
|
log("Network Requesting Debug",chain.request().url.toString())
|
||||||
|
|
||||||
return if (!isOnline()){
|
return if (!isOnline()){
|
||||||
//No Internet Connection
|
//No Internet Connection
|
||||||
showDialog()
|
showDialog()
|
||||||
|
@ -3,15 +3,11 @@ package com.shabinder.spotiflyer.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.shabinder.spotiflyer.App
|
import com.shabinder.spotiflyer.App
|
||||||
import com.shabinder.spotiflyer.SharedViewModel
|
|
||||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
|
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
|
||||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
import com.shabinder.spotiflyer.networking.*
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
|
||||||
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@ -26,6 +22,7 @@ import retrofit2.Retrofit
|
|||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
@ -59,15 +56,26 @@ object Provider {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun getMoshi(): Moshi {
|
fun getTokenStore(
|
||||||
return Moshi.Builder()
|
@ApplicationContext appContext: Context,
|
||||||
.add(KotlinJsonAdapterFactory())
|
spotifyServiceTokenRequest: SpotifyServiceTokenRequest):TokenStore = TokenStore(appContext,spotifyServiceTokenRequest)
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun getSpotifyTokenInterface(moshi: Moshi): SpotifyServiceTokenRequest {
|
fun getSpotifyService(authInterceptor: SpotifyAuthInterceptor,okHttpClient: OkHttpClient.Builder,moshi: Moshi) :SpotifyService{
|
||||||
|
val retrofit = Retrofit.Builder().run{
|
||||||
|
baseUrl("https://api.spotify.com/v1/")
|
||||||
|
client(okHttpClient.addInterceptor(authInterceptor).build())
|
||||||
|
addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
return retrofit.create(SpotifyService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getSpotifyTokenInterface(moshi: Moshi,networkInterceptor: NetworkInterceptor): SpotifyServiceTokenRequest {
|
||||||
val httpClient2: OkHttpClient.Builder = OkHttpClient.Builder()
|
val httpClient2: OkHttpClient.Builder = OkHttpClient.Builder()
|
||||||
.addInterceptor(Interceptor { chain ->
|
.addInterceptor(Interceptor { chain ->
|
||||||
val request: Request =
|
val request: Request =
|
||||||
@ -82,7 +90,7 @@ object Provider {
|
|||||||
}"
|
}"
|
||||||
).build()
|
).build()
|
||||||
chain.proceed(request)
|
chain.proceed(request)
|
||||||
}).addInterceptor(NetworkInterceptor())
|
}).addInterceptor(networkInterceptor)
|
||||||
|
|
||||||
val retrofit = Retrofit.Builder()
|
val retrofit = Retrofit.Builder()
|
||||||
.baseUrl("https://accounts.spotify.com/")
|
.baseUrl("https://accounts.spotify.com/")
|
||||||
@ -94,18 +102,10 @@ object Provider {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun okHttpClient(): OkHttpClient {
|
fun getGaanaInterface(moshi: Moshi, okHttpClient: OkHttpClient.Builder): GaanaInterface {
|
||||||
return OkHttpClient.Builder()
|
|
||||||
.addInterceptor(NetworkInterceptor())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun getGaanaInterface(moshi: Moshi, okHttpClient: OkHttpClient): GaanaInterface {
|
|
||||||
val retrofit = Retrofit.Builder()
|
val retrofit = Retrofit.Builder()
|
||||||
.baseUrl("https://api.gaana.com/")
|
.baseUrl("https://api.gaana.com/")
|
||||||
.client(okHttpClient)
|
.client(okHttpClient.build())
|
||||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
.build()
|
.build()
|
||||||
return retrofit.create(GaanaInterface::class.java)
|
return retrofit.create(GaanaInterface::class.java)
|
||||||
@ -122,4 +122,25 @@ object Provider {
|
|||||||
return retrofit.create(YoutubeMusicApi::class.java)
|
return retrofit.create(YoutubeMusicApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getNetworkInterceptor():NetworkInterceptor = NetworkInterceptor()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun okHttpClient(networkInterceptor: NetworkInterceptor): OkHttpClient.Builder {
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.readTimeout(1, TimeUnit.MINUTES)
|
||||||
|
.writeTimeout(1, TimeUnit.MINUTES)
|
||||||
|
.addInterceptor(networkInterceptor)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getMoshi(): Moshi {
|
||||||
|
return Moshi.Builder()
|
||||||
|
.add(KotlinJsonAdapterFactory())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
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
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Token
|
||||||
|
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||||
|
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,
|
||||||
|
private val spotifyServiceTokenRequest: SpotifyServiceTokenRequest
|
||||||
|
) {
|
||||||
|
private val dataStore = context.createDataStore(
|
||||||
|
name = "settings"
|
||||||
|
)
|
||||||
|
private val token = preferencesKey<String>("token")
|
||||||
|
private val tokenExpiry = preferencesKey<Long>("expiry")
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun saveToken(tokenKey:String,time:Long){
|
||||||
|
dataStore.edit {
|
||||||
|
it[token] = tokenKey
|
||||||
|
it[tokenExpiry] = (System.currentTimeMillis()/1000) + time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getToken(): Token?{
|
||||||
|
var token = dataStore.data.map {
|
||||||
|
Token(it[token],null,it[tokenExpiry])
|
||||||
|
}.firstOrNull()
|
||||||
|
if(System.currentTimeMillis()/1000 > token?.expiry?:0){
|
||||||
|
token = spotifyServiceTokenRequest.getToken().value
|
||||||
|
log("Spotify Token","Requesting New Token")
|
||||||
|
GlobalScope.launch { token?.access_token?.let { saveToken(it,token.expiry ?: 0) } }
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user