Code Cleaned & Ktlint Added

This commit is contained in:
shabinder 2021-03-19 20:45:58 +05:30
parent 46e5e89a2e
commit ccea676b77
144 changed files with 1709 additions and 1403 deletions

View File

@ -58,7 +58,6 @@ import com.shabinder.common.models.TrackDetails
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import com.shabinder.common.uikit.* import com.shabinder.common.uikit.*
import com.shabinder.database.Database
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import com.tonyodev.fetch2.Status import com.tonyodev.fetch2.Status
import kotlinx.coroutines.* import kotlinx.coroutines.*

View File

@ -16,6 +16,8 @@
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
id("org.jlleitschuh.gradle.ktlint")
id("org.jlleitschuh.gradle.ktlint-idea")
} }
allprojects { allprojects {

View File

@ -16,7 +16,6 @@
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
//`kotlin-dsl-precompiled-script-plugins`
} }
group = "com.shabinder" group = "com.shabinder"
@ -28,6 +27,7 @@ repositories {
mavenCentral() mavenCentral()
google() google()
maven(url = "https://jitpack.io") maven(url = "https://jitpack.io")
maven(url = "https://plugins.gradle.org/m2/")
maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers") maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers")
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev") maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
} }
@ -37,6 +37,7 @@ dependencies {
implementation("com.google.gms:google-services:4.3.5") implementation("com.google.gms:google-services:4.3.5")
implementation("com.google.firebase:perf-plugin:1.3.5") implementation("com.google.firebase:perf-plugin:1.3.5")
implementation("com.google.firebase:firebase-crashlytics-gradle:2.5.1") implementation("com.google.firebase:firebase-crashlytics-gradle:2.5.1")
implementation("org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}")
implementation(JetBrains.Compose.gradlePlugin) implementation(JetBrains.Compose.gradlePlugin)
implementation(JetBrains.Kotlin.gradlePlugin) implementation(JetBrains.Kotlin.gradlePlugin)
implementation(JetBrains.Kotlin.serialization) implementation(JetBrains.Kotlin.serialization)

View File

@ -21,9 +21,12 @@ object Versions {
const val kotlinVersion = "1.4.31" const val kotlinVersion = "1.4.31"
const val coroutinesVersion = "1.4.2" const val coroutinesVersion = "1.4.2"
//const val compose = "1.0.0-alpha12"
const val coilVersion = "0.4.1" const val coilVersion = "0.4.1"
// Code Formatting
const val ktLint = "10.0.0"
// DI // DI
const val koin = "3.0.1-beta-1" const val koin = "3.0.1-beta-1"

View File

@ -16,6 +16,7 @@
plugins { plugins {
id("com.android.library") id("com.android.library")
id("ktlint-setup")
} }
android { android {
@ -43,5 +44,4 @@ android {
res.srcDirs("src/androidMain/res") res.srcDirs("src/androidMain/res")
} }
} }
} }

View File

@ -14,9 +14,31 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.shabinder.common.di plugins {
id("org.jlleitschuh.gradle.ktlint")
sealed class NetworkResponse<out T> { id("org.jlleitschuh.gradle.ktlint-idea")
data class Success<T>(val value:T):NetworkResponse<T>() }
data class Error(val message:String):NetworkResponse<Nothing>()
subprojects {
apply(plugin = "org.jlleitschuh.gradle.ktlint")
apply(plugin = "org.jlleitschuh.gradle.ktlint-idea")
repositories {
// Required to download KtLint
mavenCentral()
}
ktlint {
android.set(true)
outputToConsole.set(true)
ignoreFailures.set(true)
coloredOutput.set(true)
verbose.set(true)
filter {
exclude("**/generated/**")
exclude("**/build/**")
}
}
// Optionally configure plugin
/*configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
debug.set(true)
}*/
} }

View File

@ -18,6 +18,7 @@ plugins {
id("com.android.library") id("com.android.library")
id("kotlin-multiplatform") id("kotlin-multiplatform")
id("org.jetbrains.compose") id("org.jetbrains.compose")
id("ktlint-setup")
} }
kotlin { kotlin {

View File

@ -17,6 +17,7 @@
plugins { plugins {
id("com.android.library") id("com.android.library")
id("kotlin-multiplatform") id("kotlin-multiplatform")
id("ktlint-setup")
} }
kotlin { kotlin {
@ -47,9 +48,7 @@ kotlin {
} }
} }
named("jsTest") { named("jsTest") {
dependencies { dependencies {}
}
} }
} }

View File

@ -14,17 +14,11 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.compose
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.kotlin
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.sourceSets
import org.gradle.kotlin.dsl.withType
import org.jetbrains.compose.compose
plugins { plugins {
// id("com.android.library")
id("android-setup") id("android-setup")
id("kotlin-multiplatform") id("kotlin-multiplatform")
id("org.jetbrains.compose") id("org.jetbrains.compose")
id("ktlint-setup")
} }
kotlin { kotlin {
@ -37,9 +31,7 @@ kotlin {
} }
sourceSets { sourceSets {
named("commonMain") { named("commonMain") {
dependencies { dependencies {}
}
} }
named("androidMain") { named("androidMain") {

View File

@ -21,7 +21,13 @@ package com.shabinder.common.uikit
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -68,7 +74,6 @@ actual fun pristineFont() = FontFamily(
Font(R.font.pristine_script, FontWeight.Bold) Font(R.font.pristine_script, FontWeight.Bold)
) )
@Composable @Composable
actual fun DownloadImageTick() { actual fun DownloadImageTick() {
Image( Image(

View File

@ -17,7 +17,7 @@
@file:Suppress("FunctionName") @file:Suppress("FunctionName")
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture

View File

@ -17,16 +17,30 @@
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.* import androidx.compose.material.CircularProgressIndicator
import androidx.compose.runtime.* import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -35,7 +49,6 @@ import com.shabinder.common.di.Picture
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
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 kotlinx.coroutines.CoroutineScope
@Composable @Composable
fun SpotiFlyerListContent( fun SpotiFlyerListContent(
@ -44,8 +57,6 @@ fun SpotiFlyerListContent(
) { ) {
val model by component.models.collectAsState(SpotiFlyerList.State()) val model by component.models.collectAsState(SpotiFlyerList.State())
val coroutineScope = rememberCoroutineScope()
Box(modifier = modifier.fillMaxSize()) { Box(modifier = modifier.fillMaxSize()) {
// TODO Better Null Handling // TODO Better Null Handling
val result = model.queryResult val result = model.queryResult
@ -60,7 +71,7 @@ fun SpotiFlyerListContent(
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
content = { content = {
item { item {
CoverImage(result.title, result.coverUrl, coroutineScope,component::loadImage) CoverImage(result.title, result.coverUrl, component::loadImage)
} }
itemsIndexed(model.trackList) { index, item -> itemsIndexed(model.trackList) { index, item ->
TrackCard( TrackCard(
@ -124,9 +135,13 @@ fun TrackCard(
CircularProgressIndicator(progress = 100f, color = colorAccent) CircularProgressIndicator(progress = 100f, color = colorAccent)
} }
is DownloadStatus.NotDownloaded -> { is DownloadStatus.NotDownloaded -> {
DownloadImageArrow(Modifier.clickable(onClick = { DownloadImageArrow(
Modifier.clickable(
onClick = {
downloadTrack() downloadTrack()
})) }
)
)
} }
} }
} }
@ -136,7 +151,6 @@ fun TrackCard(
fun CoverImage( fun CoverImage(
title: String, title: String,
coverURL: String, coverURL: String,
scope: CoroutineScope,
loadImage: suspend (String) -> Picture, loadImage: suspend (String) -> Picture,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -160,7 +174,6 @@ fun CoverImage(
maxLines = 2, maxLines = 2,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
//color = colorAccent,
) )
} }
/*scope.launch { /*scope.launch {

View File

@ -17,26 +17,54 @@
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.* import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.* import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Tab
import androidx.compose.material.TabPosition
import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults.textFieldColors import androidx.compose.material.TextFieldDefaults.textFieldColors
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.rounded.* import androidx.compose.material.icons.rounded.CardGiftcard
import androidx.compose.runtime.* import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Flag
import androidx.compose.material.icons.rounded.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
@ -146,10 +174,15 @@ fun SearchPanel(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
modifier = modifier.padding(12.dp).fillMaxWidth() modifier = modifier.padding(12.dp).fillMaxWidth()
.border( .border(
BorderStroke(2.dp, Brush.horizontalGradient(listOf( BorderStroke(
2.dp,
Brush.horizontalGradient(
listOf(
colorPrimary, colorPrimary,
colorAccent colorAccent
))), )
)
),
RoundedCornerShape(30.dp) RoundedCornerShape(30.dp)
), ),
shape = RoundedCornerShape(size = 30.dp), shape = RoundedCornerShape(size = 30.dp),
@ -168,10 +201,15 @@ fun SearchPanel(
onSearch(link) onSearch(link)
} }
}, },
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf( border = BorderStroke(
1.dp,
Brush.horizontalGradient(
listOf(
colorPrimary, colorPrimary,
colorAccent colorAccent
))) )
)
)
) { ) {
Text(text = "Search", style = SpotiFlyerTypography.h6, modifier = Modifier.padding(4.dp)) Text(text = "Search", style = SpotiFlyerTypography.h6, modifier = Modifier.padding(4.dp))
} }
@ -199,28 +237,35 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Spotify", "Open Spotify",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.spotify.music","http://open.spotify.com") }) onClick = { openPlatform("com.spotify.music", "http://open.spotify.com") }
)
) )
Spacer(modifier = modifier.padding(start = 16.dp)) Spacer(modifier = modifier.padding(start = 16.dp))
Icon(imageVector = GaanaLogo(), Icon(
imageVector = GaanaLogo(),
"Open Gaana", "Open Gaana",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.gaana","http://gaana.com") }) onClick = { openPlatform("com.gaana", "http://gaana.com") }
)
) )
Spacer(modifier = modifier.padding(start = 16.dp)) Spacer(modifier = modifier.padding(start = 16.dp))
Icon(imageVector = YoutubeLogo(), Icon(
imageVector = YoutubeLogo(),
"Open Youtube", "Open Youtube",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.google.android.youtube","http://m.youtube.com") }) onClick = { openPlatform("com.google.android.youtube", "http://m.youtube.com") }
)
) )
Spacer(modifier = modifier.padding(start = 12.dp)) Spacer(modifier = modifier.padding(start = 12.dp))
Icon(imageVector = YoutubeMusicLogo(), Icon(
imageVector = YoutubeMusicLogo(),
"Open Youtube Music", "Open Youtube Music",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.google.android.apps.youtube.music","https://music.youtube.com/") }) onClick = { openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
)
) )
} }
} }
@ -237,9 +282,11 @@ fun AboutColumn(modifier: Modifier = Modifier) {
color = colorAccent color = colorAccent
) )
Spacer(modifier = Modifier.padding(top = 6.dp)) Spacer(modifier = Modifier.padding(top = 6.dp))
Row(verticalAlignment = Alignment.CenterVertically, Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable( modifier = Modifier.fillMaxWidth().clickable(
onClick = { openPlatform("","http://github.com/Shabinder/SpotiFlyer") }) onClick = { openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }
)
.padding(vertical = 6.dp) .padding(vertical = 6.dp)
) { ) {
Icon(imageVector = GithubLogo(), "Open Project Repo", tint = Color(0xFFCCCCCC)) Icon(imageVector = GithubLogo(), "Open Project Repo", tint = Color(0xFFCCCCCC))
@ -293,9 +340,11 @@ fun AboutColumn(modifier: Modifier = Modifier) {
} }
Row( Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp) modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { .clickable(
onClick = {
shareApp() shareApp()
}), }
),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Rounded.Share, "Share SpotiFlyer App") Icon(Icons.Rounded.Share, "Share SpotiFlyer App")
@ -325,7 +374,8 @@ fun HistoryColumn(
Crossfade(list) { Crossfade(list) {
if (it.isEmpty()) { if (it.isEmpty()) {
Column(Modifier.padding(bottom = 32.dp).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.padding(bottom = 32.dp).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Icon(Icons.Outlined.Info,"No History Available Yet",modifier = Modifier.size(80.dp), Icon(
Icons.Outlined.Info, "No History Available Yet", modifier = Modifier.size(80.dp),
colorOffWhite colorOffWhite
) )
Text("No History Available", style = SpotiFlyerTypography.h4.copy(fontWeight = FontWeight.Light), textAlign = TextAlign.Center) Text("No History Available", style = SpotiFlyerTypography.h4.copy(fontWeight = FontWeight.Light), textAlign = TextAlign.Center)
@ -375,15 +425,16 @@ fun DownloadRecordItem(
Image( Image(
imageVector = ShareImage(), imageVector = ShareImage(),
"Research", "Research",
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(
onClick = {
// if(!isOnline(ctx)) showDialog("Check Your Internet Connection") else // if(!isOnline(ctx)) showDialog("Check Your Internet Connection") else
onItemClicked(item.link) onItemClicked(item.link)
}) }
)
) )
} }
} }
@Composable @Composable
fun HomeCategoryTabIndicator( fun HomeCategoryTabIndicator(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,

View File

@ -16,11 +16,24 @@
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.animation.core.* import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Spring.StiffnessLow import androidx.compose.animation.core.Spring.StiffnessLow
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar

View File

@ -20,7 +20,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@Composable @Composable
fun SpotiFlyerTheme(content: @Composable() () -> Unit) { fun SpotiFlyerTheme(content: @Composable () -> Unit) {
MaterialTheme( MaterialTheme(
colors = SpotiFlyerColors, colors = SpotiFlyerColors,
typography = SpotiFlyerTypography, typography = SpotiFlyerTypography,

View File

@ -23,7 +23,6 @@ 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.unit.sp import androidx.compose.ui.unit.sp
expect fun montserratFont(): FontFamily expect fun montserratFont(): FontFamily
expect fun pristineFont(): FontFamily expect fun pristineFont(): FontFamily

View File

@ -17,7 +17,13 @@
package com.shabinder.common.uikit.splash package com.shabinder.common.uikit.splash
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -29,7 +35,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.shabinder.common.uikit.* import com.shabinder.common.uikit.HeartIcon
import com.shabinder.common.uikit.SpotiFlyerLogo
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorAccent
import com.shabinder.common.uikit.colorPrimary
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
private const val SplashWaitTime: Long = 2000 private const val SplashWaitTime: Long = 2000

View File

@ -40,8 +40,10 @@ import kotlin.math.pow
*/ */
fun Modifier.verticalGradientScrim( fun Modifier.verticalGradientScrim(
color: Color, color: Color,
/*@FloatRange(from = 0.0, to = 1.0)*/ startYPercentage: Float = 0f, /*@FloatRange(from = 0.0, to = 1.0)*/
/*@FloatRange(from = 0.0, to = 1.0)*/ endYPercentage: Float = 1f, startYPercentage: Float = 0f,
/*@FloatRange(from = 0.0, to = 1.0)*/
endYPercentage: Float = 1f,
decay: Float = 1.0f, decay: Float = 1.0f,
numStops: Int = 16, numStops: Int = 16,
fixedHeight: Float? = null fixedHeight: Float? = null

View File

@ -19,7 +19,12 @@ package com.shabinder.common.uikit
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -97,7 +102,6 @@ actual fun ShareImage():ImageVector = vectorXmlResource("drawable/ic_share_open.
@Composable @Composable
actual fun PlaceHolderImage(): ImageVector = vectorXmlResource("drawable/music.xml") actual fun PlaceHolderImage(): ImageVector = vectorXmlResource("drawable/music.xml")
@Composable @Composable
actual fun SpotiFlyerLogo(): ImageVector = actual fun SpotiFlyerLogo(): ImageVector =
vectorXmlResource("drawable/ic_spotiflyer_logo.xml") vectorXmlResource("drawable/ic_spotiflyer_logo.xml")

View File

@ -41,7 +41,6 @@ data class TrackDetails(
var videoID: String? = null, var videoID: String? = null,
) : Parcelable ) : Parcelable
@Serializable @Serializable
sealed class DownloadStatus : Parcelable { sealed class DownloadStatus : Parcelable {
@Parcelize object Downloaded : DownloadStatus() @Parcelize object Downloaded : DownloadStatus()

View File

@ -17,7 +17,6 @@
package com.shabinder.common.models.gaana package com.shabinder.common.models.gaana
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable

View File

@ -37,4 +37,5 @@ data class Album(
var release_date_precision: String? = null, var release_date_precision: String? = null,
var tracks: PagingObjectTrack? = null, var tracks: PagingObjectTrack? = null,
var type: String? = null, var type: String? = null,
var uri: String? = null) var uri: String? = null
)

View File

@ -25,4 +25,5 @@ data class Artist(
var id: String? = null, var id: String? = null,
var name: String? = null, var name: String? = null,
var type: String? = null, var type: String? = null,
var uri: String? = null) var uri: String? = null
)

View File

@ -21,4 +21,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Copyright( data class Copyright(
var text: String? = null, var text: String? = null,
var type: String? = null) var type: String? = null
)

View File

@ -21,4 +21,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Followers( data class Followers(
var href: String? = null, var href: String? = null,
var total: Int? = null) var total: Int? = null
)

View File

@ -22,4 +22,5 @@ import kotlinx.serialization.Serializable
data class Image( data class Image(
var width: Int? = null, var width: Int? = null,
var height: Int? = null, var height: Int? = null,
var url: String? = null) var url: String? = null
)

View File

@ -24,4 +24,5 @@ data class LinkedTrack(
var href: String? = null, var href: String? = null,
var id: String? = null, var id: String? = null,
var type: String? = null, var type: String? = null,
var uri: String? = null) var uri: String? = null
)

View File

@ -26,4 +26,5 @@ data class PagingObjectPlaylistTrack(
var next: String? = null, var next: String? = null,
var offset: Int = 0, var offset: Int = 0,
var previous: String? = null, var previous: String? = null,
var total: Int = 0) var total: Int = 0
)

View File

@ -26,4 +26,5 @@ data class PagingObjectTrack(
var next: String? = null, var next: String? = null,
var offset: Int = 0, var offset: Int = 0,
var previous: String? = null, var previous: String? = null,
var total: Int = 0) var total: Int = 0
)

View File

@ -34,4 +34,5 @@ data class Playlist(
var snapshot_id: String? = null, var snapshot_id: String? = null,
var tracks: PagingObjectPlaylistTrack? = null, var tracks: PagingObjectPlaylistTrack? = null,
var type: String? = null, var type: String? = null,
var uri: String? = null) var uri: String? = null
)

View File

@ -23,4 +23,5 @@ data class PlaylistTrack(
var added_at: String? = null, var added_at: String? = null,
var added_by: UserPublic? = null, var added_by: UserPublic? = null,
var track: Track? = null, var track: Track? = null,
var is_local: Boolean? = null) var is_local: Boolean? = null
)

View File

@ -40,4 +40,3 @@ data class Track(
var popularity: Int? = null, var popularity: Int? = null,
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
) )

View File

@ -30,4 +30,5 @@ data class UserPrivate(
var images: List<Image?>? = null, var images: List<Image?>? = null,
var product: String, var product: String,
var type: String? = null, var type: String? = null,
var uri: String? = null) var uri: String? = null
)

View File

@ -27,4 +27,5 @@ data class UserPublic(
var id: String? = null, var id: String? = null,
var images: List<Image?>? = null, var images: List<Image?>? = null,
var type: String? = null, var type: String? = null,
var uri: String? = null) var uri: String? = null
)

View File

@ -16,8 +16,6 @@
package com.shabinder.common.models.wynk package com.shabinder.common.models.wynk
// Use Kotlinx JSON Parsing as in YT Music // Use Kotlinx JSON Parsing as in YT Music
data class ShortURLWynk( data class ShortURLWynk(
val actualTotal: Int, val actualTotal: Int,

View File

@ -21,7 +21,6 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.os.Environment import android.os.Environment
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
@ -63,16 +62,11 @@ actual class Dir actual constructor(
actual fun createDirectory(dirPath: String) { actual fun createDirectory(dirPath: String) {
val yourAppDir = File(dirPath) val yourAppDir = File(dirPath)
if(!yourAppDir.exists() && !yourAppDir.isDirectory) if (!yourAppDir.exists() && !yourAppDir.isDirectory) { // create empty directory
{ // create empty directory if (yourAppDir.mkdirs()) { logger.i { "$dirPath created" } } else {
if (yourAppDir.mkdirs())
{logger.i{"$dirPath created"}}
else
{
logger.e { "Unable to create Dir: $dirPath!" } logger.e { "Unable to create Dir: $dirPath!" }
} }
} } else {
else {
logger.i { "$dirPath already exists" } logger.i { "$dirPath already exists" }
} }
} }
@ -151,7 +145,8 @@ actual class Dir actual constructor(
logger.d { "Scanning File" } logger.d { "Scanning File" }
MediaScannerConnection.scanFile( MediaScannerConnection.scanFile(
appContext, appContext,
listOf(path).toTypedArray(), null,null) listOf(path).toTypedArray(), null, null
)
} }
actual suspend fun loadImage(url: String): Picture { actual suspend fun loadImage(url: String): Picture {

View File

@ -24,7 +24,6 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkRequest import android.net.NetworkRequest
import android.util.Log import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.shabinder.common.database.appContext import com.shabinder.common.database.appContext
@ -83,17 +82,17 @@ class ConnectionLiveData(context: Context = appContext) : LiveData<Boolean>() {
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network) Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network)
*/ */
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
Log.d(TAG, "onAvailable: ${network}") Log.d(TAG, "onAvailable: $network")
val networkCapabilities = cm.getNetworkCapabilities(network) val networkCapabilities = cm.getNetworkCapabilities(network)
val hasInternetCapability = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET) val hasInternetCapability = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET)
Log.d(TAG, "onAvailable: ${network}, $hasInternetCapability") Log.d(TAG, "onAvailable: $network, $hasInternetCapability")
if (hasInternetCapability == true) { if (hasInternetCapability == true) {
// check if this network actually has internet // check if this network actually has internet
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory) val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory)
if (hasInternet) { if (hasInternet) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
Log.d(TAG, "onAvailable: adding network. ${network}") Log.d(TAG, "onAvailable: adding network. $network")
validNetworks.add(network) validNetworks.add(network)
checkValidNetworks() checkValidNetworks()
} }
@ -107,11 +106,10 @@ class ConnectionLiveData(context: Context = appContext) : LiveData<Boolean>() {
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network) Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network)
*/ */
override fun onLost(network: Network) { override fun onLost(network: Network) {
Log.d(TAG, "onLost: ${network}") Log.d(TAG, "onLost: $network")
validNetworks.remove(network) validNetworks.remove(network)
checkValidNetworks() checkValidNetworks()
} }
} }
/** /**

View File

@ -16,7 +16,11 @@
package com.shabinder.common.di package com.shabinder.common.di
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner

View File

@ -23,7 +23,7 @@ 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
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
import io.ktor.client.* import io.ktor.client.HttpClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext

View File

@ -17,15 +17,22 @@
package com.shabinder.common.di.worker package com.shabinder.common.di.worker
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.* import android.app.DownloadManager
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.app.Service
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.os.* import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -33,18 +40,28 @@ import androidx.core.net.toUri
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.formats.Format
import com.shabinder.common.database.R.*
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.R
import com.shabinder.common.di.getData import com.shabinder.common.di.getData
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.tonyodev.fetch2.* import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Fetch
import com.tonyodev.fetch2.FetchListener
import com.tonyodev.fetch2.NetworkType
import com.tonyodev.fetch2.Priority
import com.tonyodev.fetch2.Request
import com.tonyodev.fetch2.Status
import com.tonyodev.fetch2core.DownloadBlock import com.tonyodev.fetch2core.DownloadBlock
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.io.File import java.io.File
import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class ForegroundService : Service(), CoroutineScope { class ForegroundService : Service(), CoroutineScope {
@ -78,7 +95,6 @@ class ForegroundService : Service(),CoroutineScope{
private val ytDownloader: YoutubeDownloader private val ytDownloader: YoutubeDownloader
get() = fetcher.youtubeProvider.ytDownloader get() = fetcher.youtubeProvider.ytDownloader
override fun onBind(intent: Intent): IBinder? = null override fun onBind(intent: Intent): IBinder? = null
@SuppressLint("UnspecifiedImmutableFlag") @SuppressLint("UnspecifiedImmutableFlag")
@ -117,9 +133,11 @@ class ForegroundService : Service(),CoroutineScope{
} }
} }
val downloadObjects: ArrayList<TrackDetails>? = (it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList( val downloadObjects: ArrayList<TrackDetails>? = (
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
"object" "object"
)) )
)
downloadObjects?.let { list -> downloadObjects?.let { list ->
downloadObjects.size.let { size -> downloadObjects.size.let { size ->
@ -175,7 +193,6 @@ class ForegroundService : Service(),CoroutineScope{
} }
} }
private fun downloadTrack(videoID: String, track: TrackDetails) { private fun downloadTrack(videoID: String, track: TrackDetails) {
launch { launch {
try { try {
@ -193,19 +210,19 @@ class ForegroundService : Service(),CoroutineScope{
} }
} }
private fun enqueueDownload(url: String, track: TrackDetails) { private fun enqueueDownload(url: String, track: TrackDetails) {
val request = Request(url, track.outputFilePath).apply { val request = Request(url, track.outputFilePath).apply {
priority = Priority.NORMAL priority = Priority.NORMAL
networkType = NetworkType.ALL networkType = NetworkType.ALL
} }
fetch.enqueue(request, fetch.enqueue(
request,
{ request1 -> { request1 ->
requestMap[request1] = track requestMap[request1] = track
logger.d(tag) { "Enqueuing Download" } logger.d(tag) { "Enqueuing Download" }
}, },
{ error -> { error ->
logger.d(tag){"Enqueuing Error:${error.throwable.toString()}"} logger.d(tag) { "Enqueuing Error:${error.throwable}" }
} }
) )
} }
@ -322,8 +339,7 @@ class ForegroundService : Service(),CoroutineScope{
launch { launch {
requestMap[download.request]?.run { requestMap[download.request]?.run {
allTracksStatus[title] = DownloadStatus.Downloading(download.progress) allTracksStatus[title] = DownloadStatus.Downloading(download.progress)
logger.d(tag){"${title} ETA: ${etaInMilliSeconds / 1000} sec"} logger.d(tag) { "$title ETA: ${etaInMilliSeconds / 1000} sec" }
val intent = Intent().apply { val intent = Intent().apply {
action = "Progress" action = "Progress"
@ -375,8 +391,6 @@ class ForegroundService : Service(),CoroutineScope{
} }
} }
/** /**
* This is the method that can be called to update the Notification * This is the method that can be called to update the Notification
*/ */
@ -467,7 +481,7 @@ class ForegroundService : Service(),CoroutineScope{
} }
private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run { private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run {
setSmallIcon(drawable.ic_download_arrow) setSmallIcon(R.drawable.ic_download_arrow)
setContentTitle("Total: $total Completed:$converted Failed:$failed") setContentTitle("Total: $total Completed:$converted Failed:$failed")
setSilent(true) setSilent(true)
setStyle( setStyle(
@ -479,7 +493,7 @@ class ForegroundService : Service(),CoroutineScope{
addLine(messageList[messageList.size - 5]) addLine(messageList[messageList.size - 5])
} }
) )
addAction(drawable.ic_round_cancel_24,"Exit",cancelIntent) addAction(R.drawable.ic_round_cancel_24, "Exit", cancelIntent)
build() build()
} }

View File

@ -23,10 +23,13 @@ import com.shabinder.common.di.providers.GaanaProvider
import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.di.providers.SpotifyProvider
import com.shabinder.common.di.providers.YoutubeMp3 import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.common.di.providers.YoutubeMusic
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.features.json.* import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.* import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.* import io.ktor.client.features.logging.DEFAULT
import io.ktor.client.features.logging.LogLevel
import io.ktor.client.features.logging.Logger
import io.ktor.client.features.logging.Logging
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.KoinAppDeclaration
@ -51,10 +54,12 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) } single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) }
} }
val kotlinxSerializer = KotlinxSerializer( Json { val kotlinxSerializer = KotlinxSerializer(
Json {
isLenient = true isLenient = true
ignoreUnknownKeys = true ignoreUnknownKeys = true
}) }
)
fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient { fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient {
install(JsonFeature) { install(JsonFeature) {

View File

@ -22,9 +22,10 @@ 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
import io.ktor.client.request.* import io.ktor.client.request.get
import io.ktor.client.statement.* import io.ktor.client.statement.HttpStatement
import io.ktor.http.* import io.ktor.http.contentLength
import io.ktor.http.isSuccess
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt import kotlin.math.roundToInt

View File

@ -22,13 +22,12 @@ import com.shabinder.common.di.providers.SpotifyProvider
import com.shabinder.common.di.providers.YoutubeMp3 import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.database.Database
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class FetchPlatformQueryResult( class FetchPlatformQueryResult(
private val gaanaProvider: GaanaProvider, private val gaanaProvider: GaanaProvider,
val spotifyProvider: SpotifyProvider, private val spotifyProvider: SpotifyProvider,
val youtubeProvider: YoutubeProvider, val youtubeProvider: YoutubeProvider,
val youtubeMusic: YoutubeMusic, val youtubeMusic: YoutubeMusic,
val youtubeMp3: YoutubeMp3, val youtubeMp3: YoutubeMp3,

View File

@ -18,8 +18,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.database.Database import io.ktor.client.HttpClient
import io.ktor.client.*
expect class YoutubeProvider( expect class YoutubeProvider(
httpClient: HttpClient, httpClient: HttpClient,

View File

@ -19,9 +19,13 @@ package com.shabinder.common.di.gaana
import com.shabinder.common.di.currentPlatform 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.* import com.shabinder.common.models.gaana.GaanaAlbum
import io.ktor.client.* import com.shabinder.common.models.gaana.GaanaArtistDetails
import io.ktor.client.request.* import com.shabinder.common.models.gaana.GaanaArtistTracks
import com.shabinder.common.models.gaana.GaanaPlaylist
import com.shabinder.common.models.gaana.GaanaSong
import io.ktor.client.HttpClient
import io.ktor.client.request.get
val corsApi get() = if (currentPlatform is AllPlatforms.Js) { val corsApi get() = if (currentPlatform is AllPlatforms.Js) {
corsProxy.url corsProxy.url

View File

@ -25,7 +25,7 @@ import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.gaana.GaanaTrack import com.shabinder.common.models.gaana.GaanaTrack
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
import io.ktor.client.* import io.ktor.client.HttpClient
class GaanaProvider( class GaanaProvider(
override val httpClient: HttpClient, override val httpClient: HttpClient,
@ -64,22 +64,14 @@ class GaanaProvider(
trackList = listOf(), trackList = listOf(),
Source.Gaana Source.Gaana
) )
logger.i { "GAANA SEARCH: $type - $link" }
with(result) { with(result) {
when (type) { when (type) {
"song" -> { "song" -> {
getGaanaSong(seokey = link).tracks.firstOrNull()?.also { getGaanaSong(seokey = link).tracks.firstOrNull()?.also {
folderType = "Tracks" folderType = "Tracks"
subFolder = "" subFolder = ""
if (dir.isPresent( it.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
it.track_title,
folderType,
subFolder,
dir.defaultDir()
)
)) {//Download Already Present!!
it.downloaded = DownloadStatus.Downloaded
}
trackList = listOf(it).toTrackDetailsList(folderType, subFolder) trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
title = it.track_title title = it.track_title
coverUrl = it.artworkLink coverUrl = it.artworkLink
@ -90,17 +82,7 @@ class GaanaProvider(
folderType = "Albums" folderType = "Albums"
subFolder = link subFolder = link
it.tracks.forEach { track -> it.tracks.forEach { track ->
if (dir.isPresent( track.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
track.track_title,
folderType,
subFolder,
dir.defaultDir()
)
)
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
} }
trackList = it.tracks.toTrackDetailsList(folderType, subFolder) trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
title = link title = link
@ -112,17 +94,7 @@ class GaanaProvider(
folderType = "Playlists" folderType = "Playlists"
subFolder = link subFolder = link
it.tracks.forEach { track -> it.tracks.forEach { track ->
if (dir.isPresent( track.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
track.track_title,
folderType,
subFolder,
dir.defaultDir()
)
)
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
} }
trackList = it.tracks.toTrackDetailsList(folderType, subFolder) trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
title = link title = link
@ -134,7 +106,6 @@ class GaanaProvider(
folderType = "Artist" folderType = "Artist"
subFolder = link subFolder = link
coverUrl = gaanaPlaceholderImageUrl coverUrl = gaanaPlaceholderImageUrl
val artistDetails =
getGaanaArtistDetails(seokey = link).artist.firstOrNull() getGaanaArtistDetails(seokey = link).artist.firstOrNull()
?.also { ?.also {
title = it.name title = it.name
@ -142,22 +113,13 @@ class GaanaProvider(
} }
getGaanaArtistTracks(seokey = link).also { getGaanaArtistTracks(seokey = link).also {
it.tracks?.forEach { track -> it.tracks?.forEach { track ->
if (dir.isPresent( track.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
track.track_title,
folderType,
subFolder,
dir.defaultDir()
)
)
) {//Download Already Present!!
track.downloaded = DownloadStatus.Downloaded
}
} }
trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList() trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList()
} }
} }
else -> {//TODO Handle Error} else -> {
// TODO Handle Error
} }
} }
return result return result
@ -180,4 +142,17 @@ class GaanaProvider(
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/) outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
) )
} }
private fun GaanaTrack.updateStatusIfPresent(folderType: String, subFolder: String) {
if (dir.isPresent(
dir.finalOutputDir(
track_title,
folderType,
subFolder,
dir.defaultDir()
)
)
) { // Download Already Present!!
downloaded = DownloadStatus.Downloaded
}
}
} }

View File

@ -17,7 +17,11 @@
package com.shabinder.common.di.providers package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.* import com.shabinder.common.di.Dir
import com.shabinder.common.di.TokenStore
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.di.spotify.SpotifyRequests import com.shabinder.common.di.spotify.SpotifyRequests
import com.shabinder.common.di.spotify.authenticateSpotify import com.shabinder.common.di.spotify.authenticateSpotify
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
@ -27,10 +31,10 @@ import com.shabinder.common.models.spotify.Album
import com.shabinder.common.models.spotify.Image import com.shabinder.common.models.spotify.Image
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
import com.shabinder.common.models.spotify.Track import com.shabinder.common.models.spotify.Track
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.features.* import io.ktor.client.features.defaultRequest
import io.ktor.client.features.json.* import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.* import io.ktor.client.request.header
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -55,8 +59,7 @@ class SpotifyProvider(
return if (token == null) { return if (token == null) {
logger.d { "Please Check your Network Connection" } logger.d { "Please Check your Network Connection" }
null null
} } else {
else{
logger.d { "Spotify Provider Created with $token" } logger.d { "Spotify Provider Created with $token" }
httpClient = HttpClient { httpClient = HttpClient {
defaultRequest { defaultRequest {
@ -89,7 +92,6 @@ class SpotifyProvider(
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?') val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/') val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
if (type == "Error" || link == "Error") { if (type == "Error" || link == "Error") {
return null return null
} }
@ -123,21 +125,13 @@ class SpotifyProvider(
getTrack(link).also { getTrack(link).also {
folderType = "Tracks" folderType = "Tracks"
subFolder = "" subFolder = ""
if (dir.isPresent( it.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
it.name.toString(),
folderType,
subFolder,
dir.defaultDir()
)
)
) {//Download Already Present!!
it.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
}
trackList = listOf(it).toTrackDetailsList(folderType, subFolder) trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
title = it.name.toString() title = it.name.toString()
coverUrl = (it.album?.images?.elementAtOrNull(1)?.url coverUrl = (
?: it.album?.images?.elementAtOrNull(0)?.url).toString() it.album?.images?.elementAtOrNull(1)?.url
?: it.album?.images?.elementAtOrNull(0)?.url
).toString()
} }
} }
@ -146,17 +140,7 @@ class SpotifyProvider(
folderType = "Albums" folderType = "Albums"
subFolder = albumObject.name.toString() subFolder = albumObject.name.toString()
albumObject.tracks?.items?.forEach { albumObject.tracks?.items?.forEach {
if (dir.isPresent( it.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
it.name.toString(),
folderType,
subFolder,
dir.defaultDir()
)
)
) {//Download Already Present!!
it.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
}
it.album = Album( it.album = Album(
images = listOf( images = listOf(
Image( Image(
@ -172,8 +156,10 @@ class SpotifyProvider(
} else { } else {
trackList = it trackList = it
title = albumObject.name.toString() title = albumObject.name.toString()
coverUrl = (albumObject.images?.elementAtOrNull(1)?.url coverUrl = (
?: albumObject.images?.elementAtOrNull(0)?.url).toString() albumObject.images?.elementAtOrNull(1)?.url
?: albumObject.images?.elementAtOrNull(0)?.url
).toString()
} }
} }
} }
@ -186,17 +172,7 @@ class SpotifyProvider(
// log("Tracks Fetched", playlistObject.tracks?.items?.size.toString()) // log("Tracks Fetched", playlistObject.tracks?.items?.size.toString())
playlistObject.tracks?.items?.forEach { playlistObject.tracks?.items?.forEach {
it.track?.let { it1 -> it.track?.let { it1 ->
if (dir.isPresent( it1.updateStatusIfPresent(folderType, subFolder)
dir.finalOutputDir(
it1.name.toString(),
folderType,
subFolder,
dir.defaultDir()
)
)
) {//Download Already Present!!
it1.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
}
tempTrackList.add(it1) tempTrackList.add(it1)
} }
} }
@ -257,4 +233,17 @@ class SpotifyProvider(
outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/) outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/)
) )
} }
private fun Track.updateStatusIfPresent(folderType: String, subFolder: String) {
if (dir.isPresent(
dir.finalOutputDir(
name.toString(),
folderType,
subFolder,
dir.defaultDir()
)
)
) { // Download Already Present!!
downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
}
}
} }

View File

@ -21,10 +21,7 @@ import com.shabinder.common.di.Dir
import com.shabinder.common.di.currentPlatform 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.CorsProxy import io.ktor.client.HttpClient
import com.shabinder.common.models.corsProxy
import com.shabinder.database.Database
import io.ktor.client.*
class YoutubeMp3( class YoutubeMp3(
override val httpClient: HttpClient, override val httpClient: HttpClient,

View File

@ -21,10 +21,21 @@ import com.shabinder.common.di.gaana.corsApi
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.YoutubeTrack import com.shabinder.common.models.YoutubeTrack
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.request.* import io.ktor.client.request.headers
import io.ktor.http.* import io.ktor.client.request.post
import kotlinx.serialization.json.* import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30" private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
@ -65,8 +76,10 @@ class YoutubeMusic constructor(
continue continue
} }
for(contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray for (
?: listOf()){ contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray
?: listOf()
) {
/** /**
* apparently content Blocks without an 'overlay' field don't have linkBlocks * apparently content Blocks without an 'overlay' field don't have linkBlocks
* I have no clue what they are and why there even exist * I have no clue what they are and why there even exist
@ -219,7 +232,6 @@ class YoutubeMusic constructor(
continue continue
} }
// Find artist match // Find artist match
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up // Will Be Using Fuzzy Search Because YT Spelling might be mucked up
// match = (no of artist names in result) / (no. of artist names on spotify) * 100 // match = (no of artist names in result) / (no. of artist names on spotify) * 100
@ -259,7 +271,9 @@ class YoutubeMusic constructor(
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt() linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
} }
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"} // logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap() return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
logger.d(tag) { "Match Found for $trackName - ${!it.isNullOrEmpty()}" }
}
} }
private suspend fun getYoutubeMusicResponse(query: String): String { private suspend fun getYoutubeMusicResponse(query: String): String {

View File

@ -19,13 +19,13 @@ package com.shabinder.common.di.spotify
import com.shabinder.common.di.isInternetAvailable import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.models.spotify.TokenData import com.shabinder.common.models.spotify.TokenData
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.features.auth.* import io.ktor.client.features.auth.Auth
import io.ktor.client.features.auth.providers.* import io.ktor.client.features.auth.providers.basic
import io.ktor.client.features.json.* import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.* import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.forms.* import io.ktor.client.request.post
import io.ktor.http.* import io.ktor.http.Parameters
suspend fun authenticateSpotify(): TokenData? { suspend fun authenticateSpotify(): TokenData? {
return if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") { return if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") {

View File

@ -21,8 +21,8 @@ import com.shabinder.common.models.spotify.Album
import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
import com.shabinder.common.models.spotify.Playlist import com.shabinder.common.models.spotify.Playlist
import com.shabinder.common.models.spotify.Track import com.shabinder.common.models.spotify.Track
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.request.* import io.ktor.client.request.get
private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1" private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1"

View File

@ -21,12 +21,19 @@ 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 io.ktor.utils.io.core.* import io.ktor.utils.io.core.Closeable
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.coroutines.* import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.channels.* import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.selects.* import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.* import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
class ParallelExecutor( class ParallelExecutor(
parentContext: CoroutineContext, parentContext: CoroutineContext,
@ -38,12 +45,10 @@ class ParallelExecutor(
private val killQueue = Channel<Unit>(Channel.UNLIMITED) private val killQueue = Channel<Unit>(Channel.UNLIMITED)
private val operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS) private val operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS)
init { init {
startOrStopProcessors(expectedCount = concurrentOperationLimit.value, actualCount = 0) startOrStopProcessors(expectedCount = concurrentOperationLimit.value, actualCount = 0)
} }
override fun close() { override fun close() {
if (!isClosed.compareAndSet(expect = false, update = true)) if (!isClosed.compareAndSet(expect = false, update = true))
return return
@ -55,7 +60,6 @@ class ParallelExecutor(
coroutineContext.cancel(cause) coroutineContext.cancel(cause)
} }
private fun CoroutineScope.launchProcessor() = launch { private fun CoroutineScope.launchProcessor() = launch {
while (true) { while (true) {
val operation = select<Operation<*>?> { val operation = select<Operation<*>?> {
@ -67,7 +71,6 @@ class ParallelExecutor(
} }
} }
suspend fun <Result> execute(block: suspend () -> Result): Result = suspend fun <Result> execute(block: suspend () -> Result): Result =
withContext(coroutineContext) { withContext(coroutineContext) {
val operation = Operation(block) val operation = Operation(block)
@ -76,7 +79,6 @@ class ParallelExecutor(
operation.result.await() operation.result.await()
} }
// TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this. // TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this.
fun setConcurrentOperationLimit(limit: Int) { fun setConcurrentOperationLimit(limit: Int) {
require(limit >= 1) { "'limit' must be greater than zero: $limit" } require(limit >= 1) { "'limit' must be greater than zero: $limit" }
@ -85,7 +87,6 @@ class ParallelExecutor(
startOrStopProcessors(expectedCount = limit, actualCount = concurrentOperationLimit.getAndSet(limit)) startOrStopProcessors(expectedCount = limit, actualCount = concurrentOperationLimit.getAndSet(limit))
} }
private fun startOrStopProcessors(expectedCount: Int, actualCount: Int) { private fun startOrStopProcessors(expectedCount: Int, actualCount: Int) {
if (expectedCount == actualCount) if (expectedCount == actualCount)
return return
@ -105,7 +106,6 @@ class ParallelExecutor(
repeat(-change) { killQueue.offer(Unit) } repeat(-change) { killQueue.offer(Unit) }
} }
private class Operation<Result>( private class Operation<Result>(
private val block: suspend () -> Result, private val block: suspend () -> Result,
) { ) {
@ -114,12 +114,10 @@ class ParallelExecutor(
val result: Deferred<Result> get() = _result val result: Deferred<Result> get() = _result
suspend fun execute() { suspend fun execute() {
try { try {
_result.complete(block()) _result.complete(block())
} } catch (e: Throwable) {
catch (e: Throwable) {
_result.completeExceptionally(e) _result.completeExceptionally(e)
} }
} }

View File

@ -16,8 +16,6 @@
package com.shabinder.common.di.utils package com.shabinder.common.di.utils
/** /**
* Removing Illegal Chars from File Name * Removing Illegal Chars from File Name
* **/ * **/

View File

@ -17,10 +17,10 @@
package com.shabinder.common.di.youtubeMp3 package com.shabinder.common.di.youtubeMp3
import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.di.gaana.corsApi
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.request.* import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.forms.* import io.ktor.client.request.post
import io.ktor.http.* import io.ktor.http.Parameters
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
@ -44,20 +44,24 @@ interface Yt1sMp3 {
* */ * */
private suspend fun getKey(videoID: String): String { private suspend fun getKey(videoID: String): String {
val response: JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") { val response: JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") {
body = FormDataContent(Parameters.build { body = FormDataContent(
Parameters.build {
append("q", "https://www.youtube.com/watch?v=$videoID") append("q", "https://www.youtube.com/watch?v=$videoID")
append("vt", "mp3") append("vt", "mp3")
}) }
)
} }
return response?.get("kc")?.jsonPrimitive.toString() return response?.get("kc")?.jsonPrimitive.toString()
} }
private suspend fun getConvertedMp3Link(videoID: String, key: String): JsonObject? { private suspend fun getConvertedMp3Link(videoID: String, key: String): JsonObject? {
return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") { return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") {
body = FormDataContent(Parameters.build { body = FormDataContent(
Parameters.build {
append("vid", videoID) append("vid", videoID)
append("k", key) append("k", key)
}) }
)
} }
} }
} }

View File

@ -25,7 +25,7 @@ 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 io.ktor.client.request.* import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -91,8 +91,11 @@ actual suspend fun downloadTracks(
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)
if (videoId.isNullOrBlank()) { if (videoId.isNullOrBlank()) {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0 DownloadProgressFlow.emit(
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) }) DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(it.title, DownloadStatus.Failed) }
)
} else { // Found Youtube Video ID } else { // Found Youtube Video ID
downloadTrack(videoId, it, dir::saveFileWithMetadata) downloadTrack(videoId, it, dir::saveFileWithMetadata)
} }
@ -116,17 +119,26 @@ suspend fun downloadTrack(
downloadFile(url).collect { downloadFile(url).collect {
when (it) { when (it) {
is DownloadResult.Error -> { is DownloadResult.Error -> {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0 DownloadProgressFlow.emit(
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Failed) }) DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
)
} }
is DownloadResult.Progress -> { is DownloadResult.Progress -> {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0 DownloadProgressFlow.emit(
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloading(it.progress)) }) DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloading(it.progress)) }
)
} }
is DownloadResult.Success -> { // Todo clear map is DownloadResult.Success -> { // Todo clear map
saveFileWithMetaData(it.byteArray, trackDetails) saveFileWithMetaData(it.byteArray, trackDetails)
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0 DownloadProgressFlow.emit(
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloaded) }) DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
)
} }
} }
} }

View File

@ -25,6 +25,7 @@ import com.shabinder.database.Database
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.skija.Image import org.jetbrains.skija.Image
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -35,9 +36,6 @@ import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import javax.imageio.ImageIO import javax.imageio.ImageIO
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
private val database: Database?, private val database: Database?,
@ -60,16 +58,11 @@ actual class Dir actual constructor(
actual fun createDirectory(dirPath: String) { actual fun createDirectory(dirPath: String) {
val yourAppDir = File(dirPath) val yourAppDir = File(dirPath)
if(!yourAppDir.exists() && !yourAppDir.isDirectory) if (!yourAppDir.exists() && !yourAppDir.isDirectory) { // create empty directory
{ // create empty directory if (yourAppDir.mkdirs()) { logger.i { "$dirPath created" } } else {
if (yourAppDir.mkdirs())
{logger.i{"$dirPath created"}}
else
{
logger.e { "Unable to create Dir: $dirPath!" } logger.e { "Unable to create Dir: $dirPath!" }
} }
} } else {
else {
logger.i { "$dirPath already exists" } logger.i { "$dirPath already exists" }
} }
} }
@ -119,8 +112,10 @@ actual class Dir actual constructor(
} }
} }
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun freshImage(url: String): ImageBitmap? { private suspend fun freshImage(url: String): ImageBitmap? {
return try { return withContext(Dispatchers.IO) {
try {
val source = URL(url) val source = URL(url)
val connection: HttpURLConnection = source.openConnection() as HttpURLConnection val connection: HttpURLConnection = source.openConnection() as HttpURLConnection
connection.connectTimeout = 5000 connection.connectTimeout = 5000
@ -140,6 +135,7 @@ actual class Dir actual constructor(
null null
} }
} }
}
actual val db: Database? actual val db: Database?
get() = database get() = database

View File

@ -32,7 +32,6 @@ fun Mp3File.removeAllTags(): Mp3File {
return this return this
} }
/** /**
* Modifying Mp3 with MetaData! * Modifying Mp3 with MetaData!
**/ **/

View File

@ -23,7 +23,7 @@ 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
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
import io.ktor.client.* import io.ktor.client.HttpClient
actual class YoutubeProvider actual constructor( actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient, private val httpClient: HttpClient,

View File

@ -20,10 +20,12 @@ 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 io.ktor.client.request.* import io.ktor.client.request.head
import kotlinx.coroutines.* 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
actual val currentPlatform: AllPlatforms = AllPlatforms.Js actual val currentPlatform: AllPlatforms = AllPlatforms.Js
@ -78,7 +80,7 @@ actual suspend fun downloadTracks(
dir: Dir dir: Dir
) { ) {
list.forEach { list.forEach {
withContext(Dispatchers.Default) { 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

@ -27,8 +27,8 @@ import kotlinext.js.Object
import kotlinext.js.js import kotlinext.js.js
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.ArrayBuffer
import org.w3c.dom.ImageBitmap
import org.khronos.webgl.Int8Array import org.khronos.webgl.Int8Array
import org.w3c.dom.ImageBitmap
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,

View File

@ -18,8 +18,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.database.Database import io.ktor.client.HttpClient
import io.ktor.client.*
actual class YoutubeProvider actual constructor( actual class YoutubeProvider actual constructor(
httpClient: HttpClient, httpClient: HttpClient,

View File

@ -24,9 +24,9 @@ fun <T : Store<*, *, *>> InstanceKeeper.getStore(key: Any, factory: () -> T): T
getOrCreate(key) { StoreHolder(factory()) } getOrCreate(key) { StoreHolder(factory()) }
.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)
private class StoreHolder<T : Store<*, *, *>>( private class StoreHolder<T : Store<*, *, *>>(

View File

@ -16,10 +16,16 @@
package com.shabinder.common.list.store package com.shabinder.common.list.store
import com.arkivanov.mvikotlin.core.store.* import com.arkivanov.mvikotlin.core.store.Reducer
import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.database.getLogger import com.shabinder.common.database.getLogger
import com.shabinder.common.di.* import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.downloadTracks
import com.shabinder.common.di.queryActiveTracks
import com.shabinder.common.list.SpotiFlyerList.State import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
@ -38,7 +44,9 @@ internal class SpotiFlyerListStoreProvider(
) { ) {
val logger = getLogger() val logger = getLogger()
fun provide(): SpotiFlyerListStore = fun provide(): SpotiFlyerListStore =
object : SpotiFlyerListStore, Store<Intent, State, Nothing> by storeFactory.create( object :
SpotiFlyerListStore,
Store<Intent, State, Nothing> by storeFactory.create(
name = "SpotiFlyerListStore", name = "SpotiFlyerListStore",
initialState = State(), initialState = State(),
bootstrapper = SimpleBootstrapper(Unit), bootstrapper = SimpleBootstrapper(Unit),

View File

@ -14,8 +14,6 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import org.jetbrains.compose.compose
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("android-setup") id("android-setup")

View File

@ -21,7 +21,10 @@ import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.isInternetAvailable import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.* import com.shabinder.common.main.SpotiFlyerMain.Dependencies
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
import com.shabinder.common.main.SpotiFlyerMain.Output
import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore import com.shabinder.common.main.store.getStore

View File

@ -24,9 +24,9 @@ fun <T : Store<*, *, *>> InstanceKeeper.getStore(key: Any, factory: () -> T): T
getOrCreate(key) { StoreHolder(factory()) } getOrCreate(key) { StoreHolder(factory()) }
.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)
private class StoreHolder<T : Store<*, *, *>>( private class StoreHolder<T : Store<*, *, *>>(

View File

@ -43,7 +43,9 @@ internal class SpotiFlyerMainStoreProvider(
) { ) {
fun provide(): SpotiFlyerMainStore = fun provide(): SpotiFlyerMainStore =
object : SpotiFlyerMainStore, Store<Intent, State, Nothing> by storeFactory.create( object :
SpotiFlyerMainStore,
Store<Intent, State, Nothing> by storeFactory.create(
name = "SpotiFlyerHomeStore", name = "SpotiFlyerHomeStore",
initialState = State(), initialState = State(),
bootstrapper = SimpleBootstrapper(Unit), bootstrapper = SimpleBootstrapper(Unit),
@ -64,7 +66,6 @@ internal class SpotiFlyerMainStoreProvider(
} }
} }
private sealed class Result { private sealed class Result {
data class ItemsLoaded(val items: List<DownloadRecord>) : Result() data class ItemsLoaded(val items: List<DownloadRecord>) : Result()
data class CategoryChanged(val category: SpotiFlyerMain.HomeCategory) : Result() data class CategoryChanged(val category: SpotiFlyerMain.HomeCategory) : Result()

View File

@ -14,8 +14,6 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import org.jetbrains.compose.compose
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("android-setup") id("android-setup")

View File

@ -16,7 +16,12 @@
package com.shabinder.common.root.integration package com.shabinder.common.root.integration
import com.arkivanov.decompose.* import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.RouterState
import com.arkivanov.decompose.pop
import com.arkivanov.decompose.popWhile
import com.arkivanov.decompose.push
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

View File

@ -18,7 +18,6 @@ import androidx.compose.desktop.DesktopMaterialTheme
import androidx.compose.desktop.Window import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
@ -31,13 +30,16 @@ import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.initKoin import com.shabinder.common.di.initKoin
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.uikit.* import com.shabinder.common.uikit.SpotiFlyerColors
import com.shabinder.common.uikit.SpotiFlyerRootContent
import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorOffWhite
import com.shabinder.database.Database import com.shabinder.database.Database
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage 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()
@ -54,7 +56,7 @@ fun main(){
typography = SpotiFlyerTypography, typography = SpotiFlyerTypography,
shapes = SpotiFlyerShapes shapes = SpotiFlyerShapes
) { ) {
val callBacks = SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot)).callBacks SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot))
} }
} }
} }

View File

@ -16,7 +16,6 @@
package com.willowtreeapps.fuzzywuzzy package com.willowtreeapps.fuzzywuzzy
/** /**
* Transforms an item of type T to a String. * Transforms an item of type T to a String.
* *

View File

@ -18,7 +18,10 @@ package com.willowtreeapps.fuzzywuzzy.diffutils
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditOp import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditOp
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.* import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.DELETE
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.INSERT
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.KEEP
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.REPLACE
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.MatchingBlock import com.willowtreeapps.fuzzywuzzy.diffutils.structs.MatchingBlock
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.OpCode import com.willowtreeapps.fuzzywuzzy.diffutils.structs.OpCode
@ -39,18 +42,12 @@ object DiffUtils {
var len2Copy = len2 var len2Copy = len2
var len1o = 0 var len1o = 0
val len2o: Int
var i = 0 var i = 0
val matrix: IntArray
val c1 = s1
val c2 = s2
var p1 = 0 var p1 = 0
var p2 = 0 var p2 = 0
while (len1Copy > 0 && len2Copy > 0 && c1[p1] == c2[p2]) { while (len1Copy > 0 && len2Copy > 0 && s1[p1] == s2[p2]) {
len1Copy-- len1Copy--
len2Copy-- len2Copy--
@ -60,10 +57,10 @@ object DiffUtils {
len1o++ len1o++
} }
len2o = len1o val len2o: Int = len1o
/* strip common suffix */ /* strip common suffix */
while (len1Copy > 0 && len2Copy > 0 && c1[p1 + len1Copy - 1] == c2[p2 + len2Copy - 1]) { while (len1Copy > 0 && len2Copy > 0 && s1[p1 + len1Copy - 1] == s2[p2 + len2Copy - 1]) {
len1Copy-- len1Copy--
len2Copy-- len2Copy--
} }
@ -71,7 +68,7 @@ object DiffUtils {
len1Copy++ len1Copy++
len2Copy++ len2Copy++
matrix = IntArray(len2Copy * len1Copy) val matrix: IntArray = IntArray(len2Copy * len1Copy)
while (i < len2Copy) { while (i < len2Copy) {
matrix[i] = i matrix[i] = i
@ -90,7 +87,7 @@ object DiffUtils {
var ptrC = i * len2Copy var ptrC = i * len2Copy
val ptrEnd = ptrC + len2Copy - 1 val ptrEnd = ptrC + len2Copy - 1
val char1 = c1[p1 + i - 1] val char1 = s1[p1 + i - 1]
var ptrChar2 = p2 var ptrChar2 = p2
var x = i var x = i
@ -99,7 +96,7 @@ object DiffUtils {
while (ptrC <= ptrEnd) { while (ptrC <= ptrEnd) {
var c3 = matrix[ptrPrev++] + if (char1 != c2[ptrChar2++]) 1 else 0 var c3 = matrix[ptrPrev++] + if (char1 != s2[ptrChar2++]) 1 else 0
x++ x++
if (x > c3) { if (x > c3) {
@ -113,14 +110,12 @@ object DiffUtils {
} }
matrix[ptrC++] = x matrix[ptrC++] = x
} }
i++ i++
} }
return editOpsFromCostMatrix(len1Copy, c1, p1, len1o, len2Copy, c2, p2, len2o, matrix) return editOpsFromCostMatrix(len1Copy, s1, p1, len1o, len2Copy, s2, p2, len2o, matrix)
} }
@ -134,11 +129,9 @@ object DiffUtils {
var ptr: Int = len1 * len2 - 1 var ptr: Int = len1 * len2 - 1
val ops: Array<EditOp?>
var dir = 0 var dir = 0
ops = arrayOfNulls(pos) val ops: Array<EditOp?> = arrayOfNulls(pos)
while (i > 0 || j > 0) { while (i > 0 || j > 0) {
@ -246,10 +239,9 @@ object DiffUtils {
val n = ops.size val n = ops.size
var noOfMB = 0 var noOfMB = 0
var i: Int
var o = 0 var o = 0
i = n var i: Int = n
while (i-- != 0) { while (i-- != 0) {
if (ops[o].type === KEEP) { if (ops[o].type === KEEP) {
@ -324,7 +316,6 @@ object DiffUtils {
val n = ops.size val n = ops.size
var numberOfMatchingBlocks = 0 var numberOfMatchingBlocks = 0
var i: Int
var spos: Int var spos: Int
var dpos: Int var dpos: Int
@ -335,7 +326,7 @@ object DiffUtils {
var type: EditType var type: EditType
i = n var i: Int = n
while (i != 0) { while (i != 0) {

View File

@ -28,6 +28,7 @@ import com.willowtreeapps.fuzzywuzzy.diffutils.ratio.SimpleRatio
/** /**
* FuzzySearch facade class * FuzzySearch facade class
*/ */
@Suppress("unused")
object FuzzySearch { object FuzzySearch {
/** /**

View File

@ -19,7 +19,6 @@ package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms
import com.willowtreeapps.fuzzywuzzy.ToStringFunction import com.willowtreeapps.fuzzywuzzy.ToStringFunction
import com.willowtreeapps.fuzzywuzzy.diffutils.Applicable import com.willowtreeapps.fuzzywuzzy.diffutils.Applicable
abstract class BasicAlgorithm : Applicable { abstract class BasicAlgorithm : Applicable {
var stringFunction: ToStringFunction<String>? = null var stringFunction: ToStringFunction<String>? = null

View File

@ -27,7 +27,6 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.round import kotlin.math.round
class WeightedRatio : BasicAlgorithm() { class WeightedRatio : BasicAlgorithm() {

View File

@ -26,7 +26,11 @@ import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.database.Database import com.shabinder.database.Database
import extras.renderableChild import extras.renderableChild
import react.* import react.RBuilder
import react.RComponent
import react.RProps
import react.RState
import react.ReactElement
import root.RootR import root.RootR
external interface AppProps : RProps { external interface AppProps : RProps {
@ -45,8 +49,6 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
private val lifecycle = LifecycleRegistry() private val lifecycle = LifecycleRegistry()
private val ctx = DefaultComponentContext(lifecycle = lifecycle) private val ctx = DefaultComponentContext(lifecycle = lifecycle)
private val dependencies = props.dependencies private val dependencies = props.dependencies
private val logger:Kermit
get() = dependencies.logger
private val root = SpotiFlyerRoot(ctx, private val root = SpotiFlyerRoot(ctx,
object : SpotiFlyerRoot.Dependencies{ object : SpotiFlyerRoot.Dependencies{

View File

@ -14,10 +14,28 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import kotlinx.css.* import kotlinx.css.Align
import kotlinx.css.BorderStyle
import kotlinx.css.Color
import kotlinx.css.Display
import kotlinx.css.JustifyContent
import kotlinx.css.alignContent
import kotlinx.css.alignItems
import kotlinx.css.backgroundColor
import kotlinx.css.borderBottomColor
import kotlinx.css.borderBottomStyle
import kotlinx.css.borderColor
import kotlinx.css.borderRadius
import kotlinx.css.borderRightColor
import kotlinx.css.borderWidth
import kotlinx.css.color
import kotlinx.css.display
import kotlinx.css.justifyContent
import kotlinx.css.margin
import kotlinx.css.padding
import kotlinx.css.px
import styled.StyleSheet import styled.StyleSheet
val colorPrimary = Color("#FC5C7D") val colorPrimary = Color("#FC5C7D")
val colorPrimaryDark = Color("#CE1CFF") val colorPrimaryDark = Color("#CE1CFF")
val colorAccent = Color("#9AB3FF") val colorAccent = Color("#9AB3FF")

View File

@ -16,9 +16,13 @@
package extras package extras
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import react.RComponent import react.RComponent
import react.RProps import react.RProps
import react.RState import react.RState

View File

@ -19,7 +19,10 @@ package extras
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.ValueObserver import com.arkivanov.decompose.value.ValueObserver
import extras.RenderableRootComponent.Props import extras.RenderableRootComponent.Props
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import react.RComponent import react.RComponent
import react.RProps import react.RProps
import react.RState import react.RState

View File

@ -16,16 +16,22 @@
package home package home
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.models.AllPlatforms
import extras.RenderableComponent import extras.RenderableComponent
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.css.* import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.FlexDirection
import kotlinx.css.JustifyContent
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.flexDirection
import kotlinx.css.flexGrow
import kotlinx.css.justifyContent
import kotlinx.dom.appendElement import kotlinx.dom.appendElement
import react.* import react.RBuilder
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv

View File

@ -16,14 +16,23 @@
package home package home
import kotlinx.browser.document import Styles
import kotlinx.css.* import kotlinx.css.borderRadius
import kotlinx.dom.appendElement import kotlinx.css.height
import kotlinx.dom.createElement import kotlinx.css.margin
import kotlinx.html.SCRIPT import kotlinx.css.px
import kotlinx.css.width
import kotlinx.html.id import kotlinx.html.id
import react.* import react.RBuilder
import styled.* import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css
import styled.styledA
import styled.styledDiv
import styled.styledForm
import styled.styledImg
external interface IconListProps : RProps { external interface IconListProps : RProps {
var iconsAndPlatforms: Map<String,String> var iconsAndPlatforms: Map<String,String>

View File

@ -16,8 +16,13 @@
package home package home
import kotlinx.css.* import kotlinx.css.em
import react.* import kotlinx.css.fontSize
import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledH1 import styled.styledH1

View File

@ -22,9 +22,15 @@ import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onKeyDownFunction import kotlinx.html.js.onKeyDownFunction
import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLInputElement
import org.w3c.dom.Window import react.RBuilder
import react.* import react.RProps
import styled.* import react.child
import react.functionalComponent
import styled.css
import styled.styledButton
import styled.styledDiv
import styled.styledImg
import styled.styledInput
external interface SearchbarProps : RProps { external interface SearchbarProps : RProps {
var link: String var link: String

View File

@ -16,8 +16,18 @@
package list package list
import kotlinx.css.* import kotlinx.css.Display
import react.* import kotlinx.css.JustifyContent
import kotlinx.css.display
import kotlinx.css.justifyContent
import kotlinx.css.marginBottom
import kotlinx.css.px
import kotlinx.css.width
import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledSpan import styled.styledSpan

View File

@ -16,15 +16,29 @@
package list package list
import kotlinx.css.* import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.FlexDirection
import kotlinx.css.TextAlign
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.flexDirection
import kotlinx.css.height
import kotlinx.css.marginTop
import kotlinx.css.px
import kotlinx.css.textAlign
import kotlinx.css.width
import kotlinx.html.id import kotlinx.html.id
import react.* import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledH1 import styled.styledH1
import styled.styledImg import styled.styledImg
external interface CoverImageProps : RProps { external interface CoverImageProps : RProps {
var coverImageURL: String var coverImageURL: String
var coverName: String var coverName: String

View File

@ -16,10 +16,26 @@
package list package list
import kotlinx.css.* import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.JustifyContent
import kotlinx.css.WhiteSpace
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.fontSize
import kotlinx.css.height
import kotlinx.css.justifyContent
import kotlinx.css.px
import kotlinx.css.whiteSpace
import kotlinx.html.id import kotlinx.html.id
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import react.* import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import react.useEffect
import react.useState
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledH5 import styled.styledH5

View File

@ -17,9 +17,17 @@
package list package list
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import kotlinx.css.* import kotlinx.css.borderRadius
import kotlinx.css.em
import kotlinx.css.margin
import kotlinx.css.px
import kotlinx.css.width
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import react.* import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledImg import styled.styledImg

View File

@ -20,7 +20,15 @@ import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.State import com.shabinder.common.list.SpotiFlyerList.State
import extras.RenderableComponent import extras.RenderableComponent
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.css.* import kotlinx.css.Color
import kotlinx.css.Display
import kotlinx.css.FlexDirection
import kotlinx.css.color
import kotlinx.css.display
import kotlinx.css.flexDirection
import kotlinx.css.flexGrow
import kotlinx.css.padding
import kotlinx.css.px
import kotlinx.html.id import kotlinx.html.id
import react.RBuilder import react.RBuilder
import styled.css import styled.css
@ -65,7 +73,7 @@ class ListScreen(
flexDirection = FlexDirection.column flexDirection = FlexDirection.column
color = Color.white color = Color.white
} }
state.data.trackList.forEachIndexed{ index, trackDetails -> state.data.trackList.forEachIndexed{ _, trackDetails ->
TrackItem { TrackItem {
details = trackDetails details = trackDetails
downloadTrack = model::onDownloadClicked downloadTrack = model::onDownloadClicked

View File

@ -16,12 +16,22 @@
package list package list
import kotlinx.css.* import kotlinx.css.Align
import react.* import kotlinx.css.Display
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.flexGrow
import kotlinx.css.height
import kotlinx.css.px
import kotlinx.css.width
import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@Suppress("FunctionName") @Suppress("FunctionName")
fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement { fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement {
return child(loadingAnim){ return child(loadingAnim){

View File

@ -19,7 +19,11 @@ package list
import kotlinx.css.marginRight import kotlinx.css.marginRight
import kotlinx.css.px import kotlinx.css.px
import kotlinx.css.width import kotlinx.css.width
import react.* import react.RBuilder
import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv

View File

@ -18,10 +18,43 @@ package list
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 kotlinx.css.* import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.FlexDirection
import kotlinx.css.Overflow
import kotlinx.css.TextAlign
import kotlinx.css.TextOverflow
import kotlinx.css.WhiteSpace
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.em
import kotlinx.css.flexDirection
import kotlinx.css.flexGrow
import kotlinx.css.fontSize
import kotlinx.css.height
import kotlinx.css.margin
import kotlinx.css.minWidth
import kotlinx.css.overflow
import kotlinx.css.padding
import kotlinx.css.paddingRight
import kotlinx.css.px
import kotlinx.css.textAlign
import kotlinx.css.textOverflow
import kotlinx.css.whiteSpace
import kotlinx.css.width
import kotlinx.html.id import kotlinx.html.id
import react.* import react.RBuilder
import styled.* import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import react.useEffect
import react.useState
import styled.css
import styled.styledDiv
import styled.styledH3
import styled.styledH4
import styled.styledImg
external interface TrackItemProps : RProps { external interface TrackItemProps : RProps {
var details:TrackDetails var details:TrackDetails

View File

@ -16,12 +16,33 @@
package navbar package navbar
import kotlinx.css.* import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.LinearDimension
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.filter
import kotlinx.css.fontSize
import kotlinx.css.height
import kotlinx.css.margin
import kotlinx.css.marginLeft
import kotlinx.css.marginRight
import kotlinx.css.px
import kotlinx.css.width
import kotlinx.html.id import kotlinx.html.id
import kotlinx.html.js.onBlurFunction import kotlinx.html.js.onBlurFunction
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import react.* import react.RBuilder
import styled.* import react.RProps
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css
import styled.styledA
import styled.styledDiv
import styled.styledH1
import styled.styledImg
import styled.styledNav
@Suppress("FunctionName") @Suppress("FunctionName")
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{ fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{

View File

@ -18,7 +18,7 @@ package root
import com.arkivanov.decompose.RouterState import com.arkivanov.decompose.RouterState
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.* import com.shabinder.common.root.SpotiFlyerRoot.Child
import extras.RenderableRootComponent import extras.RenderableRootComponent
import extras.renderableChild import extras.renderableChild
import home.HomeScreen import home.HomeScreen