diff --git a/.gitignore b/.gitignore index e03f02aa..285a7086 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,5 @@ /common/list/build/ /common/main/build/ /common/root/build/ +/android/release/ +/android/google-services.json diff --git a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index 74f880c9..ba2a3fa0 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -13,6 +13,8 @@ import android.os.PowerManager import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -53,6 +55,7 @@ import org.koin.android.ext.android.inject const val disableDozeCode = 1223 +@ExperimentalAnimationApi class MainActivity : ComponentActivity(), PaymentResultListener { private val database: Database by inject() @@ -76,30 +79,22 @@ class MainActivity : ComponentActivity(), PaymentResultListener { Surface(contentColor = colorOffWhite) { var statusBarHeight by remember { mutableStateOf(27.dp) } - var askForPermission by remember { mutableStateOf(false) } permissionGranted = remember { mutableStateOf(true) } val view = LocalView.current LaunchedEffect(view){ - permissionGranted.value = (ContextCompat - .checkSelfPermission(this@MainActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED - && - ContextCompat.checkSelfPermission(this@MainActivity, - Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED) - + permissionGranted.value = checkPermissions() view.setOnApplyWindowInsetsListener { _, insets -> statusBarHeight = insets.systemWindowInsetTop.dp insets } - delay(2000) - askForPermission = true } - if(askForPermission && !permissionGranted.value) permissionDialog() + root = SpotiFlyerRootContent(rememberRootComponent(::spotiFlyerRoot),statusBarHeight) + NetworkDialog() - root = SpotiFlyerRootContent(rememberRootComponent(::spotiFlyerRoot),statusBarHeight) + PermissionDialog() } } } @@ -112,6 +107,11 @@ class MainActivity : ComponentActivity(), PaymentResultListener { Checkout.preload(applicationContext) } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + permissionGranted.value = checkPermissions() + } + private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot = SpotiFlyerRoot( componentContext, @@ -247,64 +247,65 @@ class MainActivity : ComponentActivity(), PaymentResultListener { } @Composable - private fun permissionDialog(){ - AlertDialog( - onDismissRequest = {}, - buttons = { - TextButton({ - requestStoragePermission() - disableDozeMode(disableDozeCode) - permissionGranted.value = (ContextCompat - .checkSelfPermission(this@MainActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED - && - ContextCompat.checkSelfPermission(this@MainActivity,Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED) - },Modifier.padding(bottom = 16.dp,start = 16.dp,end = 16.dp).fillMaxWidth().background(colorPrimary,shape = SpotiFlyerShapes.medium).padding(horizontal = 8.dp), - ){ - Text("Grant Permissions",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) - } - },title = {Text("Required Permissions:",style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center)}, - backgroundColor = Color.DarkGray, - text = { - Column{ - Spacer(modifier = Modifier.padding(8.dp)) - Row(verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp) - ) { - Icon(Icons.Rounded.SdStorage,"Storage Permission.") - Spacer(modifier = Modifier.padding(start = 16.dp)) - Column { - Text( - text = "Storage Permission.", - style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold) - ) - Text( - text = "To download your favourite songs to this device.", - style = SpotiFlyerTypography.subtitle2, - ) - } + private fun PermissionDialog(){ + var askForPermission by remember { mutableStateOf(false) } + LaunchedEffect(Unit){ + delay(2000) + askForPermission = true + } + AnimatedVisibility(askForPermission && !permissionGranted.value){ + AlertDialog( + onDismissRequest = {}, + buttons = { + TextButton({ + requestStoragePermission() + disableDozeMode(disableDozeCode) + },Modifier.padding(bottom = 16.dp,start = 16.dp,end = 16.dp).fillMaxWidth().background(colorPrimary,shape = SpotiFlyerShapes.medium).padding(horizontal = 8.dp), + ){ + Text("Grant Permissions",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) } - Row( - modifier = Modifier.fillMaxWidth().padding(top = 6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(Icons.Rounded.SystemSecurityUpdate,"Allow Background Running") - Spacer(modifier = Modifier.padding(start = 16.dp)) - Column { - Text( - text = "Background Running.", - style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold) - ) - Text( - text = "To download all songs in background without any System Interruptions", - style = SpotiFlyerTypography.subtitle2, - ) + },title = {Text("Required Permissions:",style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center)}, + backgroundColor = Color.DarkGray, + text = { + Column{ + Spacer(modifier = Modifier.padding(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp) + ) { + Icon(Icons.Rounded.SdStorage,"Storage Permission.") + Spacer(modifier = Modifier.padding(start = 16.dp)) + Column { + Text( + text = "Storage Permission.", + style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold) + ) + Text( + text = "To download your favourite songs to this device.", + style = SpotiFlyerTypography.subtitle2, + ) + } + } + Row( + modifier = Modifier.fillMaxWidth().padding(top = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(Icons.Rounded.SystemSecurityUpdate,"Allow Background Running") + Spacer(modifier = Modifier.padding(start = 16.dp)) + Column { + Text( + text = "Background Running.", + style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold) + ) + Text( + text = "To download all songs in background without any System Interruptions", + style = SpotiFlyerTypography.subtitle2, + ) + } } } } - } - - ) + ) + } } init { diff --git a/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt b/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt index f76c81c3..65e594fb 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/utils/NetworkDialog.kt @@ -1,6 +1,7 @@ package com.shabinder.spotiflyer.utils -import androidx.compose.animation.Crossfade +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.material.AlertDialog @@ -14,13 +15,13 @@ 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 +@ExperimentalAnimationApi @Composable fun NetworkDialog( networkAvailability: State = isInternetAvailableState() @@ -31,43 +32,41 @@ fun NetworkDialog( 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 - ) - } - } + + AnimatedVisibility(networkAvailability.value == false && visible){ + 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") } - ,shape = SpotiFlyerShapes.medium) + */}, + 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/android/src/main/java/com/shabinder/spotiflyer/utils/UtilFunctions.kt b/android/src/main/java/com/shabinder/spotiflyer/utils/UtilFunctions.kt index 3a5555ae..b8afeb12 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/utils/UtilFunctions.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/utils/UtilFunctions.kt @@ -5,10 +5,12 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.PowerManager import android.provider.Settings +import androidx.core.content.ContextCompat import com.github.javiersantos.appupdater.AppUpdater import com.github.javiersantos.appupdater.enums.Display import com.github.javiersantos.appupdater.enums.UpdateFrom @@ -23,6 +25,17 @@ fun Activity.checkIfLatestVersion() { start() } } + +fun Activity.checkPermissions():Boolean{ + return (ContextCompat + .checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED + && + ContextCompat.checkSelfPermission(this, + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED) + +} + @SuppressLint("BatteryLife", "ObsoleteSdkInt") fun Activity.disableDozeMode(requestCode:Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 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 5b3b736d..3ad0c174 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,19 +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 import com.github.kiulian.downloader.model.quality.AudioQuality import com.razorpay.Checkout -import com.shabinder.common.database.R import com.shabinder.common.database.activityContext import com.shabinder.common.database.appContext import com.shabinder.common.di.worker.ForegroundService @@ -49,6 +43,9 @@ actual fun Toast( //We Have Android's Implementation of Toast so its just Empty } +actual val isInternetAvailable:Boolean + get() = internetAvailability.value ?: true + actual fun showPopUpMessage(text: String){ android.widget.Toast.makeText(appContext,text, android.widget.Toast.LENGTH_SHORT).show() } 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 index 68a9f98e..adc5c494 100644 --- 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 @@ -22,11 +22,11 @@ import javax.net.SocketFactory const val TAG = "C-Manager" -val isInternetAvailable by lazy { ConnectionLiveData(appContext) } +val internetAvailability by lazy { ConnectionLiveData(appContext) } @Composable fun isInternetAvailableState(): State{ - return isInternetAvailable.observeAsState() + return internetAvailability.observeAsState() } /** 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 8da66527..d7754bb2 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 @@ -44,20 +44,6 @@ val kotlinxSerializer = KotlinxSerializer( Json { isLenient = true ignoreUnknownKeys = true }) -/* -* Refactor This -* */ -suspend fun isInternetAvailable(): Boolean { - return withContext(dispatcherIO) { - try { - ktorHttpClient.head("http://google.com") - true - } catch (e: Exception) { - println(e.message) - false - } - } -} fun createHttpClient(enableNetworkLogs: Boolean = false,serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient { install(JsonFeature) { diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt index 32d9b755..4679adae 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt @@ -13,6 +13,8 @@ expect fun showPopUpMessage(text: String) expect val dispatcherIO: CoroutineDispatcher +expect val isInternetAvailable:Boolean + expect suspend fun downloadTracks( list: List, getYTIDBestMatch:suspend (String,TrackDetails)->String?, 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 202dcff6..0be59b8e 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 @@ -12,7 +12,7 @@ import io.ktor.client.request.forms.* import io.ktor.http.* suspend fun authenticateSpotify(): TokenData? { - return if(isInternetAvailable()) spotifyAuthClient.post("https://accounts.spotify.com/api/token"){ + return if(isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token"){ body = FormDataContent(Parameters.build { append("grant_type","client_credentials") }) } else null } 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 185a8567..0b8f1f8d 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 @@ -11,8 +11,12 @@ import com.github.kiulian.downloader.model.quality.AudioQuality import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails +import io.ktor.client.request.* +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext actual fun openPlatform(packageID:String, platformLink:String){ //TODO @@ -26,21 +30,31 @@ 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(){} +/* +* Refactor This +* */ +private suspend fun isInternetAvailable(): Boolean { + return withContext(dispatcherIO) { + try { + ktorHttpClient.head("http://google.com") + true + } catch (e: Exception) { + println(e.message) + false + } + } +} + +actual val isInternetAvailable:Boolean + get(){ + var result = false + val job = GlobalScope.launch { result = isInternetAvailable() } + while(job.isActive){} + return result + } + val DownloadProgressFlow: MutableSharedFlow> = MutableSharedFlow(1) actual suspend fun downloadTracks( diff --git a/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt b/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt index e56e1f1d..e961f1fb 100644 --- a/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt +++ b/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt @@ -3,6 +3,8 @@ package com.shabinder.common.main.integration import androidx.compose.ui.graphics.ImageBitmap import com.arkivanov.decompose.ComponentContext import com.arkivanov.mvikotlin.extensions.coroutines.states +import com.shabinder.common.di.isInternetAvailable +import com.shabinder.common.di.showPopUpMessage import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain.* import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent @@ -26,7 +28,8 @@ internal class SpotiFlyerMainImpl( override val models: Flow = store.states override fun onLinkSearch(link: String) { - mainOutput.callback(Output.Search(link = link)) + if(isInternetAvailable) mainOutput.callback(Output.Search(link = link)) + else showPopUpMessage("Check Network Connection Please") } override fun onInputLinkChanged(link: String) {