diff --git a/android/src/main/java/com/shabinder/android/MainActivity.kt b/android/src/main/java/com/shabinder/android/MainActivity.kt index 8c8a80bf..0837adbd 100644 --- a/android/src/main/java/com/shabinder/android/MainActivity.kt +++ b/android/src/main/java/com/shabinder/android/MainActivity.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.os.PowerManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.material.Surface import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.jetbrains.rootComponent import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory @@ -22,6 +23,7 @@ import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRootContent import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.ui.SpotiFlyerTheme +import com.shabinder.common.ui.colorOffWhite import com.shabinder.database.Database import kotlinx.coroutines.* import org.koin.android.ext.android.inject @@ -41,7 +43,9 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { SpotiFlyerTheme { + Surface(contentColor = colorOffWhite) { root = SpotiFlyerRootContent(rootComponent(::spotiFlyerRoot)) + } } } initialise() diff --git a/build.gradle.kts b/build.gradle.kts index 7f7a23bf..4ded7590 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,4 +13,4 @@ allprojects { maven(url = "https://kotlin.bintray.com/kotlin-js-wrappers/") maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev") } -} \ No newline at end of file +} diff --git a/common/compose-ui/build.gradle.kts b/common/compose-ui/build.gradle.kts index 2be19d62..45d9d490 100644 --- a/common/compose-ui/build.gradle.kts +++ b/common/compose-ui/build.gradle.kts @@ -23,4 +23,4 @@ kotlin { } } } -} +} \ No newline at end of file diff --git a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerListUi.kt b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerListUi.kt index c59180fd..47c3b1f1 100644 --- a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerListUi.kt +++ b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerListUi.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @Composable fun SpotiFlyerListContent( @@ -67,13 +68,8 @@ fun TrackCard( loadImage:suspend (String)-> ImageBitmap? ) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { - var pic by mutableStateOf(null) - val scope = rememberCoroutineScope() - LaunchedEffect((1..10).random()){ - pic = loadImage(track.albumArtURL) - } ImageLoad( - pic = pic, + {loadImage(track.albumArtURL)}, "Album Art", modifier = Modifier .width(75.dp) @@ -128,14 +124,8 @@ fun CoverImage( modifier.padding(vertical = 8.dp).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - var pic by mutableStateOf(null) - LaunchedEffect(true){ - scope.launch(dispatcherIO) { - pic = loadImage(coverURL) - } - } ImageLoad( - pic, + { loadImage(coverURL) }, "Cover Image", modifier = Modifier .width(210.dp) diff --git a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt index 32b3a64a..eba4e3ad 100644 --- a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt +++ b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/main/SpotiFlyerMainUi.kt @@ -30,8 +30,6 @@ import com.shabinder.common.main.SpotiFlyerMain.HomeCategory import com.shabinder.common.di.openPlatform import com.shabinder.common.ui.* import com.shabinder.common.ui.SpotiFlyerTypography -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch @Composable fun SpotiFlyerMainContent(component: SpotiFlyerMain){ @@ -326,13 +324,15 @@ fun DownloadRecordItem( onItemClicked:(String)->Unit ) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) { - val scope = rememberCoroutineScope() - var pic by mutableStateOf(null) - scope.launch(dispatcherIO) { - pic = loadImage(item.coverUrl) - } + /*KamelImage( + lazyImageResource(item.coverUrl), + "Album Art", + modifier = Modifier.height(75.dp).width(90.dp), + crossfade = true, + onLoading = { PlaceHolderImage() } + )*/ ImageLoad( - pic, + { loadImage(item.coverUrl) }, "Album Art", modifier = Modifier.height(75.dp).width(90.dp) ) diff --git a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/ui/ExpectImages.kt b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/ui/ExpectImages.kt index 693fabf4..04a37e3c 100644 --- a/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/ui/ExpectImages.kt +++ b/common/compose-ui/src/commonMain/kotlin/com/shabinder/common/ui/ExpectImages.kt @@ -1,15 +1,25 @@ @file:Suppress("FunctionName") package com.shabinder.common.ui +import androidx.compose.animation.Crossfade import androidx.compose.foundation.Image -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.vector.ImageVector +import kotlinx.coroutines.withContext @Composable -fun ImageLoad(pic: ImageBitmap?, desc: String, modifier:Modifier = Modifier, placeholder:ImageVector = PlaceHolderImage()) { - if(pic == null) Image(placeholder, desc, modifier) else Image(pic, desc, modifier) +fun ImageLoad(loader: suspend () -> ImageBitmap?, desc: String = "Album Art", modifier:Modifier = Modifier, placeholder:ImageVector = PlaceHolderImage()) { + var pic by remember { mutableStateOf(null) } + Crossfade(pic){ + if(pic == null) Image(placeholder, desc, modifier) else Image(pic!!, desc, modifier) + } + LaunchedEffect(loader){ + withContext(dispatcherIO) { + pic = loader() + } + } } @Composable diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/gaana/GaanaArtistTracks.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/gaana/GaanaArtistTracks.kt index 15279cde..75cc908d 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/gaana/GaanaArtistTracks.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/gaana/GaanaArtistTracks.kt @@ -21,5 +21,5 @@ import kotlinx.serialization.Serializable @Serializable data class GaanaArtistTracks( val count : Int, - val tracks : List + val tracks : List? = null ) \ No newline at end of file diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/TokenStore.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/TokenStore.kt index a02cee89..3501b2a6 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/TokenStore.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/TokenStore.kt @@ -16,7 +16,7 @@ class TokenStore( private val db: TokenDBQueries get() = tokenDB.tokenDBQueries - private suspend fun save(token: TokenData){ + private fun save(token: TokenData){ if(!token.access_token.isNullOrBlank() && token.expiry != null) db.add(token.access_token!!, token.expiry!! + Clock.System.now().epochSeconds) } @@ -25,6 +25,7 @@ class TokenStore( var token: TokenData? = db.select().executeAsOneOrNull()?.let { TokenData(it.accessToken,null,it.expiry) } + logger.d{"System Time:${Clock.System.now().epochSeconds} , Token Expiry:${token?.expiry}"} if(Clock.System.now().epochSeconds > token?.expiry ?:0 || token == null){ logger.d{"Requesting New Token"} token = authenticateSpotify() diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt index 6ec29d3a..7273f42d 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt @@ -85,7 +85,7 @@ class GaanaProvider( dir.defaultDir() ) )) {//Download Already Present!! - it.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded + it.downloaded = DownloadStatus.Downloaded } trackList = listOf(it).toTrackDetailsList(folderType, subFolder) title = it.track_title @@ -115,7 +115,7 @@ class GaanaProvider( ) ) ) {//Download Already Present!! - track.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded + track.downloaded = DownloadStatus.Downloaded } } trackList = it.tracks.toTrackDetailsList(folderType, subFolder) @@ -146,7 +146,7 @@ class GaanaProvider( ) ) ) {//Download Already Present!! - track.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded + track.downloaded = DownloadStatus.Downloaded } } trackList = it.tracks.toTrackDetailsList(folderType, subFolder) @@ -175,7 +175,7 @@ class GaanaProvider( coverUrl = it.artworkLink ?: gaanaPlaceholderImageUrl } getGaanaArtistTracks(seokey = link).also { - it.tracks.forEach { track -> + it.tracks?.forEach { track -> if (dir.isPresent( dir.finalOutputDir( track.track_title, @@ -185,10 +185,10 @@ class GaanaProvider( ) ) ) {//Download Already Present!! - track.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded + track.downloaded = DownloadStatus.Downloaded } } - trackList = it.tracks.toTrackDetailsList(folderType, subFolder) + trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList() withContext(Dispatchers.Default) { db.add( type = "Artist", diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt index d327b634..50b01c58 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt @@ -19,8 +19,11 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Kermit import com.shabinder.common.database.DownloadRecordDatabaseQueries import com.shabinder.common.di.Dir +import com.shabinder.common.di.TokenStore import com.shabinder.common.di.finalOutputDir +import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.spotify.SpotifyRequests +import com.shabinder.common.di.spotify.authenticateSpotify import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.spotify.Album @@ -29,16 +32,40 @@ import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Track import com.shabinder.database.Database import io.ktor.client.* +import io.ktor.client.features.* +import io.ktor.client.features.json.* +import io.ktor.client.request.* +import io.ktor.http.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class SpotifyProvider( - override val httpClient: HttpClient, + private val tokenStore: TokenStore, private val database: Database, private val logger: Kermit, private val dir: Dir, ) : SpotifyRequests { + init { + logger.d { "Creating Spotify Provider" } + GlobalScope.launch(Dispatchers.Default) { + val token = tokenStore.getToken() + httpClient = HttpClient { + defaultRequest { + header("Authorization","Bearer ${token.access_token}") + } + install(JsonFeature) { + serializer = kotlinxSerializer + } + } + logger.d { "Spotify Provider Created with $token" } + } + } + + override lateinit var httpClient: HttpClient + private val db:DownloadRecordDatabaseQueries get() = database.downloadRecordDatabaseQueries diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt index 273569a8..dbd75ab5 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyAuth.kt @@ -2,6 +2,7 @@ package com.shabinder.common.di.spotify import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.models.spotify.TokenData +import com.shabinder.database.Database import io.ktor.client.* import io.ktor.client.features.auth.* import io.ktor.client.features.auth.providers.* @@ -9,6 +10,7 @@ import io.ktor.client.features.json.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.http.* +import kotlinx.datetime.Clock suspend fun authenticateSpotify(): TokenData { return spotifyAuthClient.post("https://accounts.spotify.com/api/token"){ diff --git a/desktop/src/jvmMain/kotlin/Main.kt b/desktop/src/jvmMain/kotlin/Main.kt index 71fa0f9a..85c7a845 100644 --- a/desktop/src/jvmMain/kotlin/Main.kt +++ b/desktop/src/jvmMain/kotlin/Main.kt @@ -17,6 +17,7 @@ import com.shabinder.common.root.SpotiFlyerRootContent import com.shabinder.common.ui.SpotiFlyerColors import com.shabinder.common.ui.SpotiFlyerShapes import com.shabinder.common.ui.SpotiFlyerTypography +import com.shabinder.common.ui.colorOffWhite import com.shabinder.database.Database private val koin = initKoin(enableNetworkLogs = true).koin @@ -29,7 +30,8 @@ fun main(){ Window("SpotiFlyer") { Surface( modifier = Modifier.fillMaxSize(), - color = Color.Black + color = Color.Black, + contentColor = colorOffWhite ) { DesktopMaterialTheme( colors = SpotiFlyerColors,