splash state and project build fixes

This commit is contained in:
shabinder 2021-03-18 06:29:29 +05:30
parent cf9ed5e885
commit b330b0732f
22 changed files with 245 additions and 68 deletions

View File

@ -22,7 +22,7 @@ import com.shabinder.common.di.initKoin
import com.shabinder.spotiflyer.di.appModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.KoinComponent
import org.koin.core.component.KoinComponent
class App: Application(), KoinComponent {
override fun onCreate() {

View File

@ -22,7 +22,13 @@ import com.shabinder.common.di.dispatcherIO
import kotlinx.coroutines.withContext
@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) }
LaunchedEffect(link){
withContext(dispatcherIO) {
@ -31,7 +37,7 @@ actual fun ImageLoad(link:String, loader:suspend (String) -> Picture, desc: Stri
}
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)
}
}

View File

@ -12,7 +12,7 @@ expect fun ImageLoad(
loader:suspend (String) ->Picture,
desc: String = "Album Art",
modifier:Modifier = Modifier,
placeholder:ImageVector = PlaceHolderImage()
//placeholder:ImageVector = PlaceHolderImage()
)
@Composable

View File

@ -25,6 +25,8 @@ import com.shabinder.common.uikit.splash.Splash
import com.shabinder.common.uikit.splash.SplashState
import com.shabinder.common.uikit.utils.verticalGradientScrim
private var isSplashShown = SplashState.Shown
@Composable
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(
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(
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(
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{
Splash(
modifier = Modifier.alpha(splashAlpha),
onTimeout = { transitionState.targetState = SplashState.Completed }
onTimeout = {
transitionState.targetState = SplashState.Completed
isSplashShown = SplashState.Completed
}
)
MainScreen(
Modifier.alpha(contentAlpha),

View File

@ -16,9 +16,14 @@ import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO
import kotlinx.coroutines.withContext
@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) }
LaunchedEffect(link){
withContext(dispatcherIO) {
@ -27,7 +32,7 @@ actual fun ImageLoad(link:String, loader:suspend (String) -> Picture, desc: Stri
}
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)
}
}

View File

@ -13,6 +13,7 @@ kotlin {
implementation(project(":common:data-models"))
implementation(project(":common:database"))
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-datetime:0.1.1")
implementation(Ktor.clientCore)
@ -23,7 +24,6 @@ kotlin {
// koin
api(Koin.core)
api(Koin.test)
api(Extras.kermit)
}
}

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails

View File

@ -2,6 +2,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.shabinder.common.database.createDatabase
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database

View File

@ -16,8 +16,8 @@ class YoutubeMp3(
private val dir: Dir,
):Yt1sMp3 {
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) "https://kind-grasshopper-73.telebit.io/cors/$it"
if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/)
"https://kind-grasshopper-73.telebit.io/cors/$it"
else it
}
}

View File

@ -169,7 +169,7 @@ class YoutubeMusic constructor(
}
}
}
//logger.d(youtubeTracks.joinToString(" abc \n"),tag)
//logger.d {youtubeTracks.joinToString("\n")}
return youtubeTracks
}
@ -222,7 +222,7 @@ class YoutubeMusic constructor(
}
if(artistMatchNumber == 0) {
//log("YT Api Removing", result.toString())
//logger.d{ "YT Api Removing: $result" }
continue
}

View File

@ -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.")
}*/

View File

@ -4,6 +4,7 @@ import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.YoutubeVideo
import com.github.kiulian.downloader.model.formats.Format
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.DownloadResult
import com.shabinder.common.models.DownloadStatus
@ -58,22 +59,27 @@ actual val isInternetAvailable:Boolean
val DownloadProgressFlow: MutableSharedFlow<HashMap<String,DownloadStatus>> = MutableSharedFlow(1)
//Scope Allowing 4 Parallel Downloads
val DownloadScope = ParallelExecutor(Dispatchers.IO)
actual suspend fun downloadTracks(
list: List<TrackDetails>,
fetcher: FetchPlatformQueryResult,
dir: Dir
){
list.forEach {
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
downloadTrack(it.videoID!!, it,dir::saveFileWithMetadata)
} else {
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
if (videoId.isNullOrBlank()) {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
} else {//Found Youtube Video ID
downloadTrack(videoId, it,dir::saveFileWithMetadata)
DownloadScope.execute { // Send Download to Pool.
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
downloadTrack(it.videoID!!, it,dir::saveFileWithMetadata)
} else {
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
if (videoId.isNullOrBlank()) {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
} else {//Found Youtube Video ID
downloadTrack(videoId, it,dir::saveFileWithMetadata)
}
}
}
}

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails

View File

@ -52,6 +52,8 @@ actual val isInternetAvailable:Boolean
}
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()
actual suspend fun downloadTracks(
@ -59,14 +61,15 @@ actual suspend fun downloadTracks(
fetcher: FetchPlatformQueryResult,
dir: Dir
){
withContext(Dispatchers.Default){
list.forEach {
list.forEach {
withContext(Dispatchers.Default) {
allTracksStatus[it.title] = DownloadStatus.Queued
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
downloadTrack(it.videoID!!, it, fetcher, dir)
} else {
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
val videoID = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery,it)
println(videoID+" : "+it.title)
if (videoID.isNullOrBlank()) {
allTracksStatus[it.title] = DownloadStatus.Failed
DownloadProgressFlow.emit(allTracksStatus)
@ -74,8 +77,8 @@ actual suspend fun downloadTracks(
downloadTrack(videoID, it, fetcher, dir)
}
}
DownloadProgressFlow.emit(allTracksStatus)
}
DownloadProgressFlow.emit(allTracksStatus)
}
}

View File

@ -2,18 +2,14 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit
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.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database
import kotlinext.js.Object
import kotlinext.js.asJsObject
import kotlinext.js.js
import kotlinext.js.jsObject
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.w3c.dom.ImageBitmap
import org.khronos.webgl.Int8Array
@ -56,7 +52,7 @@ actual class Dir actual constructor(
albumArt.collect {
when(it){
is DownloadResult.Success -> {
println("Album Art Downloaded Success")
logger.d{"Album Art Downloaded Success"}
val albumArtObj = js {
this["type"] = 3
this["data"] = it.byteArray.toArrayBuffer()
@ -65,10 +61,10 @@ actual class Dir actual constructor(
writeTagsAndSave(writer, albumArtObj as Object,trackDetails)
}
is DownloadResult.Error -> {
println("Album Art Downloading Error")
logger.d{"Album Art Downloading Error"}
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()
allTracksStatus[trackDetails.title] = DownloadStatus.Downloaded
saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3")
DownloadProgressFlow.emit(allTracksStatus)
saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3")
}
actual fun addToLibrary(path:String){}

View File

@ -21,6 +21,7 @@ import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
private val koin = initKoin(enableNetworkLogs = true).koin
fun main(){
val lifecycle = LifecycleRegistry()

View File

@ -43,27 +43,12 @@ kotlin {
implementation kotlin("stdlib-common")
}
}
commonTest {
kotlin.srcDir('src/test')
dependencies {
implementation kotlin("test-common")
implementation kotlin("test-annotations-common")
}
}
jvmMain {
kotlin.srcDir('src/jvmMain/kotlin')
dependencies {
implementation kotlin("stdlib")
}
}
jvmTest {
dependencies {
implementation kotlin("test")
implementation kotlin("test-junit")
implementation 'junit:junit:4.12'
}
}
jsMain {
kotlin.srcDir('src/jsMain/kotlin')
dependencies {
@ -78,12 +63,28 @@ kotlin {
kotlinOptions.moduleKind = "umd"
}
}
/*jsTest {
/*
commonTest {
kotlin.srcDir('src/test')
dependencies {
implementation kotlin("test-common")
implementation kotlin("test-annotations-common")
}
}
jsTest {
dependencies {
implementation kotlin("test-js")
implementation kotlin("stdlib-js")
}
}*/
}
jvmTest {
dependencies {
implementation kotlin("test")
implementation kotlin("test-junit")
implementation 'junit:junit:4.13.2'
}
}
*/
/*nativeMain {
kotlin.srcDir('src/nativeMain/kotlin')
}*/

View File

@ -1,14 +1,14 @@
ext.versions = [
kotlin : '1.4.30',
kotlinCoroutines: '1.4.2',
dokka : '0.9.17',
nodePlugin: '1.2.0'
dokka : '1.4.30',
nodePlugin: '1.3.1'
]
ext.deps = [
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}",
dokka : "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}",
node : "com.moowork.gradle:gradle-node-plugin:${versions.nodePlugin}"
]
]
]

View File

@ -48,7 +48,7 @@ private val downloadAllButton = functionalComponent<DownloadAllButtonProps>("Dow
styledDiv {
attrs {
onClickFunction = {
//props.downloadAll()
props.downloadAll()
setClicked(true)
}
}

View File

@ -106,29 +106,31 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
status = downloadStatus
}
}
is DownloadStatus.Downloading -> {
//TODO Fix Progress Indicator
/*is DownloadStatus.Downloading -> {
CircularProgressBar {
progress = downloadStatus.progress
}
}
DownloadStatus.Queued -> {
is DownloadStatus.Converting -> {
LoadingSpinner {}
}
DownloadStatus.Downloaded -> {
is DownloadStatus.Queued -> {
LoadingSpinner {}
}*/
is DownloadStatus.Downloaded -> {
DownloadButton {
onClick = {}
status = downloadStatus
}
}
DownloadStatus.Converting -> {
LoadingSpinner {}
}
DownloadStatus.Failed -> {
is DownloadStatus.Failed -> {
DownloadButton {
onClick = {}
status = downloadStatus
}
}
else -> LoadingSpinner { }
}
css {

0
web-app/src/main/resources/header-dark.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB