From 5c0ec20e1bae7331f73963dfdf43634022b61f3c Mon Sep 17 00:00:00 2001 From: shabinder Date: Sat, 2 Jan 2021 00:36:39 +0530 Subject: [PATCH] Jetpack DataStore, Dependency Injection. --- app/build.gradle | 3 + app/src/main/AndroidManifest.xml | 7 +- .../com/shabinder/spotiflyer/MainActivity.kt | 53 ++----------- .../shabinder/spotiflyer/SharedViewModel.kt | 27 ++++--- .../spotiflyer/models/PlatformQueryResult.kt | 5 +- .../spotiflyer/models/spotify/Token.kt | 7 +- .../navigation/ComposeNavigation.kt | 65 ++------------- .../networking/SpotifyAuthInterceptor.kt | 40 ++++++++++ .../gaana => providers}/GaanaProvider.kt | 31 +++++++- .../spotify => providers}/SpotifyProvider.kt | 42 +++++++++- .../youtube => providers}/YoutubeProvider.kt | 41 +++++++++- .../com/shabinder/spotiflyer/ui/home/Home.kt | 33 +++----- .../spotiflyer/ui/home/HomeViewModel.kt | 38 +++------ .../spotiflyer/ui/platforms/gaana/Gaana.kt | 55 ------------- .../ui/platforms/spotify/Spotify.kt | 79 ------------------- .../ui/platforms/youtube/Youtube.kt | 64 --------------- .../spotiflyer/ui/tracklist/TrackList.kt | 78 +++++++++++++----- .../spotiflyer/utils/NetworkInterceptor.kt | 3 +- .../shabinder/spotiflyer/utils/Provider.kt | 65 +++++++++------ .../shabinder/spotiflyer/utils/TokenStore.kt | 47 +++++++++++ 20 files changed, 367 insertions(+), 416 deletions(-) create mode 100644 app/src/main/java/com/shabinder/spotiflyer/networking/SpotifyAuthInterceptor.kt rename app/src/main/java/com/shabinder/spotiflyer/{ui/platforms/gaana => providers}/GaanaProvider.kt (92%) rename app/src/main/java/com/shabinder/spotiflyer/{ui/platforms/spotify => providers}/SpotifyProvider.kt (87%) rename app/src/main/java/com/shabinder/spotiflyer/{ui/platforms/youtube => providers}/YoutubeProvider.kt (83%) delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/utils/TokenStore.kt diff --git a/app/build.gradle b/app/build.gradle index e268f2e0..11e3d8fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a1ffe0ce..037487a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,10 +41,13 @@ android:launchMode="singleTask"> - - + + + + + diff --git a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index f320c70f..9c16eb8b 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -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) } } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt index ca0b89b9..e9ef7ec5 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt @@ -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(null) + var isAuthenticated by mutableStateOf(false) + private set - private val _gradientColor = MutableStateFlow(colorPrimaryDark) - val gradientColor : StateFlow - 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 } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt b/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt index 40de91f1..4486b800 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt @@ -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 + var trackList: List, + var source: Source ) \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/models/spotify/Token.kt b/app/src/main/java/com/shabinder/spotiflyer/models/spotify/Token.kt index 0d8bdaf8..fe695650 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/models/spotify/Token.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/models/spotify/Token.kt @@ -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 \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt b/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt index 8f5816a9..e28d960a 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt @@ -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 } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/networking/SpotifyAuthInterceptor.kt b/app/src/main/java/com/shabinder/spotiflyer/networking/SpotifyAuthInterceptor.kt new file mode 100644 index 00000000..3e0016ea --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/networking/SpotifyAuthInterceptor.kt @@ -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(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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/providers/GaanaProvider.kt similarity index 92% rename from app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt rename to app/src/main/java/com/shabinder/spotiflyer/providers/GaanaProvider.kt index f942ee9c..95a95bec 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/providers/GaanaProvider.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -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) { diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/providers/SpotifyProvider.kt similarity index 87% rename from app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt rename to app/src/main/java/com/shabinder/spotiflyer/providers/SpotifyProvider.kt index d500feef..83522e13 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/providers/SpotifyProvider.kt @@ -15,9 +15,10 @@ * along with this program. If not, see . */ -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, diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/providers/YoutubeProvider.kt similarity index 83% rename from app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt rename to app/src/main/java/com/shabinder/spotiflyer/providers/YoutubeProvider.kt index aae0283e..a95383fe 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/providers/YoutubeProvider.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -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 { diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt index d1bc72c6..eb72b9e0 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt @@ -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: List, 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, diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/home/HomeViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/home/HomeViewModel.kt index f4ebec17..9d59d1c9 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/home/HomeViewModel.kt @@ -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 - 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 - get() = _isAuthenticating - - fun authenticated(s:Boolean) { - _isAuthenticating.value = s - } - - private val _selectedCategory = MutableStateFlow(HomeCategory.About) - val selectedCategory :StateFlow - get() = _selectedCategory + var selectedCategory by mutableStateOf(HomeCategory.About) + private set fun selectCategory(s:HomeCategory) { - _selectedCategory.value = s + selectedCategory = s } - private val _downloadRecordList = MutableStateFlow>(listOf()) - val downloadRecordList: StateFlow> - get() = _downloadRecordList + var downloadRecordList by mutableStateOf>(listOf()) fun getDownloadRecordList() { viewModelScope.launch { withContext(Dispatchers.IO){ - _downloadRecordList.value = sharedViewModel.databaseDAO.getRecord().toMutableList() + downloadRecordList = sharedViewModel.databaseDAO.getRecord() } } } - - init { - getDownloadRecordList() - } } enum class HomeCategory { diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt deleted file mode 100644 index 4db1fe7a..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt +++ /dev/null @@ -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(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 - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt deleted file mode 100644 index 1b29eed8..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt +++ /dev/null @@ -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(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 - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt deleted file mode 100644 index c27ed658..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt +++ /dev/null @@ -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(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 - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt index c0dedc35..74f289a0 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt @@ -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(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 diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/NetworkInterceptor.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/NetworkInterceptor.kt index 03c271a5..0c1a9702 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/NetworkInterceptor.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/NetworkInterceptor.kt @@ -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() diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt index 76ee63ec..1ed1c821 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt @@ -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() + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/TokenStore.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/TokenStore.kt new file mode 100644 index 00000000..ec9c3486 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/TokenStore.kt @@ -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("token") + private val tokenExpiry = preferencesKey("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 + } + +} \ No newline at end of file