Instance Keeper Freeze fix .IOS List Screen and Shared Code changes.

This commit is contained in:
shabinder 2021-05-02 22:24:40 +05:30
parent f80675cd13
commit 02d137588f
25 changed files with 236 additions and 153 deletions

View File

@ -265,8 +265,6 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
} }
override val isInternetAvailable get() = internetAvailability.value ?: true override val isInternetAvailable get() = internetAvailability.value ?: true
override val dispatcherIO = Dispatchers.IO
override val currentPlatform = AllPlatforms.Jvm
} }
} }
) )

View File

@ -36,6 +36,7 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import com.shabinder.common.database.R import com.shabinder.common.database.R
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -50,7 +51,7 @@ actual fun ImageLoad(
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) } var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) { LaunchedEffect(link) {
withContext(methods.value.dispatcherIO) { withContext(dispatcherIO) {
pic = loader(link).image pic = loader(link).image
} }
} }

View File

@ -35,6 +35,7 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.platform.Font import androidx.compose.ui.text.platform.Font
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -48,7 +49,7 @@ actual fun ImageLoad(
) { ) {
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) } var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) { LaunchedEffect(link) {
withContext(methods.value.dispatcherIO) { withContext(dispatcherIO) {
pic = loader(link).image pic = loader(link).image
} }
} }

View File

@ -10,9 +10,19 @@ actual interface PlatformActions {
val imageCacheDir: String val imageCacheDir: String
val sharedPreferences: SharedPreferences val sharedPreferences: SharedPreferences?
fun addToLibrary(path: String) fun addToLibrary(path: String)
fun sendTracksToService(array: ArrayList<TrackDetails>) fun sendTracksToService(array: ArrayList<TrackDetails>)
}
actual val StubPlatformActions = object: PlatformActions {
override val imageCacheDir: String = ""
override val sharedPreferences: SharedPreferences? = null
override fun addToLibrary(path: String) {}
override fun sendTracksToService(array: ArrayList<TrackDetails>) {}
} }

View File

@ -1,8 +1,6 @@
package com.shabinder.common.models package com.shabinder.common.models
import co.touchlab.stately.freeze import co.touchlab.stately.freeze
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
/* /*
* Holder to call platform actions from anywhere * Holder to call platform actions from anywhere
@ -17,6 +15,9 @@ interface Actions {
// Platform Specific Actions // Platform Specific Actions
val platformActions: PlatformActions val platformActions: PlatformActions
// Platform Specific Implementation Preferred
val isInternetAvailable: Boolean
// Show Toast // Show Toast
fun showPopUpMessage(string: String, long: Boolean = false) fun showPopUpMessage(string: String, long: Boolean = false)
@ -37,15 +38,6 @@ interface Actions {
// Open / Redirect to another Platform // Open / Redirect to another Platform
fun openPlatform(packageID: String, platformLink: String) 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 giveDonation() {}
override fun shareApp() {} override fun shareApp() {}
override fun openPlatform(packageID: String, platformLink: String) {} override fun openPlatform(packageID: String, platformLink: String) {}
override val dispatcherIO: CoroutineDispatcher = Dispatchers.Default
override val isInternetAvailable: Boolean = true override val isInternetAvailable: Boolean = true
override val currentPlatform: AllPlatforms = AllPlatforms.Jvm
} }

View File

@ -1,3 +1,5 @@
package com.shabinder.common.models package com.shabinder.common.models
expect interface PlatformActions expect interface PlatformActions
expect val StubPlatformActions : PlatformActions

View File

@ -1,4 +1,5 @@
package com.shabinder.common.models package com.shabinder.common.models
actual interface PlatformActions { actual interface PlatformActions {}
}
actual val StubPlatformActions = object: PlatformActions {}

View File

@ -2,6 +2,7 @@ package com.shabinder.common.models
import kotlin.native.concurrent.AtomicReference import kotlin.native.concurrent.AtomicReference
actual interface PlatformActions actual interface PlatformActions {}
actual val StubPlatformActions = object: PlatformActions {}
actual typealias NativeAtomicReference<T> = AtomicReference<T> actual typealias NativeAtomicReference<T> = AtomicReference<T>

View File

@ -1,4 +1,4 @@
package com.shabinder.common.models package com.shabinder.common.models
actual interface PlatformActions { actual interface PlatformActions {}
} actual val StubPlatformActions = object: PlatformActions {}

View File

@ -16,8 +16,17 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods 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( actual suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,

View File

@ -46,7 +46,8 @@ actual class Dir actual constructor(
const val DirKey = "downloadDir" 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){ fun setDownloadDirectory(newBasePath:String){
sharedPreferences.edit().putString(DirKey,newBasePath).apply() 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() File(imageCacheDir()).deleteRecursively()
} }
@ -86,76 +87,74 @@ actual class Dir actual constructor(
mp3ByteArray: ByteArray, mp3ByteArray: ByteArray,
trackDetails: TrackDetails, trackDetails: TrackDetails,
postProcess:(track: TrackDetails)->Unit postProcess:(track: TrackDetails)->Unit
) { ) = withContext(dispatcherIO) {
withContext(Dispatchers.IO){ val songFile = File(trackDetails.outputFilePath)
val songFile = File(trackDetails.outputFilePath) try {
try { /*
/* * Check , if Fetch was Used, File is saved Already, else write byteArray we Received
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received * */
* */ if(!songFile.exists()) {
if(!songFile.exists()) { /*Make intermediate Dirs if they don't exist yet*/
/*Make intermediate Dirs if they don't exist yet*/ songFile.parentFile.mkdirs()
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)
} }
".m4a" -> {
if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray) /*FFmpeg.executeAsync(
"-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}"
when (trackDetails.outputFilePath.substringAfterLast('.')) { ){ _, returnCode ->
".mp3" -> { 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)) Mp3File(File(songFile.absolutePath))
.removeAllTags() .removeAllTags()
.setId3v1Tags(trackDetails) .setId3v1Tags(trackDetails)
.setId3v2TagsAndSaveFile(trackDetails) .setId3v2TagsAndSaveFile(trackDetails)
addToLibrary(songFile.absolutePath) addToLibrary(songFile.absolutePath)
} } catch (e: Exception) { e.printStackTrace() }
".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){
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 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) val cachePath = imageCacheDir() + getNameURL(url)
Picture(image = (loadCachedImage(cachePath) ?: freshImage(url))?.asImageBitmap()) Picture(image = (loadCachedImage(cachePath) ?: freshImage(url))?.asImageBitmap())
} }
@ -169,19 +168,17 @@ actual class Dir actual constructor(
} }
} }
actual suspend fun cacheImage(image: Any, path: String) { actual suspend fun cacheImage(image: Any, path: String):Unit = withContext(dispatcherIO) {
withContext(Dispatchers.IO){ try {
try { FileOutputStream(path).use { out ->
FileOutputStream(path).use { out -> (image as? Bitmap)?.compress(Bitmap.CompressFormat.JPEG, 100, out)
(image as? Bitmap)?.compress(Bitmap.CompressFormat.JPEG, 100, out)
}
} catch (e: IOException) {
e.printStackTrace()
} }
} 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 { try {
val source = URL(url) val source = URL(url)
val connection: HttpURLConnection = source.openConnection() as HttpURLConnection val connection: HttpURLConnection = source.openConnection() as HttpURLConnection

View File

@ -16,11 +16,14 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
import io.ktor.client.request.* import io.ktor.client.request.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.native.concurrent.SharedImmutable
expect suspend fun downloadTracks( expect suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,
@ -28,8 +31,17 @@ expect suspend fun downloadTracks(
dir: Dir dir: Dir
) )
// IO-Dispatcher
@SharedImmutable
expect val dispatcherIO: CoroutineDispatcher
// Current Platform Info
@SharedImmutable
expect val currentPlatform: AllPlatforms
suspend fun isInternetAccessible(): Boolean { suspend fun isInternetAccessible(): Boolean {
return withContext(methods.value.dispatcherIO) { return withContext(dispatcherIO) {
try { try {
ktorHttpClient.head<String>("http://google.com") ktorHttpClient.head<String>("http://google.com")
true true

View File

@ -16,6 +16,7 @@
package com.shabinder.common.di.gaana package com.shabinder.common.di.gaana
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.corsProxy import com.shabinder.common.models.corsProxy
import com.shabinder.common.models.gaana.GaanaAlbum 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.HttpClient
import io.ktor.client.request.get 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 corsProxy.url
} // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/" } // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/"
else "" else ""

View File

@ -46,7 +46,7 @@ class SpotifyProvider(
/* init { /* init {
logger.d { "Creating Spotify Provider" } logger.d { "Creating Spotify Provider" }
GlobalScope.launch(Dispatchers.Default) { GlobalScope.launch(Dispatchers.Default) {
if (methods.value.currentPlatform is AllPlatforms.Js) { if (currentPlatform is AllPlatforms.Js) {
authenticateSpotifyClient(override = true) authenticateSpotifyClient(override = true)
} else authenticateSpotifyClient() } else authenticateSpotifyClient()
} }

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.youtubeMp3.Yt1sMp3 import com.shabinder.common.di.youtubeMp3.Yt1sMp3
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
@ -31,7 +32,7 @@ class YoutubeMp3(
suspend fun getMp3DownloadLink(videoID: String): String? = try { suspend fun getMp3DownloadLink(videoID: String): String? = try {
getLinkFromYt1sMp3(videoID)?.let { getLinkFromYt1sMp3(videoID)?.let {
logger.i { "Download Link: $it" } 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://kind-grasshopper-73.telebit.io/cors/$it"
// "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue // "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue
else it else it

View File

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

View File

@ -17,10 +17,12 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader import com.shabinder.downloader.YoutubeDownloader
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@ -30,6 +32,12 @@ val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = M
// Scope Allowing 4 Parallel Downloads // Scope Allowing 4 Parallel Downloads
val DownloadScope = ParallelExecutor(Dispatchers.IO) 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( actual suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,
fetcher: FetchPlatformQueryResult, fetcher: FetchPlatformQueryResult,

View File

@ -1,6 +1,14 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.TrackDetails 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( actual suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,

View File

@ -6,6 +6,7 @@ import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import platform.Foundation.NSCachesDirectory import platform.Foundation.NSCachesDirectory
import platform.Foundation.NSDirectoryEnumerationSkipsHiddenFiles import platform.Foundation.NSDirectoryEnumerationSkipsHiddenFiles
import platform.Foundation.NSFileManager import platform.Foundation.NSFileManager
@ -25,10 +26,6 @@ actual class Dir actual constructor(
private val spotiFlyerDatabase: SpotiFlyerDatabase, private val spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
init {
//createDirectories()
}
actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path) actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path)
actual fun fileSeparator(): String = "/" actual fun fileSeparator(): String = "/"
@ -37,6 +34,7 @@ actual class Dir actual constructor(
actual fun defaultDir(): String = defaultDirURL.path!! actual fun defaultDir(): String = defaultDirURL.path!!
val defaultDirURL: NSURL by lazy { val defaultDirURL: NSURL by lazy {
createDirectories()
val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!! val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!!
musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!! musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!!
} }
@ -44,6 +42,7 @@ actual class Dir actual constructor(
actual fun imageCacheDir(): String = imageCacheURL.path!! actual fun imageCacheDir(): String = imageCacheURL.path!!
val imageCacheURL: NSURL by lazy { val imageCacheURL: NSURL by lazy {
createDirectories()
val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null) val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null)
cacheDir?.URLByAppendingPathComponent("SpotiFlyer",true)!! 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 { try {
(image as? UIImage)?.let { (image as? UIImage)?.let {
// We Will Be Using JPEG as default format everywhere // 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 { actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO){
return try { try {
val cachePath = imageCacheURL.URLByAppendingPathComponent(getNameURL(url)) val cachePath = imageCacheURL.URLByAppendingPathComponent(getNameURL(url))
Picture(image = cachePath?.path?.let { loadCachedImage(it) } ?: loadFreshImage(url)) Picture(image = cachePath?.path?.let { loadCachedImage(it) } ?: loadFreshImage(url))
} catch (e: Exception) { } catch (e: Exception) {
@ -95,8 +94,8 @@ actual class Dir actual constructor(
} }
} }
private suspend fun loadFreshImage(url: String):UIImage? { private suspend fun loadFreshImage(url: String):UIImage? = withContext(dispatcherIO){
return try { try {
val nsURL = NSURL(string = url) val nsURL = NSURL(string = url)
val data = NSURLConnection.sendSynchronousRequest(NSURLRequest.requestWithURL(nsURL),null,null) val data = NSURLConnection.sendSynchronousRequest(NSURLRequest.requestWithURL(nsURL),null,null)
if (data != 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 { try {
val fileManager = NSFileManager.defaultManager val fileManager = NSFileManager.defaultManager
val paths = fileManager.contentsOfDirectoryAtURL(imageCacheURL, val paths = fileManager.contentsOfDirectoryAtURL(imageCacheURL,
@ -136,7 +135,7 @@ actual class Dir actual constructor(
mp3ByteArray: ByteArray, mp3ByteArray: ByteArray,
trackDetails: TrackDetails, trackDetails: TrackDetails,
postProcess:(track: TrackDetails)->Unit postProcess:(track: TrackDetails)->Unit
) { ) : Unit = withContext(dispatcherIO) {
when (trackDetails.outputFilePath.substringAfterLast('.')) { when (trackDetails.outputFilePath.substringAfterLast('.')) {
".mp3" -> { ".mp3" -> {
postProcess(trackDetails) postProcess(trackDetails)

View File

@ -16,10 +16,13 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -29,13 +32,19 @@ val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = M
// val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel // val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel
val allTracksStatus: HashMap<String, DownloadStatus> = hashMapOf() val allTracksStatus: HashMap<String, DownloadStatus> = hashMapOf()
// IO-Dispatcher
actual val dispatcherIO: CoroutineDispatcher = Dispatchers.Default
// Current Platform Info
actual val currentPlatform: AllPlatforms = AllPlatforms.Js
actual suspend fun downloadTracks( actual suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,
fetcher: FetchPlatformQueryResult, fetcher: FetchPlatformQueryResult,
dir: Dir dir: Dir
) { ) {
list.forEach { list.forEach {
withContext(methods.value.dispatcherIO) { withContext(dispatcherIO) {
allTracksStatus[it.title] = DownloadStatus.Queued allTracksStatus[it.title] = DownloadStatus.Queued
if (!it.videoID.isNullOrBlank()) { // Video ID already known! if (!it.videoID.isNullOrBlank()) { // Video ID already known!
downloadTrack(it.videoID!!, it, fetcher, dir) downloadTrack(it.videoID!!, it, fetcher, dir)

View File

@ -16,6 +16,7 @@
package com.shabinder.common.list.integration package com.shabinder.common.list.integration
import co.touchlab.stately.ensureNeverFrozen
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
@ -33,6 +34,10 @@ internal class SpotiFlyerListImpl(
dependencies: Dependencies dependencies: Dependencies
) : SpotiFlyerList, ComponentContext by componentContext, Dependencies by dependencies { ) : SpotiFlyerList, ComponentContext by componentContext, Dependencies by dependencies {
init {
instanceKeeper.ensureNeverFrozen()
}
private val store = private val store =
instanceKeeper.getStore { instanceKeeper.getStore {
SpotiFlyerListStoreProvider( SpotiFlyerListStoreProvider(

View File

@ -16,6 +16,7 @@
package com.shabinder.common.main.integration package com.shabinder.common.main.integration
import co.touchlab.stately.ensureNeverFrozen
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
@ -35,6 +36,10 @@ internal class SpotiFlyerMainImpl(
dependencies: Dependencies dependencies: Dependencies
) : SpotiFlyerMain, ComponentContext by componentContext, Dependencies by dependencies { ) : SpotiFlyerMain, ComponentContext by componentContext, Dependencies by dependencies {
init {
instanceKeeper.ensureNeverFrozen()
}
private val store = private val store =
instanceKeeper.getStore { instanceKeeper.getStore {
SpotiFlyerMainStoreProvider( SpotiFlyerMainStoreProvider(

View File

@ -25,7 +25,6 @@ fun <T : Store<*, *, *>> InstanceKeeper.getStore(key: Any, factory: () -> T): T
.store .store
inline fun <reified T : inline fun <reified T :
Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T = Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T =
getStore(T::class, factory) getStore(T::class, factory)

View File

@ -16,6 +16,7 @@
package com.shabinder.common.root.integration package com.shabinder.common.root.integration
import co.touchlab.stately.ensureNeverFrozen
import co.touchlab.stately.freeze import co.touchlab.stately.freeze
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.RouterState 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.Parcelable
import com.arkivanov.decompose.statekeeper.Parcelize import com.arkivanov.decompose.statekeeper.Parcelize
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.shabinder.common.database.getLogger
import com.shabinder.common.di.Dir 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.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
@ -45,23 +46,40 @@ import kotlinx.coroutines.launch
internal class SpotiFlyerRootImpl( internal class SpotiFlyerRootImpl(
componentContext: ComponentContext, componentContext: ComponentContext,
dependencies: Dependencies, private val main: (ComponentContext, output:Consumer<SpotiFlyerMain.Output>)->SpotiFlyerMain,
) : SpotiFlyerRoot, ComponentContext by componentContext, Dependencies by dependencies, Actions by dependencies.actions { private val list: (ComponentContext, link:String, output:Consumer<SpotiFlyerList.Output>)->SpotiFlyerList,
private val actions: Actions
) : SpotiFlyerRoot, ComponentContext by componentContext {
init { constructor(
methods.value = actions.freeze() componentContext: ComponentContext,
GlobalScope.launch { dependencies: Dependencies,
/*TESTING*/ ):this(
getLogger().apply { componentContext = componentContext,
d("Hey...","Background Thread") main = { childContext,output ->
//d(directories.defaultDir(),"Background Thread") spotiFlyerMain(
d("Hey...","Background Thread") childContext,
} output,
//*Authenticate Spotify Client*//* dependencies
/*fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient( )
override = true //methods.value.currentPlatform is AllPlatforms.Js },
)*/ 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 = private val router =
@ -80,36 +98,15 @@ internal class SpotiFlyerRootImpl(
it !is Configuration.Main it !is Configuration.Main
} }
} }
override fun setDownloadDirectory() { setDownloadDirectoryAction() } override fun setDownloadDirectory() { actions.setDownloadDirectoryAction() }
} }
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child = private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child =
when (configuration) { when (configuration) {
is Configuration.Main -> Child.Main(spotiFlyerMain(componentContext)) is Configuration.Main -> Child.Main(main(componentContext, Consumer(::onMainOutput)))
is Configuration.List -> Child.List(spotiFlyerList(componentContext, link = configuration.link)) 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<SpotiFlyerMain.Output> = 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<SpotiFlyerList.Output> = Consumer(::onListOutput)
override val downloadProgressFlow = downloadProgressReport
}
)
private fun onMainOutput(output: SpotiFlyerMain.Output) = private fun onMainOutput(output: SpotiFlyerMain.Output) =
when (output) { when (output) {
is SpotiFlyerMain.Output.Search -> router.push(Configuration.List(link = output.link)) is SpotiFlyerMain.Output.Search -> router.push(Configuration.List(link = output.link))
@ -120,6 +117,13 @@ internal class SpotiFlyerRootImpl(
is SpotiFlyerList.Output.Finished -> router.pop() 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 { private sealed class Configuration : Parcelable {
@Parcelize @Parcelize
object Main : Configuration() object Main : Configuration()
@ -128,3 +132,24 @@ internal class SpotiFlyerRootImpl(
data class List(val link: String) : Configuration() data class List(val link: String) : Configuration()
} }
} }
private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<SpotiFlyerMain.Output> ,dependencies: Dependencies): SpotiFlyerMain =
SpotiFlyerMain(
componentContext = componentContext,
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies {
override val mainOutput: Consumer<SpotiFlyerMain.Output> = output
override val dir: Dir = directories
}
)
private fun spotiFlyerList(componentContext: ComponentContext, link: String, output: Consumer<SpotiFlyerList.Output>, 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<SpotiFlyerList.Output> = output
override val downloadProgressFlow = downloadProgressReport
}
)

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