mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
splash state and project build fixes
This commit is contained in:
parent
cf9ed5e885
commit
b330b0732f
@ -22,7 +22,7 @@ import com.shabinder.common.di.initKoin
|
|||||||
import com.shabinder.spotiflyer.di.appModule
|
import com.shabinder.spotiflyer.di.appModule
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.android.ext.koin.androidLogger
|
import org.koin.android.ext.koin.androidLogger
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
|
||||||
class App: Application(), KoinComponent {
|
class App: Application(), KoinComponent {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
@ -22,7 +22,13 @@ import com.shabinder.common.di.dispatcherIO
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun ImageLoad(link:String, loader:suspend (String) -> Picture, desc: String, modifier:Modifier, placeholder: ImageVector) {
|
actual fun ImageLoad(
|
||||||
|
link:String,
|
||||||
|
loader:suspend (String) -> Picture,
|
||||||
|
desc: String,
|
||||||
|
modifier:Modifier,
|
||||||
|
//placeholder: ImageVector
|
||||||
|
) {
|
||||||
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
|
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
|
||||||
LaunchedEffect(link){
|
LaunchedEffect(link){
|
||||||
withContext(dispatcherIO) {
|
withContext(dispatcherIO) {
|
||||||
@ -31,7 +37,7 @@ actual fun ImageLoad(link:String, loader:suspend (String) -> Picture, desc: Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
Crossfade(pic){
|
Crossfade(pic){
|
||||||
if(it == null) Image(placeholder, desc, modifier,contentScale = ContentScale.Crop) else Image(it, desc, modifier,contentScale = ContentScale.Crop)
|
if(it == null) Image(PlaceHolderImage(), desc, modifier,contentScale = ContentScale.Crop) else Image(it, desc, modifier,contentScale = ContentScale.Crop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ expect fun ImageLoad(
|
|||||||
loader:suspend (String) ->Picture,
|
loader:suspend (String) ->Picture,
|
||||||
desc: String = "Album Art",
|
desc: String = "Album Art",
|
||||||
modifier:Modifier = Modifier,
|
modifier:Modifier = Modifier,
|
||||||
placeholder:ImageVector = PlaceHolderImage()
|
//placeholder:ImageVector = PlaceHolderImage()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -25,6 +25,8 @@ import com.shabinder.common.uikit.splash.Splash
|
|||||||
import com.shabinder.common.uikit.splash.SplashState
|
import com.shabinder.common.uikit.splash.SplashState
|
||||||
import com.shabinder.common.uikit.utils.verticalGradientScrim
|
import com.shabinder.common.uikit.utils.verticalGradientScrim
|
||||||
|
|
||||||
|
private var isSplashShown = SplashState.Shown
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SpotiFlyerRootContent(component: SpotiFlyerRoot, statusBarHeight:Dp = 0.dp): SpotiFlyerRoot {
|
fun SpotiFlyerRootContent(component: SpotiFlyerRoot, statusBarHeight:Dp = 0.dp): SpotiFlyerRoot {
|
||||||
|
|
||||||
@ -34,23 +36,26 @@ fun SpotiFlyerRootContent(component: SpotiFlyerRoot, statusBarHeight:Dp = 0.dp):
|
|||||||
val splashAlpha by transition.animateFloat(
|
val splashAlpha by transition.animateFloat(
|
||||||
transitionSpec = { tween(durationMillis = 100) }
|
transitionSpec = { tween(durationMillis = 100) }
|
||||||
) {
|
) {
|
||||||
if (it == SplashState.Shown) 1f else 0f
|
if (it == SplashState.Shown && isSplashShown == SplashState.Shown) 1f else 0f
|
||||||
}
|
}
|
||||||
val contentAlpha by transition.animateFloat(
|
val contentAlpha by transition.animateFloat(
|
||||||
transitionSpec = { tween(durationMillis = 300) }
|
transitionSpec = { tween(durationMillis = 300) }
|
||||||
) {
|
) {
|
||||||
if (it == SplashState.Shown) 0f else 1f
|
if (it == SplashState.Shown && isSplashShown == SplashState.Shown) 0f else 1f
|
||||||
}
|
}
|
||||||
val contentTopPadding by transition.animateDp(
|
val contentTopPadding by transition.animateDp(
|
||||||
transitionSpec = { spring(stiffness = StiffnessLow) }
|
transitionSpec = { spring(stiffness = StiffnessLow) }
|
||||||
) {
|
) {
|
||||||
if (it == SplashState.Shown) 100.dp else 0.dp
|
if (it == SplashState.Shown && isSplashShown == SplashState.Shown) 100.dp else 0.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
Box{
|
Box{
|
||||||
Splash(
|
Splash(
|
||||||
modifier = Modifier.alpha(splashAlpha),
|
modifier = Modifier.alpha(splashAlpha),
|
||||||
onTimeout = { transitionState.targetState = SplashState.Completed }
|
onTimeout = {
|
||||||
|
transitionState.targetState = SplashState.Completed
|
||||||
|
isSplashShown = SplashState.Completed
|
||||||
|
}
|
||||||
)
|
)
|
||||||
MainScreen(
|
MainScreen(
|
||||||
Modifier.alpha(contentAlpha),
|
Modifier.alpha(contentAlpha),
|
||||||
|
@ -16,9 +16,14 @@ import com.shabinder.common.di.Picture
|
|||||||
import com.shabinder.common.di.dispatcherIO
|
import com.shabinder.common.di.dispatcherIO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun ImageLoad(link:String, loader:suspend (String) -> Picture, desc: String, modifier:Modifier, placeholder: ImageVector) {
|
actual fun ImageLoad(
|
||||||
|
link:String,
|
||||||
|
loader:suspend (String) -> Picture,
|
||||||
|
desc: String,
|
||||||
|
modifier:Modifier,
|
||||||
|
//placeholder: ImageVector
|
||||||
|
) {
|
||||||
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
|
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
|
||||||
LaunchedEffect(link){
|
LaunchedEffect(link){
|
||||||
withContext(dispatcherIO) {
|
withContext(dispatcherIO) {
|
||||||
@ -27,7 +32,7 @@ actual fun ImageLoad(link:String, loader:suspend (String) -> Picture, desc: Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
Crossfade(pic){
|
Crossfade(pic){
|
||||||
if(it == null) Image(placeholder, desc, modifier,contentScale = ContentScale.Crop) else Image(it, desc, modifier,contentScale = ContentScale.Crop)
|
if(it == null) Image(PlaceHolderImage(), desc, modifier,contentScale = ContentScale.Crop) else Image(it, desc, modifier,contentScale = ContentScale.Crop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ kotlin {
|
|||||||
implementation(project(":common:data-models"))
|
implementation(project(":common:data-models"))
|
||||||
implementation(project(":common:database"))
|
implementation(project(":common:database"))
|
||||||
implementation(project(":fuzzywuzzy:app"))
|
implementation(project(":fuzzywuzzy:app"))
|
||||||
|
implementation("org.jetbrains.kotlinx:atomicfu:0.15.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1")
|
||||||
implementation(Ktor.clientCore)
|
implementation(Ktor.clientCore)
|
||||||
@ -23,7 +24,6 @@ kotlin {
|
|||||||
// koin
|
// koin
|
||||||
api(Koin.core)
|
api(Koin.core)
|
||||||
api(Koin.test)
|
api(Koin.test)
|
||||||
|
|
||||||
api(Extras.kermit)
|
api(Extras.kermit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.di
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
|
import com.shabinder.common.di.utils.removeIllegalChars
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
|
@ -2,6 +2,7 @@ package com.shabinder.common.di
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.database.createDatabase
|
import com.shabinder.common.database.createDatabase
|
||||||
|
import com.shabinder.common.di.utils.removeIllegalChars
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
|
@ -16,8 +16,8 @@ class YoutubeMp3(
|
|||||||
private val dir: Dir,
|
private val dir: Dir,
|
||||||
):Yt1sMp3 {
|
):Yt1sMp3 {
|
||||||
suspend fun getMp3DownloadLink(videoID:String):String? = getLinkFromYt1sMp3(videoID)?.let{
|
suspend fun getMp3DownloadLink(videoID:String):String? = getLinkFromYt1sMp3(videoID)?.let{
|
||||||
println("Is Self Hosted"+(corsProxy is CorsProxy.SelfHostedCorsProxy))
|
if (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"
|
||||||
else it
|
else it
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -169,7 +169,7 @@ class YoutubeMusic constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//logger.d(youtubeTracks.joinToString(" abc \n"),tag)
|
//logger.d {youtubeTracks.joinToString("\n")}
|
||||||
return youtubeTracks
|
return youtubeTracks
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +222,7 @@ class YoutubeMusic constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(artistMatchNumber == 0) {
|
if(artistMatchNumber == 0) {
|
||||||
//log("YT Api Removing", result.toString())
|
//logger.d{ "YT Api Removing: $result" }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
package com.shabinder.common.di.utils
|
||||||
|
|
||||||
|
// Dependencies:
|
||||||
|
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
|
||||||
|
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
||||||
|
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
|
||||||
|
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
|
import kotlinx.atomicfu.atomic
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.channels.*
|
||||||
|
import kotlinx.coroutines.selects.*
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
|
class ParallelExecutor(
|
||||||
|
parentContext: CoroutineContext,
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
private val concurrentOperationLimit = atomic(4)
|
||||||
|
private val coroutineContext = parentContext + Job()
|
||||||
|
private var isClosed = atomic(false)
|
||||||
|
private val killQueue = Channel<Unit>(Channel.UNLIMITED)
|
||||||
|
private val operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS)
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
startOrStopProcessors(expectedCount = concurrentOperationLimit.value, actualCount = 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (!isClosed.compareAndSet(expect = false, update = true))
|
||||||
|
return
|
||||||
|
|
||||||
|
val cause = CancellationException("Executor was closed.")
|
||||||
|
|
||||||
|
killQueue.close(cause)
|
||||||
|
operationQueue.close(cause)
|
||||||
|
coroutineContext.cancel(cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun CoroutineScope.launchProcessor() = launch {
|
||||||
|
while (true) {
|
||||||
|
val operation = select<Operation<*>?> {
|
||||||
|
killQueue.onReceive { null }
|
||||||
|
operationQueue.onReceive { it }
|
||||||
|
} ?: break
|
||||||
|
|
||||||
|
operation.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun <Result> execute(block: suspend () -> Result): Result =
|
||||||
|
withContext(coroutineContext) {
|
||||||
|
val operation = Operation(block)
|
||||||
|
operationQueue.send(operation)
|
||||||
|
|
||||||
|
operation.result.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this.
|
||||||
|
fun setConcurrentOperationLimit(limit: Int) {
|
||||||
|
require(limit >= 1) { "'limit' must be greater than zero: $limit" }
|
||||||
|
require(limit < 1_000_000) { "Don't use a very high limit because it will cause a lot of coroutines to be started eagerly: $limit" }
|
||||||
|
|
||||||
|
startOrStopProcessors(expectedCount = limit, actualCount = concurrentOperationLimit.getAndSet(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun startOrStopProcessors(expectedCount: Int, actualCount: Int) {
|
||||||
|
if (expectedCount == actualCount)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (isClosed.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
var change = expectedCount - actualCount
|
||||||
|
while (change > 0 && killQueue.poll() != null)
|
||||||
|
change -= 1
|
||||||
|
|
||||||
|
if (change > 0)
|
||||||
|
with(CoroutineScope(coroutineContext)) {
|
||||||
|
repeat(change) { launchProcessor() }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
repeat(-change) { killQueue.offer(Unit) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class Operation<Result>(
|
||||||
|
private val block: suspend () -> Result,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val _result = CompletableDeferred<Result>()
|
||||||
|
|
||||||
|
val result: Deferred<Result> get() = _result
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun execute() {
|
||||||
|
try {
|
||||||
|
_result.complete(block())
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
_result.completeExceptionally(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
suspend fun main() = coroutineScope {
|
||||||
|
val executor = ParallelExecutor(coroutineContext)
|
||||||
|
|
||||||
|
println("Concurrency: 1")
|
||||||
|
|
||||||
|
coroutineScope {
|
||||||
|
(1 .. 200).forEach { i ->
|
||||||
|
launch {
|
||||||
|
executor.execute {
|
||||||
|
println("Execution $i")
|
||||||
|
delay(250)
|
||||||
|
|
||||||
|
when (i) {
|
||||||
|
10 -> {
|
||||||
|
println("Concurrency: 5")
|
||||||
|
executor.setConcurrentOperationLimit(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
100 -> {
|
||||||
|
println("Concurrency: 1")
|
||||||
|
executor.setConcurrentOperationLimit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
110 -> {
|
||||||
|
println("Closing executor")
|
||||||
|
executor.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Fin.")
|
||||||
|
}*/
|
@ -1,4 +1,4 @@
|
|||||||
package com.shabinder.common.di
|
package com.shabinder.common.di.utils
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.github.kiulian.downloader.YoutubeDownloader
|
|||||||
import com.github.kiulian.downloader.model.YoutubeVideo
|
import com.github.kiulian.downloader.model.YoutubeVideo
|
||||||
import com.github.kiulian.downloader.model.formats.Format
|
import com.github.kiulian.downloader.model.formats.Format
|
||||||
import com.github.kiulian.downloader.model.quality.AudioQuality
|
import com.github.kiulian.downloader.model.quality.AudioQuality
|
||||||
|
import com.shabinder.common.di.utils.ParallelExecutor
|
||||||
import com.shabinder.common.models.AllPlatforms
|
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
|
||||||
@ -58,22 +59,27 @@ actual val isInternetAvailable:Boolean
|
|||||||
|
|
||||||
val DownloadProgressFlow: MutableSharedFlow<HashMap<String,DownloadStatus>> = MutableSharedFlow(1)
|
val DownloadProgressFlow: MutableSharedFlow<HashMap<String,DownloadStatus>> = MutableSharedFlow(1)
|
||||||
|
|
||||||
|
//Scope Allowing 4 Parallel Downloads
|
||||||
|
val DownloadScope = ParallelExecutor(Dispatchers.IO)
|
||||||
|
|
||||||
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 {
|
||||||
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
|
DownloadScope.execute { // Send Download to Pool.
|
||||||
downloadTrack(it.videoID!!, it,dir::saveFileWithMetadata)
|
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
|
||||||
} else {
|
downloadTrack(it.videoID!!, it,dir::saveFileWithMetadata)
|
||||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
} else {
|
||||||
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
|
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||||
if (videoId.isNullOrBlank()) {
|
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
|
||||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
if (videoId.isNullOrBlank()) {
|
||||||
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
|
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
||||||
} else {//Found Youtube Video ID
|
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
|
||||||
downloadTrack(videoId, it,dir::saveFileWithMetadata)
|
} else {//Found Youtube Video ID
|
||||||
|
downloadTrack(videoId, it,dir::saveFileWithMetadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.di
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
|
import com.shabinder.common.di.utils.removeIllegalChars
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
|
@ -52,6 +52,8 @@ actual val isInternetAvailable:Boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)
|
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)
|
||||||
|
//Error:https://github.com/Kotlin/kotlinx.atomicfu/issues/182
|
||||||
|
//val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel
|
||||||
val allTracksStatus: HashMap<String, DownloadStatus> = hashMapOf()
|
val allTracksStatus: HashMap<String, DownloadStatus> = hashMapOf()
|
||||||
|
|
||||||
actual suspend fun downloadTracks(
|
actual suspend fun downloadTracks(
|
||||||
@ -59,14 +61,15 @@ actual suspend fun downloadTracks(
|
|||||||
fetcher: FetchPlatformQueryResult,
|
fetcher: FetchPlatformQueryResult,
|
||||||
dir: Dir
|
dir: Dir
|
||||||
){
|
){
|
||||||
withContext(Dispatchers.Default){
|
list.forEach {
|
||||||
list.forEach {
|
withContext(Dispatchers.Default) {
|
||||||
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)
|
||||||
} else {
|
} else {
|
||||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||||
val videoID = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
|
val videoID = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
|
||||||
|
println(videoID+" : "+it.title)
|
||||||
if (videoID.isNullOrBlank()) {
|
if (videoID.isNullOrBlank()) {
|
||||||
allTracksStatus[it.title] = DownloadStatus.Failed
|
allTracksStatus[it.title] = DownloadStatus.Failed
|
||||||
DownloadProgressFlow.emit(allTracksStatus)
|
DownloadProgressFlow.emit(allTracksStatus)
|
||||||
@ -74,8 +77,8 @@ actual suspend fun downloadTracks(
|
|||||||
downloadTrack(videoID, it, fetcher, dir)
|
downloadTrack(videoID, it, fetcher, dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DownloadProgressFlow.emit(allTracksStatus)
|
||||||
}
|
}
|
||||||
DownloadProgressFlow.emit(allTracksStatus)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,14 @@ package com.shabinder.common.di
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.di.gaana.corsApi
|
import com.shabinder.common.di.gaana.corsApi
|
||||||
|
import com.shabinder.common.di.utils.removeIllegalChars
|
||||||
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.database.Database
|
import com.shabinder.database.Database
|
||||||
import kotlinext.js.Object
|
import kotlinext.js.Object
|
||||||
import kotlinext.js.asJsObject
|
|
||||||
import kotlinext.js.js
|
import kotlinext.js.js
|
||||||
import kotlinext.js.jsObject
|
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import org.khronos.webgl.ArrayBuffer
|
import org.khronos.webgl.ArrayBuffer
|
||||||
import org.w3c.dom.ImageBitmap
|
import org.w3c.dom.ImageBitmap
|
||||||
import org.khronos.webgl.Int8Array
|
import org.khronos.webgl.Int8Array
|
||||||
@ -56,7 +52,7 @@ actual class Dir actual constructor(
|
|||||||
albumArt.collect {
|
albumArt.collect {
|
||||||
when(it){
|
when(it){
|
||||||
is DownloadResult.Success -> {
|
is DownloadResult.Success -> {
|
||||||
println("Album Art Downloaded Success")
|
logger.d{"Album Art Downloaded Success"}
|
||||||
val albumArtObj = js {
|
val albumArtObj = js {
|
||||||
this["type"] = 3
|
this["type"] = 3
|
||||||
this["data"] = it.byteArray.toArrayBuffer()
|
this["data"] = it.byteArray.toArrayBuffer()
|
||||||
@ -65,10 +61,10 @@ actual class Dir actual constructor(
|
|||||||
writeTagsAndSave(writer, albumArtObj as Object,trackDetails)
|
writeTagsAndSave(writer, albumArtObj as Object,trackDetails)
|
||||||
}
|
}
|
||||||
is DownloadResult.Error -> {
|
is DownloadResult.Error -> {
|
||||||
println("Album Art Downloading Error")
|
logger.d{"Album Art Downloading Error"}
|
||||||
writeTagsAndSave(writer,null,trackDetails)
|
writeTagsAndSave(writer,null,trackDetails)
|
||||||
}
|
}
|
||||||
is DownloadResult.Progress -> println("Album Art Downloading: ${it.progress}")
|
is DownloadResult.Progress -> logger.d{"Album Art Downloading: ${it.progress}"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,8 +82,8 @@ actual class Dir actual constructor(
|
|||||||
}
|
}
|
||||||
writer.addTag()
|
writer.addTag()
|
||||||
allTracksStatus[trackDetails.title] = DownloadStatus.Downloaded
|
allTracksStatus[trackDetails.title] = DownloadStatus.Downloaded
|
||||||
saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3")
|
|
||||||
DownloadProgressFlow.emit(allTracksStatus)
|
DownloadProgressFlow.emit(allTracksStatus)
|
||||||
|
saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3")
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun addToLibrary(path:String){}
|
actual fun addToLibrary(path:String){}
|
||||||
|
@ -21,6 +21,7 @@ import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
|
|||||||
|
|
||||||
private val koin = initKoin(enableNetworkLogs = true).koin
|
private val koin = initKoin(enableNetworkLogs = true).koin
|
||||||
|
|
||||||
|
|
||||||
fun main(){
|
fun main(){
|
||||||
|
|
||||||
val lifecycle = LifecycleRegistry()
|
val lifecycle = LifecycleRegistry()
|
||||||
|
@ -43,27 +43,12 @@ kotlin {
|
|||||||
implementation kotlin("stdlib-common")
|
implementation kotlin("stdlib-common")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commonTest {
|
|
||||||
kotlin.srcDir('src/test')
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin("test-common")
|
|
||||||
implementation kotlin("test-annotations-common")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmMain {
|
jvmMain {
|
||||||
kotlin.srcDir('src/jvmMain/kotlin')
|
kotlin.srcDir('src/jvmMain/kotlin')
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin("stdlib")
|
implementation kotlin("stdlib")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmTest {
|
|
||||||
dependencies {
|
|
||||||
implementation kotlin("test")
|
|
||||||
implementation kotlin("test-junit")
|
|
||||||
implementation 'junit:junit:4.12'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsMain {
|
jsMain {
|
||||||
kotlin.srcDir('src/jsMain/kotlin')
|
kotlin.srcDir('src/jsMain/kotlin')
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -78,12 +63,28 @@ kotlin {
|
|||||||
kotlinOptions.moduleKind = "umd"
|
kotlinOptions.moduleKind = "umd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*jsTest {
|
/*
|
||||||
|
commonTest {
|
||||||
|
kotlin.srcDir('src/test')
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin("test-common")
|
||||||
|
implementation kotlin("test-annotations-common")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin("test-js")
|
implementation kotlin("test-js")
|
||||||
implementation kotlin("stdlib-js")
|
implementation kotlin("stdlib-js")
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin("test")
|
||||||
|
implementation kotlin("test-junit")
|
||||||
|
implementation 'junit:junit:4.13.2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
/*nativeMain {
|
/*nativeMain {
|
||||||
kotlin.srcDir('src/nativeMain/kotlin')
|
kotlin.srcDir('src/nativeMain/kotlin')
|
||||||
}*/
|
}*/
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
ext.versions = [
|
ext.versions = [
|
||||||
kotlin : '1.4.30',
|
kotlin : '1.4.30',
|
||||||
kotlinCoroutines: '1.4.2',
|
kotlinCoroutines: '1.4.2',
|
||||||
dokka : '0.9.17',
|
dokka : '1.4.30',
|
||||||
nodePlugin: '1.2.0'
|
nodePlugin: '1.3.1'
|
||||||
]
|
]
|
||||||
ext.deps = [
|
ext.deps = [
|
||||||
plugins: [
|
plugins: [
|
||||||
android: 'com.android.tools.build:gradle:3.3.0',
|
android: 'com.android.tools.build:gradle:4.0.2',
|
||||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}",
|
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}",
|
||||||
dokka : "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}",
|
dokka : "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}",
|
||||||
node : "com.moowork.gradle:gradle-node-plugin:${versions.nodePlugin}"
|
node : "com.moowork.gradle:gradle-node-plugin:${versions.nodePlugin}"
|
||||||
|
@ -48,7 +48,7 @@ private val downloadAllButton = functionalComponent<DownloadAllButtonProps>("Dow
|
|||||||
styledDiv {
|
styledDiv {
|
||||||
attrs {
|
attrs {
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
//props.downloadAll()
|
props.downloadAll()
|
||||||
setClicked(true)
|
setClicked(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,29 +106,31 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
|
|||||||
status = downloadStatus
|
status = downloadStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DownloadStatus.Downloading -> {
|
//TODO Fix Progress Indicator
|
||||||
|
/*is DownloadStatus.Downloading -> {
|
||||||
CircularProgressBar {
|
CircularProgressBar {
|
||||||
progress = downloadStatus.progress
|
progress = downloadStatus.progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadStatus.Queued -> {
|
is DownloadStatus.Converting -> {
|
||||||
LoadingSpinner {}
|
LoadingSpinner {}
|
||||||
}
|
}
|
||||||
DownloadStatus.Downloaded -> {
|
is DownloadStatus.Queued -> {
|
||||||
|
LoadingSpinner {}
|
||||||
|
}*/
|
||||||
|
is DownloadStatus.Downloaded -> {
|
||||||
DownloadButton {
|
DownloadButton {
|
||||||
onClick = {}
|
onClick = {}
|
||||||
status = downloadStatus
|
status = downloadStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadStatus.Converting -> {
|
is DownloadStatus.Failed -> {
|
||||||
LoadingSpinner {}
|
|
||||||
}
|
|
||||||
DownloadStatus.Failed -> {
|
|
||||||
DownloadButton {
|
DownloadButton {
|
||||||
onClick = {}
|
onClick = {}
|
||||||
status = downloadStatus
|
status = downloadStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> LoadingSpinner { }
|
||||||
}
|
}
|
||||||
|
|
||||||
css {
|
css {
|
||||||
|
0
web-app/src/main/resources/header-dark.jpg
Normal file → Executable file
0
web-app/src/main/resources/header-dark.jpg
Normal file → Executable file
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Loading…
Reference in New Issue
Block a user