Android Network Connectivity

This commit is contained in:
shabinder 2021-02-26 05:29:54 +05:30
parent 81a0b9a85c
commit bf6463a3e1
26 changed files with 328 additions and 35 deletions

View File

@ -4,6 +4,9 @@ plugins {
id("com.android.application") id("com.android.application")
kotlin("android") kotlin("android")
id("org.jetbrains.compose") id("org.jetbrains.compose")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("com.google.firebase.firebase-perf")
} }
group = "com.shabinder" group = "com.shabinder"
@ -75,6 +78,12 @@ dependencies {
implementation(Decompose.decompose) implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose) 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 //Lifecycle
Versions.androidLifecycle.let{ Versions.androidLifecycle.let{
@ -85,6 +94,7 @@ dependencies {
} }
*/ */
Extras.Android.apply { Extras.Android.apply {
implementation(appUpdator) implementation(appUpdator)
implementation(razorpay) implementation(razorpay)

View File

@ -21,6 +21,8 @@
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keepattributes *Annotation*, InnerClasses -keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations -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 # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
-keepclassmembers class kotlinx.serialization.json.** { -keepclassmembers class kotlinx.serialization.json.** {
@ -29,11 +31,14 @@
-keepclasseswithmembers class kotlinx.serialization.json.** { -keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...); kotlinx.serialization.KSerializer serializer(...);
} }
-keep class com.shabinder.** { *; }
-keep,includedescriptorclasses class com.shabinder.spotiflyer.**$$serializer { *; } # <-- change package name to your app's -keep,includedescriptorclasses class com.shabinder.**$$serializer { *; } # <-- change package name to your app's
-keepclassmembers class com.shabinder.spotiflyer.** { # <-- change package name to your app's -keepclassmembers class com.shabinder.** { # <-- change package name to your app's
*** Companion; *** 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(...); kotlinx.serialization.KSerializer serializer(...);
} }
# Ktor
-keep class io.ktor.** { *; }
-keep class kotlinx.coroutines.** { *; }

View File

@ -12,6 +12,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

View File

@ -45,9 +45,7 @@ import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import com.shabinder.common.uikit.* import com.shabinder.common.uikit.*
import com.shabinder.database.Database import com.shabinder.database.Database
import com.shabinder.spotiflyer.utils.checkIfLatestVersion import com.shabinder.spotiflyer.utils.*
import com.shabinder.spotiflyer.utils.disableDozeMode
import com.shabinder.spotiflyer.utils.requestStoragePermission
import com.tonyodev.fetch2.Status import com.tonyodev.fetch2.Status
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -100,6 +98,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
if(askForPermission && !permissionGranted.value) permissionDialog() if(askForPermission && !permissionGranted.value) permissionDialog()
NetworkDialog()
root = SpotiFlyerRootContent(rememberRootComponent(::spotiFlyerRoot),statusBarHeight) root = SpotiFlyerRootContent(rememberRootComponent(::spotiFlyerRoot),statusBarHeight)
} }
} }

View File

@ -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<Boolean?> = 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)
}
}
}
}

View File

@ -16,6 +16,9 @@ repositories {
dependencies { dependencies {
implementation("com.android.tools.build:gradle:4.0.1") 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.Compose.gradlePlugin)
implementation(JetBrains.Kotlin.gradlePlugin) implementation(JetBrains.Kotlin.gradlePlugin)
implementation(JetBrains.Kotlin.serialization) implementation(JetBrains.Kotlin.serialization)

View File

@ -27,7 +27,7 @@ object Versions {
//Android //Android
const val versionCode = 15 const val versionCode = 15
const val minSdkVersion = 24 const val minSdkVersion = 24
const val compileSdkVersion = 30 const val compileSdkVersion = 29
const val targetSdkVersion = 29 const val targetSdkVersion = 29
const val androidLifecycle = "2.3.0" const val androidLifecycle = "2.3.0"
} }

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="com.shabinder.common.ui"/> <manifest package="com.shabinder.common.uikit"/>

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="com.shabinder.common.ui"/> <manifest package="com.shabinder.common.models"/>

View File

@ -1,3 +1,5 @@
import org.jetbrains.compose.compose
plugins { plugins {
id("multiplatform-compose-setup") id("multiplatform-compose-setup")
id("android-setup") id("android-setup")
@ -8,6 +10,7 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
implementation(compose.materialIconsExtended)
implementation(project(":common:data-models")) implementation(project(":common:data-models"))
implementation(project(":common:database")) implementation(project(":common:database"))
implementation(project(":fuzzywuzzy:app")) implementation(project(":fuzzywuzzy:app"))

View File

@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="com.shabinder.common.ui"/> <manifest package="com.shabinder.common.di" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View File

@ -4,8 +4,13 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import androidx.compose.material.MaterialTheme
import androidx.compose.material.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState 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 androidx.core.content.ContextCompat
import com.github.kiulian.downloader.model.YoutubeVideo import com.github.kiulian.downloader.model.YoutubeVideo
import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.formats.Format

View File

@ -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<Boolean?>{
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<Boolean>() {
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
private val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
private val validNetworks: MutableSet<Network> = 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
}
}
}
}

View File

@ -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 <T> LiveData<T>.observeAsState(): State<T?> = 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 <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
val lifecycleOwner = LocalLifecycleOwner.current
val state = remember { mutableStateOf(initial) }
DisposableEffect(this, lifecycleOwner) {
val observer = Observer<T> { state.value = it }
observe(lifecycleOwner, observer)
onDispose { removeObserver(observer) }
}
return state
}

View File

@ -14,6 +14,7 @@ import io.ktor.client.features.logging.*
import io.ktor.client.request.* import io.ktor.client.request.*
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.KoinAppDeclaration
@ -46,20 +47,18 @@ val kotlinxSerializer = KotlinxSerializer( Json {
/* /*
* Refactor This * Refactor This
* */ * */
fun isInternetAvailable(): Boolean { suspend fun isInternetAvailable(): Boolean {
var result = false return withContext(dispatcherIO) {
val job = GlobalScope.launch {
try { try {
ktorHttpClient.head<String>("http://google.com") ktorHttpClient.head<String>("http://google.com")
result = true true
} catch (e: Exception) { } catch (e: Exception) {
println(e.message) println(e.message)
result = false false
} }
} }
while (job.isActive){}
return result
} }
fun createHttpClient(enableNetworkLogs: Boolean = false,serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient { fun createHttpClient(enableNetworkLogs: Boolean = false,serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient {
install(JsonFeature) { install(JsonFeature) {
this.serializer = serializer this.serializer = serializer

View File

@ -0,0 +1,6 @@
package com.shabinder.common.di
sealed class NetworkResponse<out T> {
data class Success<T>(val value:T):NetworkResponse<T>()
data class Error(val message:String):NetworkResponse<Nothing>()
}

View File

@ -21,7 +21,7 @@ class TokenStore(
db.add(token.access_token!!, token.expiry!! + Clock.System.now().epochSeconds) 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 { var token: TokenData? = db.select().executeAsOneOrNull()?.let {
TokenData(it.accessToken,null,it.expiry) TokenData(it.accessToken,null,it.expiry)
} }
@ -29,7 +29,7 @@ class TokenStore(
if(Clock.System.now().epochSeconds > token?.expiry ?:0 || token == null){ if(Clock.System.now().epochSeconds > token?.expiry ?:0 || token == null){
logger.d{"Requesting New Token"} logger.d{"Requesting New Token"}
token = authenticateSpotify() token = authenticateSpotify()
GlobalScope.launch { token.access_token?.let { save(token) } } GlobalScope.launch { token?.access_token?.let { save(token) } }
} }
return token return token
} }

View File

@ -18,10 +18,7 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.DownloadRecordDatabaseQueries import com.shabinder.common.database.DownloadRecordDatabaseQueries
import com.shabinder.common.di.Dir import com.shabinder.common.di.*
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.SpotifyRequests
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
@ -48,8 +45,17 @@ class SpotifyProvider(
init { init {
logger.d { "Creating Spotify Provider" } logger.d { "Creating Spotify Provider" }
GlobalScope.launch(Dispatchers.Default) { GlobalScope.launch(Dispatchers.Default) {authenticateSpotify()}
}
override suspend fun authenticateSpotify(): HttpClient?{
val token = tokenStore.getToken() 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 { httpClient = HttpClient {
defaultRequest { defaultRequest {
header("Authorization","Bearer ${token.access_token}") header("Authorization","Bearer ${token.access_token}")
@ -58,7 +64,7 @@ class SpotifyProvider(
serializer = kotlinxSerializer serializer = kotlinxSerializer
} }
} }
logger.d { "Spotify Provider Created with $token" } httpClient
} }
} }
@ -68,6 +74,11 @@ class SpotifyProvider(
get() = database.downloadRecordDatabaseQueries get() = database.downloadRecordDatabaseQueries
suspend fun query(fullLink: String): PlatformQueryResult?{ suspend fun query(fullLink: String): PlatformQueryResult?{
if(!this::httpClient.isInitialized){
authenticateSpotify()
}
var spotifyLink = var spotifyLink =
"https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim() "https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim()

View File

@ -1,5 +1,6 @@
package com.shabinder.common.di.spotify package com.shabinder.common.di.spotify
import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.models.spotify.TokenData import com.shabinder.common.models.spotify.TokenData
import io.ktor.client.* import io.ktor.client.*
@ -10,10 +11,10 @@ import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.http.* import io.ktor.http.*
suspend fun authenticateSpotify(): TokenData { suspend fun authenticateSpotify(): TokenData? {
return 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") }) body = FormDataContent(Parameters.build { append("grant_type","client_credentials") })
} } else null
} }
private val spotifyAuthClient by lazy { private val spotifyAuthClient by lazy {

View File

@ -13,6 +13,8 @@ interface SpotifyRequests {
val httpClient:HttpClient val httpClient:HttpClient
suspend fun authenticateSpotify():HttpClient?
suspend fun getPlaylist(playlistID: String): Playlist { suspend fun getPlaylist(playlistID: String): Playlist {
return httpClient.get("$BASE_URL/playlists/$playlistID") return httpClient.get("$BASE_URL/playlists/$playlistID")
} }

View File

@ -1,5 +1,9 @@
package com.shabinder.common.di 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.YoutubeDownloader
import com.github.kiulian.downloader.model.YoutubeVideo import com.github.kiulian.downloader.model.YoutubeVideo
import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.formats.Format
@ -22,6 +26,19 @@ actual fun giveDonation(){
//TODO //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(){} actual fun queryActiveTracks(){}
val DownloadProgressFlow: MutableSharedFlow<HashMap<String,DownloadStatus>> = MutableSharedFlow(1) val DownloadProgressFlow: MutableSharedFlow<HashMap<String,DownloadStatus>> = MutableSharedFlow(1)

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="com.shabinder.common.ui"/> <manifest package="com.shabinder.common.list"/>

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="com.shabinder.common.ui"/> <manifest package="com.shabinder.common.main"/>

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="com.shabinder.common.ui"/> <manifest package="com.shabinder.common.root"/>

View File

@ -22,6 +22,7 @@ kotlin {
implementation(project(":common:database")) implementation(project(":common:database"))
implementation(project(":common:dependency-injection")) implementation(project(":common:dependency-injection"))
implementation(project(":common:compose")) implementation(project(":common:compose"))
implementation(project(":common:root"))
implementation(Decompose.decompose) implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose) implementation(Decompose.extensionsCompose)
implementation(MVIKotlin.mvikotlin) implementation(MVIKotlin.mvikotlin)