Code Changes for Native, IOS app builds, gradle scripts changes

This commit is contained in:
shabinder 2021-05-01 23:30:59 +05:30
parent a14181b968
commit 751ba3512e
55 changed files with 270 additions and 296 deletions

View File

@ -20,6 +20,7 @@ import org.jetbrains.compose.compose
plugins {
id("com.android.application")
kotlin("android")
id("kotlin-parcelize")
id("org.jetbrains.compose")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
@ -96,37 +97,42 @@ dependencies {
implementation(compose.materialIconsExtended)
implementation(Androidx.androidxActivity)
// Project's SubModules
implementation(project(":common:database"))
implementation(project(":common:compose"))
implementation(project(":common:root"))
implementation(project(":common:dependency-injection"))
implementation(project(":common:data-models"))
// Koin
implementation(Koin.android)
implementation(Koin.compose)
implementation("com.google.accompanist:accompanist-insets:0.8.1")
// DECOMPOSE
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
// MVI
implementation(MVIKotlin.mvikotlin)
implementation(MVIKotlin.mvikotlinMain)
implementation(MVIKotlin.mvikotlinLogging)
implementation(MVIKotlin.mvikotlinTimeTravel)
// Firebase
implementation(platform("com.google.firebase:firebase-bom:27.1.0"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-perf-ktx")
// Extras
Extras.Android.apply {
implementation(appUpdator)
implementation(razorpay)
}
implementation(MVIKotlin.mvikotlin)
implementation(MVIKotlin.mvikotlinMain)
implementation(MVIKotlin.mvikotlinLogging)
implementation(MVIKotlin.mvikotlinTimeTravel)
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
implementation("dev.icerock.moko:parcelize:0.6.1")
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
implementation("com.google.accompanist:accompanist-insets:0.8.1")
// Test
testImplementation("junit:junit:4.13.2")

View File

@ -378,7 +378,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
while(!this@MainActivity::root.isInitialized){
delay(100)
}
if(methods.isInternetAvailable)callBacks.searchLink(link)
if(methods.value.isInternetAvailable)callBacks.searchLink(link)
}
}
}

View File

@ -23,7 +23,6 @@ plugins {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
mavenLocal()
maven(url = "https://jitpack.io")

View File

@ -22,10 +22,9 @@ group = "com.shabinder"
version = "2.1"
repositories {
jcenter()
google()
mavenLocal()
mavenCentral()
google()
maven(url = "https://jitpack.io")
maven(url = "https://plugins.gradle.org/m2/")
maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers")

View File

@ -18,6 +18,7 @@ plugins {
id("com.android.library")
id("kotlin-multiplatform")
id("org.jetbrains.compose")
id("kotlin-parcelize")
id("ktlint-setup")
}
@ -35,10 +36,22 @@ kotlin {
sourceSets {
named("commonMain") {
dependencies {
// Decompose
implementation(Decompose.decompose)
// MVI
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt")
implementation(Extras.kermit)
implementation("dev.icerock.moko:parcelize:0.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") {
isForce = true
}
}
}

View File

@ -19,6 +19,7 @@ plugins {
id("kotlin-multiplatform")
id("org.jetbrains.compose")
id("ktlint-setup")
id("kotlin-parcelize")
}
kotlin {
@ -30,7 +31,7 @@ kotlin {
if (isiOSDevice) {
iosArm64("ios")
} else {
iosX64("ios")
iosX64("ios") {}
}
}
@ -56,7 +57,32 @@ kotlin {
sourceSets {
named("commonMain") {
dependencies {}
dependencies {
// Decompose
implementation(Decompose.decompose)
// MVI
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
// Koin
implementation(Koin.core)
implementation(Ktor.auth)
implementation(Ktor.clientJson)
implementation(Ktor.clientCore)
implementation(Ktor.clientLogging)
implementation(Ktor.clientSerialization)
// Extras
implementation(Extras.kermit)
implementation("co.touchlab:stately-common:1.1.6")
implementation("dev.icerock.moko:parcelize:0.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") {
isForce = true
}
}
}
named("androidMain") {
@ -67,8 +93,9 @@ kotlin {
implementation(compose.material)
implementation(compose.foundation)
implementation(compose.materialIconsExtended)
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
implementation(Ktor.clientAndroid)
implementation(Koin.android)
}
}
@ -79,12 +106,14 @@ kotlin {
implementation(compose.material)
implementation(compose.desktop.common)
implementation(compose.materialIconsExtended)
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
implementation(Ktor.clientApache)
implementation(Ktor.slf4j)
}
}
named("jsMain") {
dependencies {
implementation(Ktor.clientJs)
implementation("org.jetbrains:kotlin-react:17.0.1-pre.148-kotlin-1.4.30")
implementation("org.jetbrains:kotlin-styled:1.0.0-pre.115-kotlin-1.4.10")
implementation("org.jetbrains:kotlin-react-dom:17.0.1-pre.148-kotlin-1.4.30")
@ -92,7 +121,9 @@ kotlin {
}
if(HostOS.isMac){
named("iosMain"){
dependencies { }
dependencies {
implementation(Ktor.clientIos)
}
}
}
}

View File

@ -19,7 +19,6 @@ import org.jetbrains.compose.compose
plugins {
id("multiplatform-compose-setup")
id("android-setup")
id("kotlin-parcelize")
}
kotlin {
@ -33,8 +32,6 @@ kotlin {
implementation(project(":common:database"))
implementation(project(":common:data-models"))
implementation(project(":common:dependency-injection"))
// DECOMPOSE
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
}
}

View File

@ -50,7 +50,7 @@ actual fun ImageLoad(
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) {
withContext(methods.dispatcherIO) {
withContext(methods.value.dispatcherIO) {
pic = loader(link).image
}
}

View File

@ -36,7 +36,6 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -46,24 +45,24 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.arkivanov.decompose.extensions.compose.jetbrains.asState
import com.shabinder.common.di.Picture
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import kotlinx.coroutines.delay
@Composable
fun SpotiFlyerListContent(
component: SpotiFlyerList,
modifier: Modifier = Modifier
) {
val model by component.models.collectAsState(SpotiFlyerList.State())
val model by component.models.asState()
LaunchedEffect(model.errorOccurred) {
/*Handle if Any Exception Occurred*/
model.errorOccurred?.let {
methods.showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection")
methods.value.showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection")
component.onBackPressed()
}
}

View File

@ -58,7 +58,6 @@ import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Flag
import androidx.compose.material.icons.rounded.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -72,6 +71,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.arkivanov.decompose.extensions.compose.jetbrains.asState
import com.shabinder.common.di.Picture
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
@ -80,7 +80,7 @@ import com.shabinder.common.models.methods
@Composable
fun SpotiFlyerMainContent(component: SpotiFlyerMain) {
val model by component.models.collectAsState(SpotiFlyerMain.State())
val model by component.models.asState()
Column {
SearchPanel(
@ -193,7 +193,7 @@ fun SearchPanel(
OutlinedButton(
modifier = Modifier.padding(12.dp).wrapContentWidth(),
onClick = {
if (link.isBlank()) methods.showPopUpMessage("Enter A Link!")
if (link.isBlank()) methods.value.showPopUpMessage("Enter A Link!")
else {
// TODO if(!isOnline(ctx)) showPopUpMessage("Check Your Internet Connection") else
onSearch(link)
@ -235,7 +235,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Spotify",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.openPlatform("com.spotify.music", "http://open.spotify.com") }
onClick = { methods.value.openPlatform("com.spotify.music", "http://open.spotify.com") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
@ -244,7 +244,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Gaana",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.openPlatform("com.gaana", "http://gaana.com") }
onClick = { methods.value.openPlatform("com.gaana", "http://gaana.com") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
@ -253,7 +253,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Youtube",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.openPlatform("com.google.android.youtube", "http://m.youtube.com") }
onClick = { methods.value.openPlatform("com.google.android.youtube", "http://m.youtube.com") }
)
)
Spacer(modifier = modifier.padding(start = 12.dp))
@ -262,7 +262,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Youtube Music",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
onClick = { methods.value.openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
)
)
}
@ -283,7 +283,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable(
onClick = { methods.openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }
onClick = { methods.value.openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }
)
.padding(vertical = 6.dp)
) {
@ -302,7 +302,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
}
Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { methods.openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }),
.clickable(onClick = { methods.value.openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Flag, "Help Translate", Modifier.size(32.dp))
@ -320,7 +320,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
}
Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { methods.giveDonation() }),
.clickable(onClick = { methods.value.giveDonation() }),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.CardGiftcard, "Support Developer")
@ -340,7 +340,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(
onClick = {
methods.shareApp()
methods.value.shareApp()
}
),
verticalAlignment = Alignment.CenterVertically

View File

@ -48,7 +48,7 @@ actual fun ImageLoad(
) {
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) {
withContext(methods.dispatcherIO) {
withContext(methods.value.dispatcherIO) {
pic = loader(link).image
}
}

View File

@ -24,16 +24,7 @@ plugins {
kotlin {
sourceSets {
commonMain {
dependencies {
api("dev.icerock.moko:parcelize:0.6.1")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt")
}
}
if(HostOS.isMac){
val iosMain by getting {
}
dependencies {}
}
}
}

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
actual class NativeAtomicReference<T> actual constructor(actual var value: T)

View File

@ -1,11 +1,13 @@
package com.shabinder.common.models
import co.touchlab.stately.freeze
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
/*
* Holder to call platform actions from anywhere
* */
lateinit var methods: Actions
var methods: NativeAtomicReference<Actions> = NativeAtomicReference(stubActions().freeze())
/*
* Interface Having All Platform Dependent Functions
@ -45,3 +47,17 @@ interface Actions {
// Current Platform Info
val currentPlatform: AllPlatforms
}
private fun stubActions() = object :Actions{
override val platformActions = object: PlatformActions{}
override fun showPopUpMessage(string: String, long: Boolean) {}
override fun setDownloadDirectoryAction() {}
override fun queryActiveTracks() {}
override fun giveDonation() {}
override fun shareApp() {}
override fun openPlatform(packageID: String, platformLink: String) {}
override val dispatcherIO: CoroutineDispatcher = Dispatchers.Default
override val isInternetAvailable: Boolean = true
override val currentPlatform: AllPlatforms = AllPlatforms.Jvm
}

View File

@ -0,0 +1,6 @@
package com.shabinder.common.models
expect class NativeAtomicReference<T>(value: T) {
var value: T
// fun compareAndSet(expected: T, new: T): Boolean
}

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
actual class NativeAtomicReference<T> actual constructor(actual var value: T)

View File

@ -0,0 +1,7 @@
package com.shabinder.common.models
import kotlin.native.concurrent.AtomicReference
actual interface PlatformActions
actual typealias NativeAtomicReference<T> = AtomicReference<T>

View File

@ -1,4 +0,0 @@
package com.shabinder.common.models
actual interface PlatformActions {
}

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
actual class NativeAtomicReference<T> actual constructor(actual var value: T)

View File

@ -31,13 +31,13 @@ kotlin {
commonMain {
dependencies {
implementation(project(":common:data-models"))
// SQL Delight
implementation(SqlDelight.runtime)
implementation(SqlDelight.coroutineExtensions)
api(Extras.kermit)
// koin
api(Koin.core)
api(Koin.test)
implementation(Koin.test)
}
}
@ -53,6 +53,7 @@ kotlin {
implementation(SqlDelight.jdbcDriver)
}
}
if(HostOS.isMac) {
val iosMain by getting {
dependencies {

View File

@ -20,75 +20,43 @@ plugins {
id("multiplatform-setup")
id("android-setup")
kotlin("plugin.serialization")
kotlin("native.cocoapods")
}
version = "1.0"
kotlin {
cocoapods {
// Configure fields required by CocoaPods.
summary = "SpotiFlyer-DI Native Module"
homepage = "https://github.com/Shabinder/SpotiFlyer"
authors = "Shabinder Singh"
// You can change the name of the produced framework.
// By default, it is the name of the Gradle project.
frameworkName = "SpotiFlyer-DI"
ios.deploymentTarget = "9.0"
// Dependencies
pod("TagLibIOS") {
version = "~> 0.3"
}
//podfile = project.file("spotiflyer-ios/Podfile")
}
sourceSets {
commonMain {
dependencies {
implementation(project(":common:data-models"))
implementation(project(":common:database"))
implementation("org.jetbrains.kotlinx:atomicfu:0.16.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.0")
implementation("com.shabinder.fuzzywuzzy:fuzzywuzzy:1.0")
api(Ktor.clientCore)
api(Ktor.clientSerialization)
api(Ktor.clientLogging)
api(Ktor.clientJson)
api(Ktor.auth)
api(Extras.youtubeDownloader)
api(Extras.kermit)
implementation(Extras.youtubeDownloader)
implementation(MVIKotlin.rx)
}
}
androidMain {
dependencies {
implementation(compose.materialIconsExtended)
api(Koin.android)
api(Ktor.clientAndroid)
api(Extras.Android.razorpay)
api(Extras.mp3agic)
api(Extras.jaudioTagger)
api("com.github.shabinder:storage-chooser:2.0.4.45")
// api(files("$rootDir/libs/mobile-ffmpeg.aar"))
implementation(Extras.Android.razorpay)
implementation(Extras.mp3agic)
//implementation(Extras.jaudioTagger)
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
// implementation(files("$rootDir/libs/mobile-ffmpeg.aar"))
}
}
desktopMain {
dependencies {
implementation(compose.materialIconsExtended)
api(Ktor.clientApache)
api(Ktor.slf4j)
api(Extras.mp3agic)
api(Extras.jaudioTagger)
implementation(Extras.mp3agic)
//implementation(Extras.jaudioTagger)
}
}
jsMain {
dependencies {
implementation(npm("browser-id3-writer", "4.4.0"))
implementation(npm("file-saver", "2.0.4"))
implementation(project(":common:data-models"))
api(Ktor.clientJs)
}
}
}

View File

@ -1,46 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'dependency_injection'
spec.version = '1.0'
spec.homepage = 'https://github.com/Shabinder/SpotiFlyer'
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = 'Shabinder Singh'
spec.license = ''
spec.summary = 'SpotiFlyer-DI Native Module'
spec.static_framework = true
spec.vendored_frameworks = "build/cocoapods/framework/SpotiFlyer-DI.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.ios.deployment_target = '9.0'
spec.dependency 'TagLibIOS', '~> 0.3'
spec.pod_target_xcconfig = {
'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm',
'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x64',
'KOTLIN_TARGET[sdk=watchos*]' => 'watchos_arm',
'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
}
spec.script_phases = [
{
:name => 'Build dependency_injection',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../../gradlew" -p "$REPO_ROOT" :common:dependency-injection:syncFramework \
-Pkotlin.native.cocoapods.target=$KOTLIN_TARGET \
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION \
-Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \
-Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \
-Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS"
SCRIPT
}
]
end

View File

@ -25,6 +25,6 @@ actual suspend fun downloadTracks(
dir: Dir
) {
if (!list.isNullOrEmpty()) {
methods.platformActions.sendTracksToService(ArrayList(list))
methods.value.platformActions.sendTracksToService(ArrayList(list))
}
}

View File

@ -46,7 +46,7 @@ actual class Dir actual constructor(
const val DirKey = "downloadDir"
}
private val sharedPreferences:SharedPreferences by lazy { methods.platformActions.sharedPreferences }
private val sharedPreferences:SharedPreferences by lazy { methods.value.platformActions.sharedPreferences }
fun setDownloadDirectory(newBasePath:String){
sharedPreferences.edit().putString(DirKey,newBasePath).apply()
@ -57,7 +57,7 @@ actual class Dir actual constructor(
actual fun fileSeparator(): String = File.separator
actual fun imageCacheDir(): String = methods.platformActions.imageCacheDir
actual fun imageCacheDir(): String = methods.value.platformActions.imageCacheDir
// fun call in order to always access Updated Value
actual fun defaultDir(): String = sharedPreferences.getString(DirKey,defaultBaseDir)!! + File.separator +
@ -84,7 +84,8 @@ actual class Dir actual constructor(
@Suppress("BlockingMethodInNonBlockingContext")
actual suspend fun saveFileWithMetadata(
mp3ByteArray: ByteArray,
trackDetails: TrackDetails
trackDetails: TrackDetails,
postProcess:(track: TrackDetails)->Unit
) {
withContext(Dispatchers.IO){
val songFile = File(trackDetails.outputFilePath)
@ -152,7 +153,7 @@ actual class Dir actual constructor(
}
}
actual fun addToLibrary(path: String) = methods.platformActions.addToLibrary(path)
actual fun addToLibrary(path: String) = methods.value.platformActions.addToLibrary(path)
actual suspend fun loadImage(url: String): Picture = withContext(Dispatchers.IO){
val cachePath = imageCacheDir() + getNameURL(url)

View File

@ -236,7 +236,7 @@ class ForegroundService : Service(), CoroutineScope {
is DownloadResult.Success -> {
try {
// Save File and Embed Metadata
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) }
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track){} }
allTracksStatus[track.title] = DownloadStatus.Converting
sendTrackBroadcast("Converting", track)
addToNotification("Processing ${track.title}")
@ -293,7 +293,7 @@ class ForegroundService : Service(), CoroutineScope {
// Checking if the received broadcast is for our enqueued download by matching download id
if (downloadID == id) {
allTracksStatus[track.title] = DownloadStatus.Converting
launch { dir.saveFileWithMetadata(byteArrayOf(), track); converted++ }
launch { dir.saveFileWithMetadata(byteArrayOf(), track){}; converted++ }
// Unregister this broadcast Receiver
this@ForegroundService.unregisterReceiver(this)
}

View File

@ -42,6 +42,9 @@ fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclarat
modules(commonModule(enableNetworkLogs = enableNetworkLogs), databaseModule())
}
// Called by IOS
fun initKoin() = initKoin(enableNetworkLogs = false) { }
fun commonModule(enableNetworkLogs: Boolean) = module {
single { createHttpClient(enableNetworkLogs = enableNetworkLogs) }
single { Dir(get(), get()) }

View File

@ -43,7 +43,7 @@ expect class Dir (
suspend fun cacheImage(image: Any, path: String) // in Android = ImageBitmap, Desktop = BufferedImage
suspend fun loadImage(url: String): Picture
suspend fun clearCache()
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails)
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails,postProcess:(track: TrackDetails)->Unit = {})
fun addToLibrary(path: String)
}

View File

@ -19,6 +19,7 @@ package com.shabinder.common.di
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
expect suspend fun downloadTracks(
@ -28,7 +29,7 @@ expect suspend fun downloadTracks(
)
suspend fun isInternetAccessible(): Boolean {
return withContext(methods.dispatcherIO) {
return withContext(methods.value.dispatcherIO) {
try {
ktorHttpClient.head<String>("http://google.com")
true
@ -38,3 +39,5 @@ suspend fun isInternetAccessible(): Boolean {
}
}
}
internal val dispatcherDefault = Dispatchers.Default

View File

@ -27,7 +27,7 @@ import com.shabinder.common.models.methods
import io.ktor.client.HttpClient
import io.ktor.client.request.get
val corsApi get() = if (methods.currentPlatform is AllPlatforms.Js) {
val corsApi get() = if (methods.value.currentPlatform is AllPlatforms.Js) {
corsProxy.url
} // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/"
else ""

View File

@ -38,6 +38,7 @@ import io.ktor.client.request.header
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.native.concurrent.ThreadLocal
class SpotifyProvider(
private val tokenStore: TokenStore,
@ -48,7 +49,7 @@ class SpotifyProvider(
/* init {
logger.d { "Creating Spotify Provider" }
GlobalScope.launch(Dispatchers.Default) {
if (methods.currentPlatform is AllPlatforms.Js) {
if (methods.value.currentPlatform is AllPlatforms.Js) {
authenticateSpotifyClient(override = true)
} else authenticateSpotifyClient()
}

View File

@ -31,7 +31,7 @@ class YoutubeMp3(
suspend fun getMp3DownloadLink(videoID: String): String? = try {
getLinkFromYt1sMp3(videoID)?.let {
logger.i { "Download Link: $it" }
if (methods.currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/)
if (methods.value.currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/)
"https://kind-grasshopper-73.telebit.io/cors/$it"
// "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue
else it

View File

@ -29,7 +29,7 @@ import io.ktor.http.Parameters
suspend fun authenticateSpotify(): TokenData? {
return try {
if (methods.isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
if (methods.value.isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") })
} else null
}catch (e:Exception) {

View File

@ -17,7 +17,7 @@
package com.shabinder.common.di.utils
// Dependencies:
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9-native-mt")
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
@ -37,7 +37,7 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
class ParallelExecutor(
parentContext: CoroutineContext = methods.dispatcherIO,
parentContext: CoroutineContext = methods.value.dispatcherIO,
) : Closeable {
private val concurrentOperationLimit = atomic(4)

View File

@ -0,0 +1,23 @@
package com.shabinder.common.di.utils
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.ValueObserver
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.rx.Disposable
fun <T : Any> Store<*, T, *>.asValue(): Value<T> =
object : Value<T>() {
override val value: T get() = state
private var disposables = emptyMap<ValueObserver<T>, Disposable>()
override fun subscribe(observer: ValueObserver<T>) {
val disposable = states(com.arkivanov.mvikotlin.rx.observer(onNext = observer))
this.disposables += observer to disposable
}
override fun unsubscribe(observer: ValueObserver<T>) {
val disposable = disposables[observer] ?: return
this.disposables -= observer
disposable.dispose()
}
}

View File

@ -61,7 +61,7 @@ private val ytDownloader = YoutubeDownloader()
suspend fun downloadTrack(
videoID: String,
trackDetails: TrackDetails,
saveFileWithMetaData: suspend (mp3ByteArray: ByteArray, trackDetails: TrackDetails) -> Unit
saveFileWithMetaData: suspend (mp3ByteArray: ByteArray, trackDetails: TrackDetails,postProcess:(TrackDetails)->Unit) -> Unit
) {
try {
val audioData = ytDownloader.getVideo(videoID).getData()
@ -85,7 +85,7 @@ suspend fun downloadTrack(
)
}
is DownloadResult.Success -> { // Todo clear map
saveFileWithMetaData(it.byteArray, trackDetails)
saveFileWithMetaData(it.byteArray, trackDetails){}
DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0

View File

@ -85,7 +85,8 @@ actual class Dir actual constructor(
@Suppress("BlockingMethodInNonBlockingContext")
actual suspend fun saveFileWithMetadata(
mp3ByteArray: ByteArray,
trackDetails: TrackDetails
trackDetails: TrackDetails,
postProcess:(track: TrackDetails)->Unit
) {
val file = File(trackDetails.outputFilePath)
file.writeBytes(mp3ByteArray)

View File

@ -7,6 +7,5 @@ actual suspend fun downloadTracks(
fetcher: FetchPlatformQueryResult,
dir: Dir
) {
// TODO
}

View File

@ -0,0 +1,17 @@
package com.shabinder.common.di
import com.shabinder.common.models.DownloadStatus
import kotlinx.coroutines.flow.MutableSharedFlow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
/*
* Dependency Provider for IOS
* */
object IOSDeps: KoinComponent {
val dir: Dir by inject() // = get()
val fetchPlatformQueryResult: FetchPlatformQueryResult by inject() // get()
val database get() = dir.db
val sharedFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
val defaultDispatcher = dispatcherDefault
}

View File

@ -1,7 +1,6 @@
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import cocoapods.TagLibIOS.TLAudio
import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database
@ -27,7 +26,7 @@ actual class Dir actual constructor(
) {
init {
createDirectories()
//createDirectories()
}
actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path)
@ -135,16 +134,18 @@ actual class Dir actual constructor(
actual suspend fun saveFileWithMetadata(
mp3ByteArray: ByteArray,
trackDetails: TrackDetails
trackDetails: TrackDetails,
postProcess:(track: TrackDetails)->Unit
) {
when (trackDetails.outputFilePath.substringAfterLast('.')) {
".mp3" -> {
val file = TLAudio(trackDetails.outputFilePath)
postProcess(trackDetails)
/*val file = TLAudio(trackDetails.outputFilePath)
file.addTagsAndSave(
trackDetails,
this::loadCachedImage,
this::addToLibrary
)
)*/
}
}
}

View File

@ -1,4 +1,5 @@
package com.shabinder.common.di
/*
import cocoapods.TagLibIOS.TLAudio
import com.shabinder.common.models.TrackDetails
@ -34,4 +35,4 @@ suspend fun TLAudio.addTagsAndSave(
}
} catch (e: Exception){ e.printStackTrace() }
}
}
}*/

View File

@ -35,7 +35,7 @@ actual suspend fun downloadTracks(
dir: Dir
) {
list.forEach {
withContext(methods.dispatcherIO) {
withContext(methods.value.dispatcherIO) {
allTracksStatus[it.title] = DownloadStatus.Queued
if (!it.videoID.isNullOrBlank()) { // Video ID already known!
downloadTrack(it.videoID!!, it, fetcher, dir)
@ -66,7 +66,7 @@ suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher: FetchPl
when (it) {
is DownloadResult.Success -> {
println("Download Completed")
dir.saveFileWithMetadata(it.byteArray, track)
dir.saveFileWithMetadata(it.byteArray, track){}
}
is DownloadResult.Error -> {
allTracksStatus[track.title] = DownloadStatus.Failed

View File

@ -62,7 +62,8 @@ actual class Dir actual constructor(
@Suppress("BlockingMethodInNonBlockingContext")
actual suspend fun saveFileWithMetadata(
mp3ByteArray: ByteArray,
trackDetails: TrackDetails
trackDetails: TrackDetails,
postProcess:(track: TrackDetails)->Unit
) {
val writer = ID3Writer(mp3ByteArray.toArrayBuffer())
val albumArt = downloadFile(corsApi + trackDetails.albumArtURL)

View File

@ -28,9 +28,6 @@ kotlin {
implementation(project(":common:data-models"))
implementation(project(":common:database"))
implementation(SqlDelight.coroutineExtensions)
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(Decompose.decompose)
}
}
}

View File

@ -17,6 +17,7 @@
package com.shabinder.common.list
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
@ -26,12 +27,11 @@ import com.shabinder.common.models.Consumer
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
interface SpotiFlyerList {
val models: Flow<State>
val models: Value<State>
/*
* Download All Tracks(after filtering already Downloaded)
@ -66,9 +66,11 @@ interface SpotiFlyerList {
val listOutput: Consumer<Output>
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
}
sealed class Output {
object Finished : Output()
}
data class State(
val queryResult: PlatformQueryResult? = null,
val link: String = "",

View File

@ -17,8 +17,9 @@
package com.shabinder.common.list.integration
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Picture
import com.shabinder.common.di.utils.asValue
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.Dependencies
import com.shabinder.common.list.SpotiFlyerList.State
@ -26,7 +27,6 @@ import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.list.store.SpotiFlyerListStoreProvider
import com.shabinder.common.list.store.getStore
import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.flow.Flow
internal class SpotiFlyerListImpl(
componentContext: ComponentContext,
@ -44,7 +44,7 @@ internal class SpotiFlyerListImpl(
).provide()
}
override val models: Flow<State> = store.states
override val models: Value<State> = store.asValue()
override fun onDownloadAllClicked(trackList: List<TrackDetails>) {
store.accept(Intent.StartDownloadAll(trackList))

View File

@ -92,7 +92,7 @@ internal class SpotiFlyerListStoreProvider(
is Intent.StartDownloadAll -> {
val finalList =
intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
if (finalList.isNullOrEmpty()) methods.showPopUpMessage("All Songs are Processed")
if (finalList.isNullOrEmpty()) methods.value.showPopUpMessage("All Songs are Processed")
else downloadTracks(finalList, fetchQuery, dir)
val list = intent.trackList.map {
@ -106,7 +106,7 @@ internal class SpotiFlyerListStoreProvider(
downloadTracks(listOf(intent.track), fetchQuery, dir)
dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued)))
}
is Intent.RefreshTracksStatuses -> methods.queryActiveTracks()
is Intent.RefreshTracksStatuses -> methods.value.queryActiveTracks()
}
}
}

View File

@ -28,9 +28,6 @@ kotlin {
implementation(project(":common:data-models"))
implementation(project(":common:database"))
implementation(SqlDelight.coroutineExtensions)
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(Decompose.decompose)
}
}
}

View File

@ -17,6 +17,7 @@
package com.shabinder.common.main
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir
import com.shabinder.common.di.Picture
@ -28,7 +29,7 @@ import kotlinx.coroutines.flow.Flow
interface SpotiFlyerMain {
val models: Flow<State>
val models: Value<State>
/*
* We Intend to Move to List Screen
@ -67,6 +68,7 @@ interface SpotiFlyerMain {
val link: String = "",
val selectedCategory: HomeCategory = HomeCategory.About
)
enum class HomeCategory {
About, History
}

View File

@ -17,8 +17,9 @@
package com.shabinder.common.main.integration
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Picture
import com.shabinder.common.di.utils.asValue
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.Dependencies
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
@ -28,7 +29,6 @@ import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore
import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.Flow
internal class SpotiFlyerMainImpl(
componentContext: ComponentContext,
@ -43,11 +43,11 @@ internal class SpotiFlyerMainImpl(
).provide()
}
override val models: Flow<State> = store.states
override val models: Value<State> = store.asValue()
override fun onLinkSearch(link: String) {
if (methods.isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else methods.showPopUpMessage("Check Network Connection Please")
if (methods.value.isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else methods.value.showPopUpMessage("Check Network Connection Please")
}
override fun onInputLinkChanged(link: String) {

View File

@ -78,9 +78,9 @@ internal class SpotiFlyerMainStoreProvider(
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {
is Intent.OpenPlatform -> methods.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> methods.giveDonation()
is Intent.ShareApp -> methods.shareApp()
is Intent.OpenPlatform -> methods.value.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> methods.value.giveDonation()
is Intent.ShareApp -> methods.value.shareApp()
is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link))
is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category))
}

View File

@ -18,11 +18,22 @@ plugins {
id("multiplatform-setup")
id("android-setup")
id("kotlin-parcelize")
//kotlin("native.cocoapods")
}
// Required be cocoapods
version = "1.0"
fun org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer.generateFramework() {
framework {
baseName = "SpotiFlyer"
linkerOpts.add("-lsqlite3")
export(project(":common:dependency-injection"))
export(project(":common:data-models"))
export(project(":common:database"))
export(project(":common:main"))
export(project(":common:list"))
export(Decompose.decompose)
export(MVIKotlin.mvikotlinMain)
export(MVIKotlin.mvikotlinLogging)
}
}
kotlin {
@ -33,53 +44,17 @@ kotlin {
if (isiOSDevice) {
iosArm64("ios"){
binaries {
framework {
baseName = "SpotiFlyer"
linkerOpts.add("-lsqlite3")
export(project(":common:database"))
export(project(":common:main"))
export(project(":common:list"))
export(project(":common:dependency-injection"))
export(project(":common:data-models"))
export(Decompose.decompose)
export(MVIKotlin.mvikotlin)
}
generateFramework()
}
}
} else {
iosX64("ios"){
binaries {
framework {
baseName = "SpotiFlyer"
linkerOpts.add("-lsqlite3")
export(project(":common:database"))
export(project(":common:main"))
export(project(":common:list"))
export(project(":common:dependency-injection"))
export(project(":common:data-models"))
export(Decompose.decompose)
export(MVIKotlin.mvikotlin)
generateFramework()
}
}
}
}
}
/*cocoapods {
// Configure fields required by CocoaPods.
summary = "SpotiFlyer Native Module"
homepage = "https://github.com/Shabinder/SpotiFlyer"
authors = "Shabinder Singh"
// You can change the name of the produced framework.
// By default, it is the name of the Gradle project.
frameworkName = "SpotiFlyer"
ios.deploymentTarget = "11.0"
*//*pod("dependency_injection"){
version = "1.0"
source = path(rootProject.file("common/dependency-injection"))
}*//*
}*/
sourceSets {
commonMain {
@ -90,12 +65,11 @@ kotlin {
implementation(project(":common:list"))
implementation(project(":common:main"))
implementation(SqlDelight.coroutineExtensions)
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(Decompose.decompose)
}
}
}
/*Required to Export `packForXcode`*/
sourceSets {
named("iosMain") {
dependencies {
@ -105,7 +79,8 @@ kotlin {
api(project(":common:list"))
api(project(":common:main"))
api(Decompose.decompose)
api(MVIKotlin.mvikotlin)
api(MVIKotlin.mvikotlinMain)
api(MVIKotlin.mvikotlinLogging)
}
}
}
@ -115,7 +90,8 @@ val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val targetName = "ios"
val framework = kotlin.targets.getByName<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>(targetName).binaries.getFramework(mode)
val framework = kotlin.targets.getByName<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>(targetName)
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")

View File

@ -1,46 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'root'
spec.version = '1.0'
spec.homepage = 'https://github.com/Shabinder/SpotiFlyer'
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = 'Shabinder Singh'
spec.license = ''
spec.summary = 'SpotiFlyer Native Module'
spec.static_framework = true
spec.vendored_frameworks = "build/cocoapods/framework/SpotiFlyer.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.ios.deployment_target = '11.0'
spec.pod_target_xcconfig = {
'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64',
'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm',
'KOTLIN_TARGET[sdk=watchsimulator*]' => 'watchos_x64',
'KOTLIN_TARGET[sdk=watchos*]' => 'watchos_arm',
'KOTLIN_TARGET[sdk=appletvsimulator*]' => 'tvos_x64',
'KOTLIN_TARGET[sdk=appletvos*]' => 'tvos_arm64',
'KOTLIN_TARGET[sdk=macosx*]' => 'macos_x64'
}
spec.script_phases = [
{
:name => 'Build root',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../../gradlew" -p "$REPO_ROOT" :common:root:syncFramework \
-Pkotlin.native.cocoapods.target=$KOTLIN_TARGET \
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION \
-Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \
-Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \
-Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS"
SCRIPT
}
]
end

View File

@ -16,6 +16,7 @@
package com.shabinder.common.root.integration
import co.touchlab.stately.freeze
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.RouterState
import com.arkivanov.decompose.pop
@ -36,6 +37,7 @@ import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Child
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -45,12 +47,12 @@ internal class SpotiFlyerRootImpl(
) : SpotiFlyerRoot, ComponentContext by componentContext, Dependencies by dependencies, Actions by dependencies.actions {
init {
methods = actions
GlobalScope.launch {
/*Authenticate Spotify Client*/
if (methods.currentPlatform is AllPlatforms.Js) {
methods.value = actions.freeze()
GlobalScope.launch(Dispatchers.Default) {
//*Authenticate Spotify Client*//*
/*if (methods.value.currentPlatform is AllPlatforms.Js) {
fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient(override = true)
} else fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient()
} else fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient()*/
}
}

View File

@ -41,5 +41,6 @@ kotlin.native.disableCompilerDaemon=true
kotlin.mpp.stability.nowarn=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
kotlin.native.cacheKind=none
xcodeproj=./spotiflyer-ios
#kotlin.native.cacheKind=none
#org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

@ -1 +1 @@
Subproject commit d7df9dda6df716aff3d78dda0d522b57b5a2c0c6
Subproject commit 31517f90ef04efa4aea88c61ca627647c146f471