From bf6463a3e12fc6b1813a1561675fa681fb446fb5 Mon Sep 17 00:00:00 2001 From: shabinder Date: Fri, 26 Feb 2021 05:29:54 +0530 Subject: [PATCH] Android Network Connectivity --- android/build.gradle.kts | 10 ++ android/proguard-rules.pro | 15 ++- android/src/main/AndroidManifest.xml | 1 + .../com/shabinder/spotiflyer/MainActivity.kt | 5 +- .../spotiflyer/utils/NetworkDialog.kt | 73 +++++++++++ buildSrc/build.gradle.kts | 3 + buildSrc/buildSrc/src/main/kotlin/Versions.kt | 2 +- .../src/androidMain/AndroidManifest.xml | 2 +- .../src/androidMain/AndroidManifest.xml | 2 +- .../com/shabinder/common/database/Actual.kt | 2 +- common/dependency-injection/build.gradle.kts | 3 + .../src/androidMain/AndroidManifest.xml | 4 +- .../com/shabinder/common/di/AndroidActual.kt | 5 + .../common/di/AndroidNetworkObserver.kt | 122 ++++++++++++++++++ .../com/shabinder/common/di/LiveDataExt.kt | 33 +++++ .../kotlin/com/shabinder/common/di/DI.kt | 13 +- .../shabinder/common/di/NetworkResponse.kt | 6 + .../com/shabinder/common/di/TokenStore.kt | 4 +- .../common/di/providers/SpotifyProvider.kt | 25 +++- .../common/di/spotify/SpotifyAuth.kt | 7 +- .../common/di/spotify/SpotifyRequests.kt | 2 + .../com/shabinder/common/di/DesktopActual.kt | 17 +++ .../list/src/androidMain/AndroidManifest.xml | 2 +- .../main/src/androidMain/AndroidManifest.xml | 2 +- .../root/src/androidMain/AndroidManifest.xml | 2 +- desktop/build.gradle.kts | 1 + 26 files changed, 328 insertions(+), 35 deletions(-) create mode 100644 android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt create mode 100644 common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidNetworkObserver.kt create mode 100644 common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/LiveDataExt.kt create mode 100644 common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/NetworkResponse.kt diff --git a/android/build.gradle.kts b/android/build.gradle.kts index e12073fb..c738d302 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -4,6 +4,9 @@ plugins { id("com.android.application") kotlin("android") id("org.jetbrains.compose") + id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") + id("com.google.firebase.firebase-perf") } group = "com.shabinder" @@ -75,6 +78,12 @@ dependencies { implementation(Decompose.decompose) implementation(Decompose.extensionsCompose) + //Firebase + implementation(platform("com.google.firebase:firebase-bom:26.5.0")) + implementation("com.google.firebase:firebase-analytics-ktx") + implementation("com.google.firebase:firebase-crashlytics-ktx") + implementation("com.google.firebase:firebase-perf-ktx") + /* //Lifecycle Versions.androidLifecycle.let{ @@ -85,6 +94,7 @@ dependencies { } */ + Extras.Android.apply { implementation(appUpdator) implementation(razorpay) diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index bc8e382d..3ecf9dfd 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -21,6 +21,8 @@ #-renamesourcefileattribute SourceFile -keepattributes *Annotation*, InnerClasses -dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations +-keepattributes SourceFile,LineNumberTable # Keep file names and line numbers. +-keep public class * extends java.lang.Exception # Optional: Keep custom exceptions. # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer -keepclassmembers class kotlinx.serialization.json.** { @@ -29,11 +31,14 @@ -keepclasseswithmembers class kotlinx.serialization.json.** { kotlinx.serialization.KSerializer serializer(...); } - --keep,includedescriptorclasses class com.shabinder.spotiflyer.**$$serializer { *; } # <-- change package name to your app's --keepclassmembers class com.shabinder.spotiflyer.** { # <-- change package name to your app's +-keep class com.shabinder.** { *; } +-keep,includedescriptorclasses class com.shabinder.**$$serializer { *; } # <-- change package name to your app's +-keepclassmembers class com.shabinder.** { # <-- change package name to your app's *** Companion; } --keepclasseswithmembers class com.shabinder.spotiflyer.** { # <-- change package name to your app's +-keepclasseswithmembers class com.shabinder.** { # <-- change package name to your app's kotlinx.serialization.KSerializer serializer(...); -} \ No newline at end of file +} +# Ktor +-keep class io.ktor.** { *; } +-keep class kotlinx.coroutines.** { *; } \ No newline at end of file diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 333c6873..77898321 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + diff --git a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index 06168b92..74f880c9 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -45,9 +45,7 @@ import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.uikit.* import com.shabinder.database.Database -import com.shabinder.spotiflyer.utils.checkIfLatestVersion -import com.shabinder.spotiflyer.utils.disableDozeMode -import com.shabinder.spotiflyer.utils.requestStoragePermission +import com.shabinder.spotiflyer.utils.* import com.tonyodev.fetch2.Status import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -100,6 +98,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener { if(askForPermission && !permissionGranted.value) permissionDialog() + NetworkDialog() root = SpotiFlyerRootContent(rememberRootComponent(::spotiFlyerRoot),statusBarHeight) } } diff --git a/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt b/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt new file mode 100644 index 00000000..f76c81c3 --- /dev/null +++ b/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt @@ -0,0 +1,73 @@ +package com.shabinder.spotiflyer.utils + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.CloudOff +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.shabinder.common.di.isInternetAvailable +import com.shabinder.common.di.isInternetAvailableState +import com.shabinder.common.uikit.SpotiFlyerShapes +import com.shabinder.common.uikit.SpotiFlyerTypography +import com.shabinder.common.uikit.colorOffWhite +import kotlinx.coroutines.delay + +@Composable +fun NetworkDialog( + networkAvailability: State = isInternetAvailableState() +){ + var visible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit){ + delay(2600) + visible = true + } + if(networkAvailability.value == false){ + Crossfade(visible){ + if(it){ + AlertDialog( + onDismissRequest = {}, + buttons = { + /* TextButton({ + //Retry Network Connection + }, + Modifier.padding(bottom = 16.dp,start = 16.dp,end = 16.dp).fillMaxWidth().background(Color(0xFFFC5C7D),shape = RoundedCornerShape(size = 8.dp)).padding(horizontal = 8.dp), + ){ + Text("Retry",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) + Icon(Icons.Rounded.SyncProblem,"Check Network Connection Again") + } + */}, + title = { Text("No Internet Connection!", + style = SpotiFlyerTypography.h5, + textAlign = TextAlign.Center) }, + backgroundColor = Color.DarkGray, + text = { + Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){ + Spacer(modifier = Modifier.padding(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp) + ) { + Image(Icons.Rounded.CloudOff,"No Internet.",Modifier.size(42.dp),colorFilter = ColorFilter.tint( + colorOffWhite)) + Spacer(modifier = Modifier.padding(start = 16.dp)) + Text( + text = "Please Check Your Network Connection.", + style = SpotiFlyerTypography.subtitle1 + ) + } + } + } + ,shape = SpotiFlyerShapes.medium) + } + } + } +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 26453ee3..a7f96ee8 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,6 +16,9 @@ repositories { dependencies { implementation("com.android.tools.build:gradle:4.0.1") + implementation("com.google.gms:google-services:4.3.5") + implementation("com.google.firebase:perf-plugin:1.3.4") + implementation("com.google.firebase:firebase-crashlytics-gradle:2.5.0") implementation(JetBrains.Compose.gradlePlugin) implementation(JetBrains.Kotlin.gradlePlugin) implementation(JetBrains.Kotlin.serialization) diff --git a/buildSrc/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/buildSrc/src/main/kotlin/Versions.kt index e5a7aca3..6c5a6212 100644 --- a/buildSrc/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/buildSrc/src/main/kotlin/Versions.kt @@ -27,7 +27,7 @@ object Versions { //Android const val versionCode = 15 const val minSdkVersion = 24 - const val compileSdkVersion = 30 + const val compileSdkVersion = 29 const val targetSdkVersion = 29 const val androidLifecycle = "2.3.0" } diff --git a/common/compose/src/androidMain/AndroidManifest.xml b/common/compose/src/androidMain/AndroidManifest.xml index 17d82132..1e7f8ead 100644 --- a/common/compose/src/androidMain/AndroidManifest.xml +++ b/common/compose/src/androidMain/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/common/data-models/src/androidMain/AndroidManifest.xml b/common/data-models/src/androidMain/AndroidManifest.xml index 17d82132..a9a2d83c 100644 --- a/common/data-models/src/androidMain/AndroidManifest.xml +++ b/common/data-models/src/androidMain/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/common/database/src/androidMain/kotlin/com/shabinder/common/database/Actual.kt b/common/database/src/androidMain/kotlin/com/shabinder/common/database/Actual.kt index 45339fd1..fa7a76a6 100644 --- a/common/database/src/androidMain/kotlin/com/shabinder/common/database/Actual.kt +++ b/common/database/src/androidMain/kotlin/com/shabinder/common/database/Actual.kt @@ -19,4 +19,4 @@ actual fun createDatabase(): Database { val driver = AndroidSqliteDriver(Database.Schema, appContext, "Database.db") return Database(driver) } -actual fun getLogger(): Logger = LogcatLogger() +actual fun getLogger(): Logger = LogcatLogger() \ No newline at end of file diff --git a/common/dependency-injection/build.gradle.kts b/common/dependency-injection/build.gradle.kts index f8c411d4..c99266c4 100644 --- a/common/dependency-injection/build.gradle.kts +++ b/common/dependency-injection/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.compose.compose + plugins { id("multiplatform-compose-setup") id("android-setup") @@ -8,6 +10,7 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(compose.materialIconsExtended) implementation(project(":common:data-models")) implementation(project(":common:database")) implementation(project(":fuzzywuzzy:app")) diff --git a/common/dependency-injection/src/androidMain/AndroidManifest.xml b/common/dependency-injection/src/androidMain/AndroidManifest.xml index 17d82132..7262d16c 100644 --- a/common/dependency-injection/src/androidMain/AndroidManifest.xml +++ b/common/dependency-injection/src/androidMain/AndroidManifest.xml @@ -1,2 +1,4 @@ - + + + diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt index 3174c3eb..5b3b736d 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidActual.kt @@ -4,8 +4,13 @@ import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.net.Uri +import androidx.compose.material.MaterialTheme +import androidx.compose.material.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.core.content.ContextCompat import com.github.kiulian.downloader.model.YoutubeVideo import com.github.kiulian.downloader.model.formats.Format diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidNetworkObserver.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidNetworkObserver.kt new file mode 100644 index 00000000..68a9f98e --- /dev/null +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidNetworkObserver.kt @@ -0,0 +1,122 @@ +package com.shabinder.common.di + +import android.content.Context +import android.content.Context.CONNECTIVITY_SERVICE +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkRequest +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.lifecycle.LiveData +import com.shabinder.common.database.appContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException +import java.net.InetSocketAddress +import javax.net.SocketFactory + +const val TAG = "C-Manager" + +val isInternetAvailable by lazy { ConnectionLiveData(appContext) } + +@Composable +fun isInternetAvailableState(): State{ + return isInternetAvailable.observeAsState() +} + +/** + * Save all available networks with an internet connection to a set (@validNetworks). + * As long as the size of the set > 0, this LiveData emits true. + * MinSdk = 21. + * + * Inspired by: + * https://github.com/AlexSheva-mason/Rick-Morty-Database/blob/master/app/src/main/java/com/shevaalex/android/rickmortydatabase/utils/networking/ConnectionLiveData.kt + */ +class ConnectionLiveData(context: Context = appContext) : LiveData() { + + private lateinit var networkCallback: ConnectivityManager.NetworkCallback + private val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + private val validNetworks: MutableSet = HashSet() + + private fun checkValidNetworks() { + postValue(validNetworks.size > 0) + } + + override fun onActive() { + checkValidNetworks() + networkCallback = createNetworkCallback() + val networkRequest = NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build() + cm.registerNetworkCallback(networkRequest, networkCallback) + } + + override fun onInactive() { + cm.unregisterNetworkCallback(networkCallback) + } + + private fun createNetworkCallback() = object : ConnectivityManager.NetworkCallback() { + + /* + Called when a network is detected. If that network has internet, save it in the Set. + Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network) + */ + override fun onAvailable(network: Network) { + Log.d(TAG, "onAvailable: ${network}") + val networkCapabilities = cm.getNetworkCapabilities(network) + val hasInternetCapability = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET) + Log.d(TAG, "onAvailable: ${network}, $hasInternetCapability") + if (hasInternetCapability == true) { + // check if this network actually has internet + CoroutineScope(Dispatchers.IO).launch { + val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory) + if(hasInternet){ + withContext(Dispatchers.Main){ + Log.d(TAG, "onAvailable: adding network. ${network}") + validNetworks.add(network) + checkValidNetworks() + } + } + } + } + } + + /* + If the callback was registered with registerNetworkCallback() it will be called for each network which no longer satisfies the criteria of the callback. + Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network) + */ + override fun onLost(network: Network) { + Log.d(TAG, "onLost: ${network}") + validNetworks.remove(network) + checkValidNetworks() + } + + } + + /** + * Send a ping to googles primary DNS. + * If successful, that means we have internet. + */ + object DoesNetworkHaveInternet { + + // Make sure to execute this on a background thread. + fun execute(socketFactory: SocketFactory): Boolean { + return try{ + Log.d(TAG, "PINGING google.") + val socket = socketFactory.createSocket() ?: throw IOException("Socket is null.") + socket.connect(InetSocketAddress("8.8.8.8", 53), 1500) + socket.close() + Log.d(TAG, "PING success.") + true + }catch (e: IOException){ + Log.e(TAG, "No internet connection. $e") + false + } + } + } +} \ No newline at end of file diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/LiveDataExt.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/LiveDataExt.kt new file mode 100644 index 00000000..4bb8d52e --- /dev/null +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/LiveDataExt.kt @@ -0,0 +1,33 @@ +package com.shabinder.common.di + +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer + +@Composable +fun LiveData.observeAsState(): State = observeAsState(value) + +/** + * Starts observing this [LiveData] and represents its values via [State]. Every time there would + * be new value posted into the [LiveData] the returned [State] will be updated causing + * recomposition of every [State.value] usage. + * + * The inner observer will automatically be removed when this composable disposes or the current + * [LifecycleOwner] moves to the [Lifecycle.State.DESTROYED] state. + * + * @sample androidx.compose.runtime.livedata.samples.LiveDataWithInitialSample + */ +@Composable +fun LiveData.observeAsState(initial: R): State { + val lifecycleOwner = LocalLifecycleOwner.current + val state = remember { mutableStateOf(initial) } + DisposableEffect(this, lifecycleOwner) { + val observer = Observer { state.value = it } + observe(lifecycleOwner, observer) + onDispose { removeObserver(observer) } + } + return state +} \ No newline at end of file diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt index 225d5e18..8da66527 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt @@ -14,6 +14,7 @@ import io.ktor.client.features.logging.* import io.ktor.client.request.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import org.koin.core.context.startKoin import org.koin.dsl.KoinAppDeclaration @@ -46,20 +47,18 @@ val kotlinxSerializer = KotlinxSerializer( Json { /* * Refactor This * */ -fun isInternetAvailable(): Boolean { - var result = false - val job = GlobalScope.launch { +suspend fun isInternetAvailable(): Boolean { + return withContext(dispatcherIO) { try { ktorHttpClient.head("http://google.com") - result = true + true } catch (e: Exception) { println(e.message) - result = false + false } } - while (job.isActive){} - return result } + fun createHttpClient(enableNetworkLogs: Boolean = false,serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient { install(JsonFeature) { this.serializer = serializer diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/NetworkResponse.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/NetworkResponse.kt new file mode 100644 index 00000000..2d9836e7 --- /dev/null +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/NetworkResponse.kt @@ -0,0 +1,6 @@ +package com.shabinder.common.di + +sealed class NetworkResponse { + data class Success(val value:T):NetworkResponse() + data class Error(val message:String):NetworkResponse() +} \ 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 1be6ea19..945bd7cf 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 @@ -21,7 +21,7 @@ class TokenStore( db.add(token.access_token!!, token.expiry!! + Clock.System.now().epochSeconds) } - suspend fun getToken(): TokenData { + suspend fun getToken(): TokenData? { var token: TokenData? = db.select().executeAsOneOrNull()?.let { TokenData(it.accessToken,null,it.expiry) } @@ -29,7 +29,7 @@ class TokenStore( if(Clock.System.now().epochSeconds > token?.expiry ?:0 || token == null){ logger.d{"Requesting New Token"} token = authenticateSpotify() - GlobalScope.launch { token.access_token?.let { save(token) } } + GlobalScope.launch { token?.access_token?.let { save(token) } } } return token } 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 cd894776..b6b64b0d 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 @@ -18,10 +18,7 @@ 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.* import com.shabinder.common.di.spotify.SpotifyRequests import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.TrackDetails @@ -48,8 +45,17 @@ class SpotifyProvider( init { logger.d { "Creating Spotify Provider" } - GlobalScope.launch(Dispatchers.Default) { - val token = tokenStore.getToken() + GlobalScope.launch(Dispatchers.Default) {authenticateSpotify()} + } + + override suspend fun authenticateSpotify(): HttpClient?{ + val token = tokenStore.getToken() + return if(token == null) { + showPopUpMessage("Please Check your Network Connection") + null + } + else{ + logger.d { "Spotify Provider Created with $token" } httpClient = HttpClient { defaultRequest { header("Authorization","Bearer ${token.access_token}") @@ -58,7 +64,7 @@ class SpotifyProvider( serializer = kotlinxSerializer } } - logger.d { "Spotify Provider Created with $token" } + httpClient } } @@ -68,6 +74,11 @@ class SpotifyProvider( get() = database.downloadRecordDatabaseQueries suspend fun query(fullLink: String): PlatformQueryResult?{ + + if(!this::httpClient.isInitialized){ + authenticateSpotify() + } + var spotifyLink = "https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim() 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..202dcff6 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 @@ -1,5 +1,6 @@ package com.shabinder.common.di.spotify +import com.shabinder.common.di.isInternetAvailable import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.models.spotify.TokenData import io.ktor.client.* @@ -10,10 +11,10 @@ import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.http.* -suspend fun authenticateSpotify(): TokenData { - return spotifyAuthClient.post("https://accounts.spotify.com/api/token"){ +suspend fun authenticateSpotify(): TokenData? { + return if(isInternetAvailable()) spotifyAuthClient.post("https://accounts.spotify.com/api/token"){ body = FormDataContent(Parameters.build { append("grant_type","client_credentials") }) - } + } else null } private val spotifyAuthClient by lazy { diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt index a561303c..49c95c3a 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt @@ -13,6 +13,8 @@ interface SpotifyRequests { val httpClient:HttpClient + suspend fun authenticateSpotify():HttpClient? + suspend fun getPlaylist(playlistID: String): Playlist { return httpClient.get("$BASE_URL/playlists/$playlistID") } diff --git a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt index 531b3e86..185a8567 100644 --- a/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt +++ b/common/dependency-injection/src/desktopMain/kotlin/com/shabinder/common/di/DesktopActual.kt @@ -1,5 +1,9 @@ package com.shabinder.common.di +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.model.YoutubeVideo import com.github.kiulian.downloader.model.formats.Format @@ -22,6 +26,19 @@ actual fun giveDonation(){ //TODO } + +@Composable +actual fun AlertDialog( + onDismissRequest: () -> Unit, + buttons: @Composable () -> Unit, + modifier: Modifier, + title: (@Composable () -> Unit)?, + text: @Composable (() -> Unit)?, + shape: Shape, + backgroundColor: Color, + contentColor: Color, +){} + actual fun queryActiveTracks(){} val DownloadProgressFlow: MutableSharedFlow> = MutableSharedFlow(1) diff --git a/common/list/src/androidMain/AndroidManifest.xml b/common/list/src/androidMain/AndroidManifest.xml index 17d82132..56cf6a33 100644 --- a/common/list/src/androidMain/AndroidManifest.xml +++ b/common/list/src/androidMain/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/common/main/src/androidMain/AndroidManifest.xml b/common/main/src/androidMain/AndroidManifest.xml index 17d82132..49b84c42 100644 --- a/common/main/src/androidMain/AndroidManifest.xml +++ b/common/main/src/androidMain/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/common/root/src/androidMain/AndroidManifest.xml b/common/root/src/androidMain/AndroidManifest.xml index 17d82132..240f1584 100644 --- a/common/root/src/androidMain/AndroidManifest.xml +++ b/common/root/src/androidMain/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 885fe678..dbf113eb 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { implementation(project(":common:database")) implementation(project(":common:dependency-injection")) implementation(project(":common:compose")) + implementation(project(":common:root")) implementation(Decompose.decompose) implementation(Decompose.extensionsCompose) implementation(MVIKotlin.mvikotlin)