From 02d137588f96740bf3e1338a355eca0150adbbb4 Mon Sep 17 00:00:00 2001 From: shabinder Date: Sun, 2 May 2021 22:24:40 +0530 Subject: [PATCH] Instance Keeper Freeze fix .IOS List Screen and Shared Code changes. --- .../com/shabinder/spotiflyer/MainActivity.kt | 2 - .../shabinder/common/uikit/AndroidImages.kt | 3 +- .../shabinder/common/uikit/DesktopImages.kt | 3 +- .../AndroidPlatformActions.kt | 12 +- .../com/shabinder/common/models/Actions.kt | 16 +-- .../common/models/PlatformActions.kt | 4 +- .../common/models/DesktopPlatformActions.kt | 5 +- .../IOSPlatformActions.kt | 3 +- .../JSPlatformActions.kt | 4 +- .../com/shabinder/common/di/AndroidActual.kt | 9 ++ .../com/shabinder/common/di/AndroidDir.kt | 133 +++++++++--------- .../kotlin/com/shabinder/common/di/Expect.kt | 14 +- .../common/di/gaana/GaanaRequests.kt | 3 +- .../common/di/providers/SpotifyProvider.kt | 2 +- .../common/di/providers/YoutubeMp3.kt | 3 +- .../common/di/utils/ParallelExecutor.kt | 3 +- .../com/shabinder/common/di/DesktopActual.kt | 8 ++ .../com.shabinder.common.di/IOSActual.kt | 8 ++ .../kotlin/com.shabinder.common.di/IOSDir.kt | 21 ++- .../com/shabinder/common/di/WebActual.kt | 11 +- .../list/integration/SpotiFlyerListImpl.kt | 5 + .../main/integration/SpotiFlyerMainImpl.kt | 5 + .../common/main/store/InstanceKeeperExt.kt | 1 - .../root/integration/SpotiFlyerRootImpl.kt | 109 ++++++++------ spotiflyer-ios | 2 +- 25 files changed, 236 insertions(+), 153 deletions(-) diff --git a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index b6a91186..8403029c 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -265,8 +265,6 @@ class MainActivity : ComponentActivity(), PaymentResultListener { } override val isInternetAvailable get() = internetAvailability.value ?: true - override val dispatcherIO = Dispatchers.IO - override val currentPlatform = AllPlatforms.Jvm } } ) diff --git a/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt b/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt index 7edd5679..05f9dcdf 100644 --- a/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt +++ b/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import com.shabinder.common.database.R import com.shabinder.common.di.Picture +import com.shabinder.common.di.dispatcherIO import com.shabinder.common.models.methods import kotlinx.coroutines.withContext @@ -50,7 +51,7 @@ actual fun ImageLoad( var pic by remember(link) { mutableStateOf(null) } LaunchedEffect(link) { - withContext(methods.value.dispatcherIO) { + withContext(dispatcherIO) { pic = loader(link).image } } diff --git a/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt b/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt index ae6484eb..22c8e5c5 100644 --- a/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt +++ b/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.platform.Font import com.shabinder.common.di.Picture +import com.shabinder.common.di.dispatcherIO import com.shabinder.common.models.methods import kotlinx.coroutines.withContext @@ -48,7 +49,7 @@ actual fun ImageLoad( ) { var pic by remember(link) { mutableStateOf(null) } LaunchedEffect(link) { - withContext(methods.value.dispatcherIO) { + withContext(dispatcherIO) { pic = loader(link).image } } diff --git a/common/data-models/src/androidMain/kotlin/com.shabinder.common.models/AndroidPlatformActions.kt b/common/data-models/src/androidMain/kotlin/com.shabinder.common.models/AndroidPlatformActions.kt index a6b583e9..21131944 100644 --- a/common/data-models/src/androidMain/kotlin/com.shabinder.common.models/AndroidPlatformActions.kt +++ b/common/data-models/src/androidMain/kotlin/com.shabinder.common.models/AndroidPlatformActions.kt @@ -10,9 +10,19 @@ actual interface PlatformActions { val imageCacheDir: String - val sharedPreferences: SharedPreferences + val sharedPreferences: SharedPreferences? fun addToLibrary(path: String) fun sendTracksToService(array: ArrayList) +} + +actual val StubPlatformActions = object: PlatformActions { + override val imageCacheDir: String = "" + + override val sharedPreferences: SharedPreferences? = null + + override fun addToLibrary(path: String) {} + + override fun sendTracksToService(array: ArrayList) {} } \ No newline at end of file diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt index d0e584d5..68b8a516 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt @@ -1,8 +1,6 @@ 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 @@ -17,6 +15,9 @@ interface Actions { // Platform Specific Actions val platformActions: PlatformActions + // Platform Specific Implementation Preferred + val isInternetAvailable: Boolean + // Show Toast fun showPopUpMessage(string: String, long: Boolean = false) @@ -37,15 +38,6 @@ interface Actions { // Open / Redirect to another Platform fun openPlatform(packageID: String, platformLink: String) - - // IO-Dispatcher - val dispatcherIO: CoroutineDispatcher - - // Internet Connectivity Check - val isInternetAvailable: Boolean - - // Current Platform Info - val currentPlatform: AllPlatforms } @@ -57,7 +49,5 @@ private fun stubActions() = object :Actions{ 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 } \ No newline at end of file diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/PlatformActions.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/PlatformActions.kt index 8d17e74f..4bde8127 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/PlatformActions.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/PlatformActions.kt @@ -1,3 +1,5 @@ package com.shabinder.common.models -expect interface PlatformActions \ No newline at end of file +expect interface PlatformActions + +expect val StubPlatformActions : PlatformActions \ No newline at end of file diff --git a/common/data-models/src/desktopMain/kotlin/com/shabinder/common/models/DesktopPlatformActions.kt b/common/data-models/src/desktopMain/kotlin/com/shabinder/common/models/DesktopPlatformActions.kt index 02c6ce65..a7695dfb 100644 --- a/common/data-models/src/desktopMain/kotlin/com/shabinder/common/models/DesktopPlatformActions.kt +++ b/common/data-models/src/desktopMain/kotlin/com/shabinder/common/models/DesktopPlatformActions.kt @@ -1,4 +1,5 @@ package com.shabinder.common.models -actual interface PlatformActions { -} \ No newline at end of file +actual interface PlatformActions {} + +actual val StubPlatformActions = object: PlatformActions {} diff --git a/common/data-models/src/iosMain/kotlin/com.shabinder.common.models/IOSPlatformActions.kt b/common/data-models/src/iosMain/kotlin/com.shabinder.common.models/IOSPlatformActions.kt index fa3b53af..3b621244 100644 --- a/common/data-models/src/iosMain/kotlin/com.shabinder.common.models/IOSPlatformActions.kt +++ b/common/data-models/src/iosMain/kotlin/com.shabinder.common.models/IOSPlatformActions.kt @@ -2,6 +2,7 @@ package com.shabinder.common.models import kotlin.native.concurrent.AtomicReference -actual interface PlatformActions +actual interface PlatformActions {} +actual val StubPlatformActions = object: PlatformActions {} actual typealias NativeAtomicReference = AtomicReference \ No newline at end of file diff --git a/common/data-models/src/jsMain/kotlin/com.shabinder.common.models/JSPlatformActions.kt b/common/data-models/src/jsMain/kotlin/com.shabinder.common.models/JSPlatformActions.kt index 02c6ce65..8220804a 100644 --- a/common/data-models/src/jsMain/kotlin/com.shabinder.common.models/JSPlatformActions.kt +++ b/common/data-models/src/jsMain/kotlin/com.shabinder.common.models/JSPlatformActions.kt @@ -1,4 +1,4 @@ package com.shabinder.common.models -actual interface PlatformActions { -} \ No newline at end of file +actual interface PlatformActions {} +actual val StubPlatformActions = object: PlatformActions {} \ No newline at end of file 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 708e521e..dbfae1b1 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 @@ -16,8 +16,17 @@ package com.shabinder.common.di +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.methods +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +// IO-Dispatcher +actual val dispatcherIO: CoroutineDispatcher = Dispatchers.IO + +// Current Platform Info +actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm actual suspend fun downloadTracks( list: List, diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt index b67c82b5..1f214cf3 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt @@ -46,7 +46,8 @@ actual class Dir actual constructor( const val DirKey = "downloadDir" } - private val sharedPreferences:SharedPreferences by lazy { methods.value.platformActions.sharedPreferences } + // This Wont throw `NPE` as We will never pass null + private val sharedPreferences:SharedPreferences by lazy { methods.value.platformActions.sharedPreferences!! } fun setDownloadDirectory(newBasePath:String){ sharedPreferences.edit().putString(DirKey,newBasePath).apply() @@ -77,7 +78,7 @@ actual class Dir actual constructor( } } - actual suspend fun clearCache() { + actual suspend fun clearCache(): Unit = withContext(dispatcherIO) { File(imageCacheDir()).deleteRecursively() } @@ -86,76 +87,74 @@ actual class Dir actual constructor( mp3ByteArray: ByteArray, trackDetails: TrackDetails, postProcess:(track: TrackDetails)->Unit - ) { - withContext(Dispatchers.IO){ - val songFile = File(trackDetails.outputFilePath) - try { - /* - * Check , if Fetch was Used, File is saved Already, else write byteArray we Received - * */ - if(!songFile.exists()) { - /*Make intermediate Dirs if they don't exist yet*/ - songFile.parentFile.mkdirs() + ) = withContext(dispatcherIO) { + val songFile = File(trackDetails.outputFilePath) + try { + /* + * Check , if Fetch was Used, File is saved Already, else write byteArray we Received + * */ + if(!songFile.exists()) { + /*Make intermediate Dirs if they don't exist yet*/ + songFile.parentFile.mkdirs() + } + + if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray) + + when (trackDetails.outputFilePath.substringAfterLast('.')) { + ".mp3" -> { + Mp3File(File(songFile.absolutePath)) + .removeAllTags() + .setId3v1Tags(trackDetails) + .setId3v2TagsAndSaveFile(trackDetails) + addToLibrary(songFile.absolutePath) } - - if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray) - - when (trackDetails.outputFilePath.substringAfterLast('.')) { - ".mp3" -> { + ".m4a" -> { + /*FFmpeg.executeAsync( + "-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}" + ){ _, returnCode -> + when (returnCode) { + Config.RETURN_CODE_SUCCESS -> { + //FFMPEG task Completed + logger.d{ "Async command execution completed successfully." } + scope.launch { + Mp3File(File(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3")) + .removeAllTags() + .setId3v1Tags(trackDetails) + .setId3v2TagsAndSaveFile(trackDetails) + addToLibrary(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3") + } + } + Config.RETURN_CODE_CANCEL -> { + logger.d{"Async command execution cancelled by user."} + } + else -> { + logger.d { "Async command execution failed with rc=$returnCode" } + } + } + }*/ + } + else -> { + try { Mp3File(File(songFile.absolutePath)) .removeAllTags() .setId3v1Tags(trackDetails) .setId3v2TagsAndSaveFile(trackDetails) addToLibrary(songFile.absolutePath) - } - ".m4a" -> { - /*FFmpeg.executeAsync( - "-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}" - ){ _, returnCode -> - when (returnCode) { - Config.RETURN_CODE_SUCCESS -> { - //FFMPEG task Completed - logger.d{ "Async command execution completed successfully." } - scope.launch { - Mp3File(File(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3")) - .removeAllTags() - .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails) - addToLibrary(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3") - } - } - Config.RETURN_CODE_CANCEL -> { - logger.d{"Async command execution cancelled by user."} - } - else -> { - logger.d { "Async command execution failed with rc=$returnCode" } - } - } - }*/ - } - else -> { - try { - Mp3File(File(songFile.absolutePath)) - .removeAllTags() - .setId3v1Tags(trackDetails) - .setId3v2TagsAndSaveFile(trackDetails) - addToLibrary(songFile.absolutePath) - } catch (e: Exception) { e.printStackTrace() } - } + } catch (e: Exception) { e.printStackTrace() } } - }catch (e:Exception){ - withContext(Dispatchers.Main){ - //Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() - } - if(songFile.exists()) songFile.delete() - logger.e { "${songFile.absolutePath} could not be created" } } + }catch (e:Exception){ + withContext(Dispatchers.Main){ + //Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() + } + if(songFile.exists()) songFile.delete() + logger.e { "${songFile.absolutePath} could not be created" } } } actual fun addToLibrary(path: String) = methods.value.platformActions.addToLibrary(path) - actual suspend fun loadImage(url: String): Picture = withContext(Dispatchers.IO){ + actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO){ val cachePath = imageCacheDir() + getNameURL(url) Picture(image = (loadCachedImage(cachePath) ?: freshImage(url))?.asImageBitmap()) } @@ -169,19 +168,17 @@ actual class Dir actual constructor( } } - actual suspend fun cacheImage(image: Any, path: String) { - withContext(Dispatchers.IO){ - try { - FileOutputStream(path).use { out -> - (image as? Bitmap)?.compress(Bitmap.CompressFormat.JPEG, 100, out) - } - } catch (e: IOException) { - e.printStackTrace() + actual suspend fun cacheImage(image: Any, path: String):Unit = withContext(dispatcherIO) { + try { + FileOutputStream(path).use { out -> + (image as? Bitmap)?.compress(Bitmap.CompressFormat.JPEG, 100, out) } + } catch (e: IOException) { + e.printStackTrace() } } - private suspend fun freshImage(url: String): Bitmap? = withContext(Dispatchers.IO) { + private suspend fun freshImage(url: String): Bitmap? = withContext(dispatcherIO) { try { val source = URL(url) val connection: HttpURLConnection = source.openConnection() as HttpURLConnection 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 94d72fb0..4d2a6628 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 @@ -16,11 +16,14 @@ package com.shabinder.common.di +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.methods import io.ktor.client.request.* +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlin.native.concurrent.SharedImmutable expect suspend fun downloadTracks( list: List, @@ -28,8 +31,17 @@ expect suspend fun downloadTracks( dir: Dir ) + +// IO-Dispatcher +@SharedImmutable +expect val dispatcherIO: CoroutineDispatcher + +// Current Platform Info +@SharedImmutable +expect val currentPlatform: AllPlatforms + suspend fun isInternetAccessible(): Boolean { - return withContext(methods.value.dispatcherIO) { + return withContext(dispatcherIO) { try { ktorHttpClient.head("http://google.com") true diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt index 14d7bffb..e84ea778 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt @@ -16,6 +16,7 @@ package com.shabinder.common.di.gaana +import com.shabinder.common.di.currentPlatform import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.corsProxy import com.shabinder.common.models.gaana.GaanaAlbum @@ -27,7 +28,7 @@ import com.shabinder.common.models.methods import io.ktor.client.HttpClient import io.ktor.client.request.get -val corsApi get() = if (methods.value.currentPlatform is AllPlatforms.Js) { +val corsApi get() = if (currentPlatform is AllPlatforms.Js) { corsProxy.url } // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/" else "" 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 a5a053be..7a2f3797 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 @@ -46,7 +46,7 @@ class SpotifyProvider( /* init { logger.d { "Creating Spotify Provider" } GlobalScope.launch(Dispatchers.Default) { - if (methods.value.currentPlatform is AllPlatforms.Js) { + if (currentPlatform is AllPlatforms.Js) { authenticateSpotifyClient(override = true) } else authenticateSpotifyClient() } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt index f2e8f1ec..33083b9e 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMp3.kt @@ -18,6 +18,7 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Kermit import com.shabinder.common.di.Dir +import com.shabinder.common.di.currentPlatform import com.shabinder.common.di.youtubeMp3.Yt1sMp3 import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.methods @@ -31,7 +32,7 @@ class YoutubeMp3( suspend fun getMp3DownloadLink(videoID: String): String? = try { getLinkFromYt1sMp3(videoID)?.let { logger.i { "Download Link: $it" } - if (methods.value.currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/) + if (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 diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt index b6fbdab2..b43673ce 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/ParallelExecutor.kt @@ -21,6 +21,7 @@ package com.shabinder.common.di.utils // implementation("org.jetbrains.kotlinx:atomicfu:0.14.4") // Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e +import com.shabinder.common.di.dispatcherIO import com.shabinder.common.models.methods import io.ktor.utils.io.core.Closeable import kotlinx.atomicfu.atomic @@ -37,7 +38,7 @@ import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext class ParallelExecutor( - parentContext: CoroutineContext = methods.value.dispatcherIO, + parentContext: CoroutineContext = dispatcherIO, ) : Closeable { private val concurrentOperationLimit = atomic(4) 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 c829d0cd..3ab77fab 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 @@ -17,10 +17,12 @@ package com.shabinder.common.di import com.shabinder.common.di.utils.ParallelExecutor +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails import com.shabinder.downloader.YoutubeDownloader +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect @@ -30,6 +32,12 @@ val DownloadProgressFlow: MutableSharedFlow> = M // Scope Allowing 4 Parallel Downloads val DownloadScope = ParallelExecutor(Dispatchers.IO) +// IO-Dispatcher +actual val dispatcherIO: CoroutineDispatcher = Dispatchers.IO + +// Current Platform Info +actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm + actual suspend fun downloadTracks( list: List, fetcher: FetchPlatformQueryResult, diff --git a/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSActual.kt b/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSActual.kt index 489bd785..39782f98 100644 --- a/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSActual.kt +++ b/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSActual.kt @@ -1,6 +1,14 @@ package com.shabinder.common.di +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.TrackDetails +import kotlinx.coroutines.Dispatchers + +@SharedImmutable +actual val dispatcherIO = Dispatchers.Default + +@SharedImmutable +actual val currentPlatform: AllPlatforms = AllPlatforms.Native actual suspend fun downloadTracks( list: List, diff --git a/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt b/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt index 6de2c90d..04ea6488 100644 --- a/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt +++ b/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt @@ -6,6 +6,7 @@ import com.shabinder.common.models.TrackDetails import com.shabinder.database.Database import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import platform.Foundation.NSCachesDirectory import platform.Foundation.NSDirectoryEnumerationSkipsHiddenFiles import platform.Foundation.NSFileManager @@ -25,10 +26,6 @@ actual class Dir actual constructor( private val spotiFlyerDatabase: SpotiFlyerDatabase, ) { - init { - //createDirectories() - } - actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path) actual fun fileSeparator(): String = "/" @@ -37,6 +34,7 @@ actual class Dir actual constructor( actual fun defaultDir(): String = defaultDirURL.path!! val defaultDirURL: NSURL by lazy { + createDirectories() val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!! musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!! } @@ -44,6 +42,7 @@ actual class Dir actual constructor( actual fun imageCacheDir(): String = imageCacheURL.path!! val imageCacheURL: NSURL by lazy { + createDirectories() val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null) cacheDir?.URLByAppendingPathComponent("SpotiFlyer",true)!! } @@ -64,7 +63,7 @@ actual class Dir actual constructor( } } - actual suspend fun cacheImage(image: Any, path: String) { + actual suspend fun cacheImage(image: Any, path: String): Unit = withContext(dispatcherIO){ try { (image as? UIImage)?.let { // We Will Be Using JPEG as default format everywhere @@ -76,8 +75,8 @@ actual class Dir actual constructor( } } - actual suspend fun loadImage(url: String): Picture { - return try { + actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO){ + try { val cachePath = imageCacheURL.URLByAppendingPathComponent(getNameURL(url)) Picture(image = cachePath?.path?.let { loadCachedImage(it) } ?: loadFreshImage(url)) } catch (e: Exception) { @@ -95,8 +94,8 @@ actual class Dir actual constructor( } } - private suspend fun loadFreshImage(url: String):UIImage? { - return try { + private suspend fun loadFreshImage(url: String):UIImage? = withContext(dispatcherIO){ + try { val nsURL = NSURL(string = url) val data = NSURLConnection.sendSynchronousRequest(NSURLRequest.requestWithURL(nsURL),null,null) if (data != null) { @@ -112,7 +111,7 @@ actual class Dir actual constructor( } } - actual suspend fun clearCache() { + actual suspend fun clearCache(): Unit = withContext(dispatcherIO) { try { val fileManager = NSFileManager.defaultManager val paths = fileManager.contentsOfDirectoryAtURL(imageCacheURL, @@ -136,7 +135,7 @@ actual class Dir actual constructor( mp3ByteArray: ByteArray, trackDetails: TrackDetails, postProcess:(track: TrackDetails)->Unit - ) { + ) : Unit = withContext(dispatcherIO) { when (trackDetails.outputFilePath.substringAfterLast('.')) { ".mp3" -> { postProcess(trackDetails) diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt index c9516d70..54392397 100644 --- a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt @@ -16,10 +16,13 @@ package com.shabinder.common.di +import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.methods +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.withContext @@ -29,13 +32,19 @@ val DownloadProgressFlow: MutableSharedFlow> = M // val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel val allTracksStatus: HashMap = hashMapOf() +// IO-Dispatcher +actual val dispatcherIO: CoroutineDispatcher = Dispatchers.Default + +// Current Platform Info +actual val currentPlatform: AllPlatforms = AllPlatforms.Js + actual suspend fun downloadTracks( list: List, fetcher: FetchPlatformQueryResult, dir: Dir ) { list.forEach { - withContext(methods.value.dispatcherIO) { + withContext(dispatcherIO) { allTracksStatus[it.title] = DownloadStatus.Queued if (!it.videoID.isNullOrBlank()) { // Video ID already known! downloadTrack(it.videoID!!, it, fetcher, dir) diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt index 21a6f600..fc7dab53 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/integration/SpotiFlyerListImpl.kt @@ -16,6 +16,7 @@ package com.shabinder.common.list.integration +import co.touchlab.stately.ensureNeverFrozen import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.Value import com.shabinder.common.di.Picture @@ -33,6 +34,10 @@ internal class SpotiFlyerListImpl( dependencies: Dependencies ) : SpotiFlyerList, ComponentContext by componentContext, Dependencies by dependencies { + init { + instanceKeeper.ensureNeverFrozen() + } + private val store = instanceKeeper.getStore { SpotiFlyerListStoreProvider( 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 4ca09dc7..ce417a6c 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 @@ -16,6 +16,7 @@ package com.shabinder.common.main.integration +import co.touchlab.stately.ensureNeverFrozen import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.Value import com.shabinder.common.di.Picture @@ -35,6 +36,10 @@ internal class SpotiFlyerMainImpl( dependencies: Dependencies ) : SpotiFlyerMain, ComponentContext by componentContext, Dependencies by dependencies { + init { + instanceKeeper.ensureNeverFrozen() + } + private val store = instanceKeeper.getStore { SpotiFlyerMainStoreProvider( diff --git a/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/InstanceKeeperExt.kt b/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/InstanceKeeperExt.kt index 7ac77488..d514a43c 100644 --- a/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/InstanceKeeperExt.kt +++ b/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/InstanceKeeperExt.kt @@ -25,7 +25,6 @@ fun > InstanceKeeper.getStore(key: Any, factory: () -> T): T .store inline fun > InstanceKeeper.getStore(noinline factory: () -> T): T = getStore(T::class, factory) diff --git a/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt b/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt index af92eeae..0bde9ada 100644 --- a/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt +++ b/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt @@ -16,6 +16,7 @@ package com.shabinder.common.root.integration +import co.touchlab.stately.ensureNeverFrozen import co.touchlab.stately.freeze import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.RouterState @@ -26,9 +27,9 @@ import com.arkivanov.decompose.router import com.arkivanov.decompose.statekeeper.Parcelable import com.arkivanov.decompose.statekeeper.Parcelize import com.arkivanov.decompose.value.Value -import com.shabinder.common.database.getLogger import com.shabinder.common.di.Dir -import com.shabinder.common.di.createDirectories +import com.shabinder.common.di.currentPlatform +import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.models.Actions @@ -45,23 +46,40 @@ import kotlinx.coroutines.launch internal class SpotiFlyerRootImpl( componentContext: ComponentContext, - dependencies: Dependencies, -) : SpotiFlyerRoot, ComponentContext by componentContext, Dependencies by dependencies, Actions by dependencies.actions { + private val main: (ComponentContext, output:Consumer)->SpotiFlyerMain, + private val list: (ComponentContext, link:String, output:Consumer)->SpotiFlyerList, + private val actions: Actions +) : SpotiFlyerRoot, ComponentContext by componentContext { - init { - methods.value = actions.freeze() - GlobalScope.launch { - /*TESTING*/ - getLogger().apply { - d("Hey...","Background Thread") - //d(directories.defaultDir(),"Background Thread") - d("Hey...","Background Thread") - } - //*Authenticate Spotify Client*//* - /*fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient( - override = true //methods.value.currentPlatform is AllPlatforms.Js - )*/ - } + constructor( + componentContext: ComponentContext, + dependencies: Dependencies, + ):this( + componentContext = componentContext, + main = { childContext,output -> + spotiFlyerMain( + childContext, + output, + dependencies + ) + }, + list = { childContext, link, output -> + spotiFlyerList( + childContext, + link, + output, + dependencies + ) + }, + actions = dependencies.actions.freeze() + ) { + instanceKeeper.ensureNeverFrozen() + methods.value = dependencies.actions.freeze() + /*Authenticate Spotify Client*/ + authenticateSpotify( + dependencies.fetchPlatformQueryResult.spotifyProvider, + currentPlatform is AllPlatforms.Js + ) } private val router = @@ -80,36 +98,15 @@ internal class SpotiFlyerRootImpl( it !is Configuration.Main } } - override fun setDownloadDirectory() { setDownloadDirectoryAction() } + override fun setDownloadDirectory() { actions.setDownloadDirectoryAction() } } private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child = when (configuration) { - is Configuration.Main -> Child.Main(spotiFlyerMain(componentContext)) - is Configuration.List -> Child.List(spotiFlyerList(componentContext, link = configuration.link)) + is Configuration.Main -> Child.Main(main(componentContext, Consumer(::onMainOutput))) + is Configuration.List -> Child.List(list(componentContext, configuration.link, Consumer(::onListOutput))) } - private fun spotiFlyerMain(componentContext: ComponentContext): SpotiFlyerMain = - SpotiFlyerMain( - componentContext = componentContext, - dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by this { - override val mainOutput: Consumer = Consumer(::onMainOutput) - override val dir: Dir = directories - } - ) - - private fun spotiFlyerList(componentContext: ComponentContext, link: String): SpotiFlyerList = - SpotiFlyerList( - componentContext = componentContext, - dependencies = object : SpotiFlyerList.Dependencies, Dependencies by this { - override val fetchQuery = fetchPlatformQueryResult - override val dir: Dir = directories - override val link: String = link - override val listOutput: Consumer = Consumer(::onListOutput) - override val downloadProgressFlow = downloadProgressReport - } - ) - private fun onMainOutput(output: SpotiFlyerMain.Output) = when (output) { is SpotiFlyerMain.Output.Search -> router.push(Configuration.List(link = output.link)) @@ -120,6 +117,13 @@ internal class SpotiFlyerRootImpl( is SpotiFlyerList.Output.Finished -> router.pop() } + private fun authenticateSpotify(spotifyProvider: SpotifyProvider, override:Boolean){ + GlobalScope.launch(Dispatchers.Default) { + /*Authenticate Spotify Client*/ + spotifyProvider.authenticateSpotifyClient(override) + } + } + private sealed class Configuration : Parcelable { @Parcelize object Main : Configuration() @@ -128,3 +132,24 @@ internal class SpotiFlyerRootImpl( data class List(val link: String) : Configuration() } } + +private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer ,dependencies: Dependencies): SpotiFlyerMain = + SpotiFlyerMain( + componentContext = componentContext, + dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies { + override val mainOutput: Consumer = output + override val dir: Dir = directories + } + ) + +private fun spotiFlyerList(componentContext: ComponentContext, link: String, output: Consumer, dependencies: Dependencies): SpotiFlyerList = + SpotiFlyerList( + componentContext = componentContext, + dependencies = object : SpotiFlyerList.Dependencies, Dependencies by dependencies { + override val fetchQuery = fetchPlatformQueryResult + override val dir: Dir = directories + override val link: String = link + override val listOutput: Consumer = output + override val downloadProgressFlow = downloadProgressReport + } + ) diff --git a/spotiflyer-ios b/spotiflyer-ios index 31517f90..f218336b 160000 --- a/spotiflyer-ios +++ b/spotiflyer-ios @@ -1 +1 @@ -Subproject commit 31517f90ef04efa4aea88c61ca627647c146f471 +Subproject commit f218336b5b31b365bbf34503b79f2c4f2b703d7d