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-insets:$coil_version"
|
||||
|
||||
//DataStore
|
||||
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha05"
|
||||
|
||||
//Extras
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
|
@ -41,10 +41,13 @@
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<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" />
|
||||
</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>
|
||||
|
||||
<!-- Add your API key here -->
|
||||
|
@ -28,8 +28,9 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.jetcaster.util.verticalGradientScrim
|
||||
import com.shabinder.spotiflyer.models.spotify.Token
|
||||
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.SpotifyServiceTokenRequest
|
||||
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
|
||||
@ -40,7 +41,10 @@ import com.squareup.moshi.Moshi
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
|
||||
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@ -69,14 +73,12 @@ class MainActivity : AppCompatActivity() {
|
||||
ComposeLearnTheme {
|
||||
Providers(AmbientContentColor provides colorOffWhite) {
|
||||
ProvideWindowInsets {
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.6f)
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.7f)
|
||||
navController = rememberNavController()
|
||||
|
||||
val gradientColor by sharedViewModel.gradientColor.collectAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
||||
color = gradientColor.copy(alpha = 0.38f),
|
||||
color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
||||
startYPercentage = 1f,
|
||||
endYPercentage = 0f,
|
||||
fixedHeight = 700f,
|
||||
@ -101,7 +103,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun initialize() {
|
||||
authenticateSpotify()
|
||||
requestStoragePermission()
|
||||
disableDozeMode()
|
||||
//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()) {
|
||||
if (intent?.action == Intent.ACTION_SEND) {
|
||||
if ("text/plain" == intent.type) {
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
log("Intent Received", it)
|
||||
navController.navigateToPlatform(it)
|
||||
navController.navigateToTrackList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,41 +17,42 @@
|
||||
|
||||
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.hilt.lifecycle.ViewModelInject
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
||||
import com.shabinder.spotiflyer.ui.colorPrimaryDark
|
||||
import com.shabinder.spotiflyer.ui.home.HomeCategory
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@ActivityRetainedScoped
|
||||
class SharedViewModel @ViewModelInject constructor(
|
||||
val databaseDAO: DatabaseDAO,
|
||||
val spotifyService: SpotifyService,
|
||||
val gaanaInterface : GaanaInterface,
|
||||
val ytDownloader: YoutubeDownloader
|
||||
) : ViewModel() {
|
||||
var spotifyService = MutableStateFlow<SpotifyService?>(null)
|
||||
var isAuthenticated by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private val _gradientColor = MutableStateFlow(colorPrimaryDark)
|
||||
val gradientColor : StateFlow<Color>
|
||||
get() = _gradientColor
|
||||
fun authenticated(s:Boolean) {
|
||||
isAuthenticated = s
|
||||
}
|
||||
|
||||
var gradientColor by mutableStateOf(colorPrimaryDark)
|
||||
private set
|
||||
|
||||
fun updateGradientColor(color: Color) {
|
||||
_gradientColor.value = color
|
||||
gradientColor = color
|
||||
}
|
||||
|
||||
fun resetGradient() {
|
||||
_gradientColor.value = colorPrimaryDark
|
||||
gradientColor = colorPrimaryDark
|
||||
}
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
package com.shabinder.spotiflyer.models
|
||||
|
||||
import com.shabinder.spotiflyer.models.spotify.Source
|
||||
|
||||
data class PlatformQueryResult(
|
||||
var folderType: String,
|
||||
var subFolder: String,
|
||||
var title: 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
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class Token(
|
||||
var access_token:String,
|
||||
var token_type:String,
|
||||
var expires_in:Int
|
||||
var access_token:String?,
|
||||
var token_type:String?,
|
||||
@Json(name = "expires_in") var expiry:Long?
|
||||
): Parcelable
|
@ -7,12 +7,7 @@ import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.*
|
||||
import androidx.navigation.compose.popUpTo
|
||||
import com.shabinder.spotiflyer.ui.home.Home
|
||||
import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana
|
||||
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
|
||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
||||
|
||||
@Composable
|
||||
fun ComposeNavigation(navController: NavHostController) {
|
||||
@ -29,34 +24,10 @@ fun ComposeNavigation(navController: NavHostController) {
|
||||
//Spotify Screen
|
||||
//Argument `link` = Link of Track/Album/Playlist
|
||||
composable(
|
||||
"spotify/{link}",
|
||||
"track_list/{link}",
|
||||
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
||||
) {
|
||||
Spotify(
|
||||
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(
|
||||
TrackList(
|
||||
fullLink = it.arguments?.getString("link") ?: "error",
|
||||
navController = navController
|
||||
)
|
||||
@ -64,34 +35,10 @@ fun ComposeNavigation(navController: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.navigateToPlatform(link:String){
|
||||
when{
|
||||
//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){
|
||||
fun NavController.navigateToTrackList(link:String, singleInstance: Boolean = true, inclusive:Boolean = false) {
|
||||
navigate("track_list/$link") {
|
||||
launchSingleTop = singleInstance
|
||||
popUpTo(route = "home"){
|
||||
popUpTo(route = "home") {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui.platforms.gaana
|
||||
package com.shabinder.spotiflyer.providers
|
||||
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
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.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.queryActiveTracks
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
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(
|
||||
type:String,
|
||||
link:String,
|
||||
@ -46,6 +70,7 @@ suspend fun gaanaSearch(
|
||||
title = link,
|
||||
coverUrl = gaanaPlaceholderImageUrl,
|
||||
trackList = listOf(),
|
||||
Source.Gaana
|
||||
)
|
||||
with(result) {
|
||||
when (type) {
|
@ -15,9 +15,10 @@
|
||||
* 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.compose.runtime.Composable
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||
@ -34,6 +35,40 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
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(
|
||||
type:String,
|
||||
link: String,
|
||||
@ -46,6 +81,7 @@ suspend fun spotifySearch(
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.Spotify
|
||||
)
|
||||
with(result) {
|
||||
when (type) {
|
||||
@ -192,6 +228,10 @@ suspend fun spotifySearch(
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
* New Link Schema: https://link.tospotify.com/kqTBblrjQbb,
|
||||
* Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630
|
||||
* */
|
||||
@WorkerThread
|
||||
fun resolveLink(
|
||||
url:String,
|
@ -15,7 +15,7 @@
|
||||
* 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 com.github.kiulian.downloader.YoutubeDownloader
|
||||
@ -37,6 +37,43 @@ import java.io.File
|
||||
* 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(
|
||||
searchId: String,
|
||||
ytDownloader: YoutubeDownloader,
|
||||
@ -48,6 +85,7 @@ suspend fun getYTPlaylist(
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.YouTube
|
||||
)
|
||||
with(result) {
|
||||
try {
|
||||
@ -127,6 +165,7 @@ suspend fun getYTTrack(
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.YouTube
|
||||
)
|
||||
with(result) {
|
||||
try {
|
@ -15,8 +15,6 @@ import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -34,14 +32,13 @@ import androidx.core.net.toUri
|
||||
import androidx.navigation.NavController
|
||||
import com.shabinder.spotiflyer.R
|
||||
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.colorAccent
|
||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
||||
import com.shabinder.spotiflyer.utils.openPlatform
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import dev.chrisbanes.accompanist.glide.GlideImage
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Composable
|
||||
fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
||||
@ -49,26 +46,23 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
|
||||
|
||||
Column(modifier = modifier) {
|
||||
|
||||
val link by viewModel.link.collectAsState()
|
||||
|
||||
AuthenticationBanner(viewModel,modifier)
|
||||
AuthenticationBanner(sharedViewModel.isAuthenticated,modifier)
|
||||
|
||||
SearchPanel(
|
||||
link,
|
||||
viewModel.link,
|
||||
viewModel::updateLink,
|
||||
navController,
|
||||
modifier
|
||||
)
|
||||
val selectedCategory by viewModel.selectedCategory.collectAsState()
|
||||
|
||||
HomeTabBar(
|
||||
selectedCategory,
|
||||
viewModel.selectedCategory,
|
||||
HomeCategory.values(),
|
||||
viewModel::selectCategory,
|
||||
modifier
|
||||
)
|
||||
|
||||
when(selectedCategory){
|
||||
when(viewModel.selectedCategory){
|
||||
HomeCategory.About -> AboutColumn()
|
||||
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
||||
}
|
||||
@ -212,11 +206,9 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
fun HistoryColumn(
|
||||
downloadRecordList: StateFlow<List<DownloadRecord>>,
|
||||
list: List<DownloadRecord>,
|
||||
navController: NavController
|
||||
) {
|
||||
val list by downloadRecordList.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = {
|
||||
@ -254,16 +246,15 @@ fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
||||
}
|
||||
Image(
|
||||
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
|
||||
fun AuthenticationBanner(viewModel: HomeViewModel, modifier: Modifier) {
|
||||
val authenticationStatus by viewModel.isAuthenticating.collectAsState()
|
||||
fun AuthenticationBanner(isAuthenticated: Boolean, modifier: Modifier) {
|
||||
|
||||
if (authenticationStatus) {
|
||||
if (!isAuthenticated) {
|
||||
// TODO show a progress indicator or similar
|
||||
}
|
||||
}
|
||||
@ -321,7 +312,7 @@ fun SearchPanel(
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.padding(top = 16.dp,)
|
||||
modifier = modifier.padding(top = 16.dp)
|
||||
){
|
||||
TextField(
|
||||
leadingIcon = {
|
||||
@ -346,7 +337,7 @@ fun SearchPanel(
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(12.dp).wrapContentWidth(),
|
||||
onClick = {
|
||||
navController.navigateToPlatform(link)
|
||||
navController.navigateToTrackList(link)
|
||||
},
|
||||
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
|
||||
){
|
||||
@ -354,6 +345,8 @@ fun SearchPanel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun HomeCategoryTabIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -2,56 +2,40 @@ package com.shabinder.spotiflyer.ui.home
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
|
||||
private val _link = MutableStateFlow("")
|
||||
val link:StateFlow<String>
|
||||
get() = _link
|
||||
var link by mutableStateOf("")
|
||||
private set
|
||||
|
||||
fun updateLink(s:String) {
|
||||
_link.value = s
|
||||
link = s
|
||||
}
|
||||
|
||||
private val _isAuthenticating = MutableStateFlow(true)
|
||||
val isAuthenticating:StateFlow<Boolean>
|
||||
get() = _isAuthenticating
|
||||
|
||||
fun authenticated(s:Boolean) {
|
||||
_isAuthenticating.value = s
|
||||
}
|
||||
|
||||
private val _selectedCategory = MutableStateFlow(HomeCategory.About)
|
||||
val selectedCategory :StateFlow<HomeCategory>
|
||||
get() = _selectedCategory
|
||||
var selectedCategory by mutableStateOf(HomeCategory.About)
|
||||
private set
|
||||
|
||||
fun selectCategory(s:HomeCategory) {
|
||||
_selectedCategory.value = s
|
||||
selectedCategory = s
|
||||
}
|
||||
|
||||
private val _downloadRecordList = MutableStateFlow<List<DownloadRecord>>(listOf())
|
||||
val downloadRecordList: StateFlow<List<DownloadRecord>>
|
||||
get() = _downloadRecordList
|
||||
var downloadRecordList by mutableStateOf<List<DownloadRecord>>(listOf())
|
||||
|
||||
fun getDownloadRecordList() {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
_downloadRecordList.value = sharedViewModel.databaseDAO.getRecord().toMutableList()
|
||||
downloadRecordList = sharedViewModel.databaseDAO.getRecord()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
getDownloadRecordList()
|
||||
}
|
||||
}
|
||||
|
||||
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.Text
|
||||
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.ui.Alignment
|
||||
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.sp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.NavController
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.models.PlatformQueryResult
|
||||
import com.shabinder.spotiflyer.models.TrackDetails
|
||||
import com.shabinder.spotiflyer.models.spotify.Source
|
||||
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.sharedViewModel
|
||||
import com.shabinder.spotiflyer.utils.showDialog
|
||||
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@ -36,28 +44,56 @@ import kotlinx.coroutines.launch
|
||||
**/
|
||||
@Composable
|
||||
fun TrackList(
|
||||
result: PlatformQueryResult,
|
||||
source: Source,
|
||||
fullLink: String,
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier
|
||||
){
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
Box(modifier = modifier.fillMaxSize()){
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = {
|
||||
item {
|
||||
CoverImage(result.title,result.coverUrl,coroutineScope)
|
||||
|
||||
var result by remember(fullLink) { mutableStateOf<PlatformQueryResult?>(null) }
|
||||
|
||||
coroutineScope.launch {
|
||||
if(result == null){
|
||||
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)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
DownloadAllButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.padding(bottom = 24.dp).align(Alignment.BottomCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
//Error Occurred And Has Been Shown to User
|
||||
if(result == null) navController.popBackStack()
|
||||
}
|
||||
|
||||
|
||||
result?.let{
|
||||
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,
|
||||
)
|
||||
}
|
||||
scope.launch { updateGradient(coverURL) }
|
||||
scope.launch {
|
||||
updateGradient(coverURL)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -26,7 +26,8 @@ const val NoInternetErrorCode = 222
|
||||
|
||||
class NetworkInterceptor: Interceptor {
|
||||
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()){
|
||||
//No Internet Connection
|
||||
showDialog()
|
||||
|
@ -3,15 +3,11 @@ package com.shabinder.spotiflyer.utils
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.util.Base64
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.github.kiulian.downloader.YoutubeDownloader
|
||||
import com.shabinder.spotiflyer.App
|
||||
import com.shabinder.spotiflyer.SharedViewModel
|
||||
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
|
||||
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
||||
import com.shabinder.spotiflyer.networking.*
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import dagger.Module
|
||||
@ -26,6 +22,7 @@ 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
|
||||
|
||||
|
||||
@ -59,15 +56,26 @@ object Provider {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun getMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
fun getTokenStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
spotifyServiceTokenRequest: SpotifyServiceTokenRequest):TokenStore = TokenStore(appContext,spotifyServiceTokenRequest)
|
||||
|
||||
@Provides
|
||||
@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()
|
||||
.addInterceptor(Interceptor { chain ->
|
||||
val request: Request =
|
||||
@ -82,7 +90,7 @@ object Provider {
|
||||
}"
|
||||
).build()
|
||||
chain.proceed(request)
|
||||
}).addInterceptor(NetworkInterceptor())
|
||||
}).addInterceptor(networkInterceptor)
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://accounts.spotify.com/")
|
||||
@ -94,18 +102,10 @@ object Provider {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun okHttpClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(NetworkInterceptor())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun getGaanaInterface(moshi: Moshi, okHttpClient: OkHttpClient): GaanaInterface {
|
||||
fun getGaanaInterface(moshi: Moshi, okHttpClient: OkHttpClient.Builder): GaanaInterface {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://api.gaana.com/")
|
||||
.client(okHttpClient)
|
||||
.client(okHttpClient.build())
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.build()
|
||||
return retrofit.create(GaanaInterface::class.java)
|
||||
@ -122,4 +122,25 @@ object Provider {
|
||||
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