diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index f37792e1..4a88c08c 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -16,7 +16,6 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
import org.jetbrains.compose.compose
-import org.jetbrains.kotlin.kapt.cli.main
plugins {
id("com.android.application")
@@ -79,6 +78,9 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
+ packagingOptions {
+ exclude("META-INF/*")
+ }
configurations {
"implementation" {
exclude(group = "androidx.compose.animation")
@@ -92,7 +94,7 @@ android {
dependencies {
implementation(compose.material)
implementation(compose.materialIconsExtended)
- implementation(Androidx.androidxActivity)
+ implementation(deps.androidx.activity)
// Project's SubModules
implementation(project(":common:database"))
@@ -103,43 +105,40 @@ dependencies {
implementation(project(":common:core-components"))
implementation(project(":common:providers"))
- // Koin
- implementation(Koin.android)
- implementation(Koin.compose)
+ with(deps) {
- // DECOMPOSE
- implementation(Decompose.decompose)
- implementation(Decompose.extensionsCompose)
+ // Koin
+ with(koin) {
+ implementation(androidx.compose)
+ implementation(android)
+ }
- // MVI
- implementation(MVIKotlin.mvikotlin)
- implementation(MVIKotlin.mvikotlinMain)
- implementation(MVIKotlin.mvikotlinLogging)
- implementation(MVIKotlin.mvikotlinTimeTravel)
+ // DECOMPOSE
+ with(decompose) {
+ implementation(dep)
+ implementation(extensions.compose)
+ }
- // Extras
- with(Extras.Android) {
- implementation(countly)
- implementation(appUpdator)
+ implementation(countly.android)
+ implementation(android.app.notifier)
+ implementation(storage.chooser)
+
+ with(bundles) {
+ implementation(ktor)
+ implementation(mviKotlin)
+ implementation(androidx.lifecycle)
+ implementation(accompanist.inset)
+ }
+
+ // Test
+ testImplementation(junit)
+ androidTestImplementation(androidx.junit)
+ androidTestImplementation(androidx.expresso)
+
+ // Desugar
+ coreLibraryDesugaring(androidx.desugar)
+
+ // Debug
+ debugImplementation(leak.canary)
}
-
- with(Versions.androidxLifecycle) {
- implementation("androidx.lifecycle:lifecycle-service:$this")
- implementation("androidx.lifecycle:lifecycle-common-java8:$this")
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:$this")
- }
-
- implementation(Extras.kermit)
- // implementation("com.jakewharton.timber:timber:4.7.1")
- implementation("dev.icerock.moko:parcelize:${Versions.mokoParcelize}")
- implementation("com.github.shabinder:storage-chooser:2.0.4.45")
- implementation("com.google.accompanist:accompanist-insets:0.16.1")
-
- // Test
- testImplementation("junit:junit:4.13.2")
- androidTestImplementation(Androidx.junit)
- androidTestImplementation(Androidx.expresso)
-
- // Desugaring
- coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
}
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 847dc6dc..00bda4ce 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -23,6 +23,7 @@
+
diff --git a/android/src/main/java/com/shabinder/spotiflyer/service/ForegroundService.kt b/android/src/main/java/com/shabinder/spotiflyer/service/ForegroundService.kt
index b66853ec..d0eb2034 100644
--- a/android/src/main/java/com/shabinder/spotiflyer/service/ForegroundService.kt
+++ b/android/src/main/java/com/shabinder/spotiflyer/service/ForegroundService.kt
@@ -43,6 +43,7 @@ import com.shabinder.common.models.event.coroutines.failure
import com.shabinder.common.providers.FetchPlatformQueryResult
import com.shabinder.common.translations.Strings
import com.shabinder.spotiflyer.R
+import io.ktor.client.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -62,6 +63,7 @@ class ForegroundService : LifecycleService() {
private val fetcher: FetchPlatformQueryResult by inject()
private val logger: Kermit by inject()
private val dir: FileManager by inject()
+ private val httpClient: HttpClient by inject()
private var messageList =
java.util.Collections.synchronizedList(MutableList(5) { emptyMessage })
@@ -170,7 +172,7 @@ class ForegroundService : LifecycleService() {
trackStatusFlowMap[track.title] = DownloadStatus.Downloading()
// Enqueueing Download
- downloadFile(url).collect {
+ httpClient.downloadFile(url).collect {
when (it) {
is DownloadResult.Error -> {
logger.d(TAG) { it.message }
diff --git a/android/src/main/java/com/shabinder/spotiflyer/service/Utils.kt b/android/src/main/java/com/shabinder/spotiflyer/service/Utils.kt
index fdd75530..06b015b2 100644
--- a/android/src/main/java/com/shabinder/spotiflyer/service/Utils.kt
+++ b/android/src/main/java/com/shabinder/spotiflyer/service/Utils.kt
@@ -15,12 +15,19 @@ fun cleanFiles(dir: File) {
if (file.isDirectory) {
cleanFiles(file)
} else if (file.isFile) {
- if (file.path.toString().substringAfterLast(".") != "mp3") {
- Log.d("Files Cleaning", "Cleaning ${file.path}")
+ val filePath = file.path.toString()
+ if (filePath.substringAfterLast(".") != "mp3" || filePath.isTempFile()) {
+ Log.d("Files Cleaning", "Cleaning $filePath")
file.delete()
}
}
}
}
- } catch (e: Exception) { e.printStackTrace() }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+}
+
+private fun String.isTempFile(): Boolean {
+ return substringBeforeLast(".").takeLast(5) == ".temp"
}
diff --git a/art/SpotiFlyer.png b/art/SpotiFlyer.png
new file mode 100644
index 00000000..21c6153b
Binary files /dev/null and b/art/SpotiFlyer.png differ
diff --git a/build.gradle.kts b/build.gradle.kts
index d775fc1f..c6ff0025 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -38,22 +38,32 @@ allprojects {
tasks.withType>().configureEach {
dependsOn(":common:data-models:generateI18n4kFiles")
kotlinOptions {
- if(this is org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions) {
+ if (this is org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions) {
jvmTarget = "1.8"
}
freeCompilerArgs = (freeCompilerArgs + listOf("-Xopt-in=kotlin.RequiresOptIn"))
}
}
-
- afterEvaluate {
- project.extensions.findByType()?.let { kmpExt ->
- kmpExt.sourceSets.run {
- all {
- languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
- languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
+ configurations.all {
+ resolutionStrategy {
+ eachDependency {
+ if (requested.group == "org.jetbrains.kotlin") {
+ @Suppress("UnstableApiUsage")
+ useVersion(deps.kotlin.kotlinGradlePlugin.get().versionConstraint.requiredVersion)
}
- removeAll { it.name == "androidAndroidTestRelease" }
}
}
}
+ afterEvaluate {
+ project.extensions.findByType()
+ ?.let { kmpExt ->
+ kmpExt.sourceSets.run {
+ all {
+ languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
+ languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
+ }
+ removeAll { it.name == "androidAndroidTestRelease" }
+ }
+ }
+ }
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 33d0caf4..7c635940 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -30,17 +30,20 @@ repositories {
}
dependencies {
- implementation(Androidx.gradlePlugin)
- implementation(JetBrains.Compose.gradlePlugin)
- implementation(JetBrains.Kotlin.gradlePlugin)
- implementation(JetBrains.Kotlin.serialization)
- implementation(SqlDelight.gradlePlugin)
- implementation(KTLint.gradlePlugin)
- implementation(Internationalization.gradlePlugin)
- implementation(Mosaic.gradlePlugin)
+ with(deps) {
+ implementation(androidx.gradle.plugin)
+ implementation(kotlin.compose.gradle)
+ implementation(ktlint.gradle)
+ implementation(mosaic.gradle)
+ implementation(kotlin.kotlinGradlePlugin)
+ implementation(sqldelight.gradle.plugin)
+ implementation(i18n4k.gradle.plugin)
+ implementation(kotlin.serialization)
+ }
}
kotlin {
// Add Deps to compilation, so it will become available in main project
sourceSets.getByName("main").kotlin.srcDir("buildSrc/src/main/kotlin")
}
+
diff --git a/buildSrc/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/buildSrc/src/main/kotlin/Versions.kt
index f837ff4b..9de83bc6 100644
--- a/buildSrc/buildSrc/src/main/kotlin/Versions.kt
+++ b/buildSrc/buildSrc/src/main/kotlin/Versions.kt
@@ -14,11 +14,15 @@
* * along with this program. If not, see .
*/
-@file:Suppress("MayBeConstant", "SpellCheckingInspection")
+@file:Suppress("MayBeConstant", "SpellCheckingInspection", "UnstableApiUsage")
+import org.gradle.api.Project
import org.gradle.api.artifacts.ExternalModuleDependency
+import org.gradle.api.artifacts.VersionCatalog
+import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo
+import org.gradle.kotlin.dsl.getByType
object Versions {
// App's Version (To be bumped at each update)
@@ -26,44 +30,10 @@ object Versions {
const val versionCode = 26
- // Kotlin
- const val kotlinVersion = "1.5.21"
-
- const val coroutinesVersion = "1.5.1"
-
- // Code Formatting
- const val ktLint = "10.1.0"
-
- // Console-App UI
- const val mosaic = "0.1.0"
-
- // DI
- const val koin = "3.1.2"
-
- // Logger
- const val kermit = "0.1.9"
-
- const val mokoParcelize = "0.7.1"
-
- // Internet
- const val ktor = "1.6.2"
-
- const val kotlinxSerialization = "1.2.2"
-
- // Database
- const val sqlDelight = "1.5.1"
-
- const val sqliteJdbcDriver = "3.34.0"
- const val slf4j = "1.7.31"
-
- // Internationalisation
- const val i18n4k = "0.1.3"
-
// Android
const val minSdkVersion = 21
- const val compileSdkVersion = 30
+ const val compileSdkVersion = 31
const val targetSdkVersion = 29
- const val androidxLifecycle = "2.4.0-alpha03"
}
object HostOS {
@@ -74,143 +44,46 @@ object HostOS {
val isLinux = hostOs.startsWith("Linux", true)
}
-object MultiPlatformSettings {
- const val dep = "com.russhwolf:multiplatform-settings-no-arg:0.7.7"
-}
+val Project.Deps: VersionCatalog get() = project.extensions.getByType().named("deps")
-object KotlinJSWrappers {
- private const val bomVersion = "0.0.1-pre.235-kotlin-1.5.21"
- val bom = "org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:${bomVersion}"
- const val kotlinReact = "org.jetbrains.kotlin-wrappers:kotlin-react"
- const val kotlinReactDom = "org.jetbrains.kotlin-wrappers:kotlin-react-dom"
- const val kotlinStyled = "org.jetbrains.kotlin-wrappers:kotlin-styled"
-}
+val VersionCatalog.ktorBundle get() = findBundle("ktor").get()
+val VersionCatalog.statelyBundle get() = findBundle("stately").get()
+val VersionCatalog.androidXLifecycleBundle get() = findBundle("androidx-lifecycle").get()
+val VersionCatalog.androidXCommonBundle get() = findBundle("androidx-common").get()
+val VersionCatalog.kotlinTestBundle get() = findBundle("kotlin-test").get()
+val VersionCatalog.sqldelightBundle get() = findBundle("sqldelight").get()
+val VersionCatalog.mviKotlinBundle get() = findBundle("mviKotlin").get()
+val VersionCatalog.essentyBundle get() = findBundle("essenty").get()
+val VersionCatalog.koinAndroidBundle get() = findBundle("koin-android").get()
+val VersionCatalog.kotlinJSWrappers get() = findBundle("kotlin-js-wrappers").get()
-object Koin {
- val core = "io.insert-koin:koin-core:${Versions.koin}"
- val test = "io.insert-koin:koin-test:${Versions.koin}"
- val android = "io.insert-koin:koin-android:${Versions.koin}"
- val compose = "io.insert-koin:koin-androidx-compose:${Versions.koin}"
-}
+val VersionCatalog.kotlinJunitTest get() = findDependency("kotlin-kotlinTestJunit").get()
+val VersionCatalog.kotlinJSTest get() = findDependency("kotlin-kotlinTestJs").get()
+val VersionCatalog.kermit get() = findDependency("kermit").get()
+val VersionCatalog.decompose get() = findDependency("decompose-dep").get()
+val VersionCatalog.decomposeComposeExt get() = findDependency("decompose-extensions-compose").get()
+val VersionCatalog.jaffree get() = findDependency("jaffree").get()
-object Androidx {
- const val androidxActivity = "androidx.activity:activity-compose:1.3.1"
- const val core = "androidx.core:core-ktx:1.6.0"
- const val palette = "androidx.palette:palette-ktx:1.0.0"
- const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesVersion}"
+val VersionCatalog.ktlintGradle get() = findDependency("ktlint-gradle").get()
+val VersionCatalog.androidGradle get() = findDependency("androidx-gradle-plugin").get()
+val VersionCatalog.mosaicGradle get() = findDependency("mosaic-gradle").get()
+val VersionCatalog.kotlinComposeGradle get() = findDependency("kotlin-compose-gradle").get()
+val VersionCatalog.kotlinGradle get() = findDependency("kotlin-kotlinGradlePlugin").get()
+val VersionCatalog.i18n4kGradle get() = findDependency("i18n4k-gradle-plugin").get()
+val VersionCatalog.sqlDelightGradle get() = findDependency("sqldelight-gradle-plugin").get()
+val VersionCatalog.kotlinSerializationPlugin get() = findDependency("kotlin-serialization").get()
- const val junit = "androidx.test.ext:junit:1.1.2"
- const val expresso = "androidx.test.espresso:espresso-core:3.3.0"
+val VersionCatalog.koinCore get() = findDependency("koin-core").get()
+val VersionCatalog.kotlinCoroutines get() = findDependency("kotlin-coroutines").get()
+val VersionCatalog.kotlinxSerialization get() = findDependency("kotlinx-serialization-json").get()
+val VersionCatalog.ktorClientIOS get() = findDependency("ktor-client-ios").get()
+val VersionCatalog.ktorClientAndroid get() = findDependency("ktor-client-android").get()
+val VersionCatalog.ktorClientApache get() = findDependency("ktor-client-apache").get()
+val VersionCatalog.ktorClientJS get() = findDependency("ktor-client-js").get()
+val VersionCatalog.ktorClientCIO get() = findDependency("ktor-client-cio").get()
+val VersionCatalog.slf4j get() = findDependency("slf4j-simple").get()
- const val gradlePlugin = "com.android.tools.build:gradle:7.0.1"
-}
-
-object KTLint {
- const val gradlePlugin = "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}"
-}
-
-object JetBrains {
- object Kotlin {
- const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1-native-mt"
- const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinVersion}"
- const val serialization = "org.jetbrains.kotlin:kotlin-serialization:${Versions.kotlinVersion}"
- const val testCommon = "org.jetbrains.kotlin:kotlin-test-common:${Versions.kotlinVersion}"
- const val testJunit = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlinVersion}"
- const val testAnnotationsCommon =
- "org.jetbrains.kotlin:kotlin-test-annotations-common:${Versions.kotlinVersion}"
- }
-
- object Compose {
- // __LATEST_COMPOSE_RELEASE_VERSION__
- private const val VERSION = "1.0.0-alpha3"
- const val gradlePlugin = "org.jetbrains.compose:compose-gradle-plugin:$VERSION"
- }
-}
-
-object Mosaic {
- const val gradlePlugin = "com.jakewharton.mosaic:mosaic-gradle-plugin:${Versions.mosaic}"
-}
-
-object Decompose {
- private const val VERSION = "0.3.1"
- const val decompose = "com.arkivanov.decompose:decompose:$VERSION"
- const val decomposeIosX64 = "com.arkivanov.decompose:decompose-iosx64:$VERSION"
- const val decomposeIosArm64 = "com.arkivanov.decompose:decompose-iosarm64:$VERSION"
- const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION"
-}
-
-object MVIKotlin {
- private const val VERSION = "2.0.4"
- const val rx = "com.arkivanov.mvikotlin:rx:$VERSION"
- const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION"
- const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION"
- const val coroutines = "com.arkivanov.mvikotlin:mvikotlin-extensions-coroutines:$VERSION"
- const val keepers = "com.arkivanov.mvikotlin:keepers:$VERSION"
- const val mvikotlinMainIosX64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosx64:$VERSION"
- const val mvikotlinMainIosArm64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosarm64:$VERSION"
- const val mvikotlinLogging = "com.arkivanov.mvikotlin:mvikotlin-logging:$VERSION"
- const val mvikotlinTimeTravel = "com.arkivanov.mvikotlin:mvikotlin-timetravel:$VERSION"
- const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION"
-}
-
-object Ktor {
- val clientCore = "io.ktor:ktor-client-core:${Versions.ktor}"
- val clientJson = "io.ktor:ktor-client-json:${Versions.ktor}"
- val clientLogging = "io.ktor:ktor-client-logging:${Versions.ktor}"
- val clientSerialization = "io.ktor:ktor-client-serialization:${Versions.ktor}"
-
- val auth = "io.ktor:ktor-client-auth:${Versions.ktor}"
- val clientAndroid = "io.ktor:ktor-client-android:${Versions.ktor}"
- val clientCurl = "io.ktor:ktor-client-curl:${Versions.ktor}"
- val clientApache = "io.ktor:ktor-client-apache:${Versions.ktor}"
- val slf4j = "org.slf4j:slf4j-simple:${Versions.slf4j}"
- val clientIos = "io.ktor:ktor-client-ios:${Versions.ktor}"
- val clientCio = "io.ktor:ktor-client-cio:${Versions.ktor}"
- val clientJs = "io.ktor:ktor-client-js:${Versions.ktor}"
-}
-
-object Internationalization {
- const val dep = "de.comahe.i18n4k:i18n4k-core:${Versions.i18n4k}"
- const val gradlePlugin = "de.comahe.i18n4k:i18n4k-gradle-plugin:${Versions.i18n4k}"
-}
-
-object Extras {
- const val youtubeDownloader = "io.github.shabinder:youtube-api-dl:1.3"
- const val fuzzyWuzzy = "io.github.shabinder:fuzzywuzzy:1.1"
- const val mp3agic = "com.mpatric:mp3agic:0.9.0"
- const val jaudioTagger = "com.github.Shabinder:JAudioTagger-Android:1.0"
- const val kermit = "co.touchlab:kermit:${Versions.kermit}"
-
- object Android {
- // Self Hosted Analytics & Crashlytics (FOSS)
- val countly = "ly.count.android:sdk:20.11.8"
- val appUpdator = "com.github.amitbd1508:AppUpdater:4.1.0"
- }
-
- object Desktop {
- val countly = "ly.count.sdk:java:20.11.0"
- }
-}
-
-object Serialization {
- val json = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerialization}"
-}
-
-object SqlDelight {
- val runtime = "com.squareup.sqldelight:runtime:${Versions.sqlDelight}"
- val coroutineExtensions = "com.squareup.sqldelight:coroutines-extensions:${Versions.sqlDelight}"
-
- const val gradlePlugin = "com.squareup.sqldelight:gradle-plugin:${Versions.sqlDelight}"
- const val androidDriver = "com.squareup.sqldelight:android-driver:${Versions.sqlDelight}"
- const val sqliteDriver = "com.squareup.sqldelight:sqlite-driver:${Versions.sqlDelight}"
- const val nativeDriver = "com.squareup.sqldelight:native-driver:${Versions.sqlDelight}"
- val nativeDriverMacos = "com.squareup.sqldelight:native-driver-macosx64:${Versions.sqlDelight}"
- val jdbcDriver = "org.xerial:sqlite-jdbc:${Versions.sqliteJdbcDriver}"
-}
-
-fun DependencyHandler.`implementation`(
- dependencyNotation: String,
- dependencyConfiguration: ExternalModuleDependency.() -> Unit
-): ExternalModuleDependency = addDependencyTo(
- this, "implementation", dependencyNotation
-) { dependencyConfiguration() }
+val VersionCatalog.sqlDelightJDBC get() = findDependency("sqlite-jdbc-driver").get()
+val VersionCatalog.sqlDelightNative get() = findDependency("sqldelight-native-driver").get()
+val VersionCatalog.sqlDelightAndroid get() = findDependency("sqldelight-android-driver").get()
+val VersionCatalog.sqlDelightDriver get() = findDependency("sqldelight-driver").get()
diff --git a/buildSrc/deps.versions.toml b/buildSrc/deps.versions.toml
new file mode 100644
index 00000000..a3507216
--- /dev/null
+++ b/buildSrc/deps.versions.toml
@@ -0,0 +1,135 @@
+[versions]
+kotlin = "1.5.31"
+androidCoroutines = "1.5.1"
+ktLint = "10.1.0"
+mosaic = "0.1.0"
+koin = "3.1.2"
+kermit = "0.1.9"
+mokoParcelize = "0.7.1"
+ktor = "1.6.3"
+kotlinxSerialization = "1.3.0"
+sqlDelight = "1.5.1"
+sqliteJdbcDriver = "3.34.0"
+slf4j = "1.7.31"
+i18n4k = "0.1.3"
+essenty = "0.1.3"
+multiplatformSettings = "0.7.7"
+decompose = "0.3.1"
+mviKotlin = "2.0.4"
+accompanist = "0.18.0"
+statelyVersion = "1.1.10"
+statelyIsoVersion = "1.2.0-nmm"
+androidxLifecycle = "2.4.0-alpha03"
+
+
+[libraries]
+kotlin-kotlinGradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
+kotlin-serialization = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" }
+kotlin-kotlinTestCommon = { group = "org.jetbrains.kotlin", name = "kotlin-test-common", version.ref = "kotlin" }
+kotlin-kotlinTestJs = { group = "org.jetbrains.kotlin", name = "kotlin-test-js", version.ref = "kotlin" }
+kotlin-kotlinTestJunit = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit", version.ref = "kotlin" }
+kotlin-kotlinTestAnnotationsCommon = { group = "org.jetbrains.kotlin", name = "kotlin-test-annotations-common", version.ref = "kotlin" }
+kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "1.5.2-native-mt" }
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
+kotlinx-atomicfu = { group = "org.jetbrains.kotlinx", name = "atomicfu", version = "0.16.3" }
+kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version = "0.2.1" }
+
+kotlin-compose-gradle = { group = "org.jetbrains.compose", name = "compose-gradle-plugin", version = "1.0.0-alpha4-build366" }
+mosaic-gradle = { group = "com.jakewharton.mosaic", name = "mosaic-gradle-plugin", version.ref = "mosaic" }
+
+essenty-lifecycle = { group = "com.arkivanov.essenty", name = "lifecycle", version.ref = "essenty" }
+essenty-instanceKeeper = { group = "com.arkivanov.essenty", name = "instance-keeper", version.ref = "essenty" }
+
+decompose-dep = { group = "com.arkivanov.decompose", name = "decompose", version.ref = "decompose" }
+decompose-extensions-compose = { group = "com.arkivanov.decompose", name = "extensions-compose-jetbrains", version.ref = "decompose" }
+
+mviKotlin-dep = { group = "com.arkivanov.mvikotlin", name = "mvikotlin", version.ref = "mviKotlin" }
+mviKotlin-rx = { group = "com.arkivanov.mvikotlin", name = "rx", version.ref = "mviKotlin" }
+mviKotlin-main = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-main", version.ref = "mviKotlin" }
+mviKotlin-coroutines = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-extensions-coroutines", version.ref = "mviKotlin" }
+mviKotlin-keepers = { group = "com.arkivanov.mvikotlin", name = "keepers", version.ref = "mviKotlin" }
+mviKotlin-logging = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-logging", version.ref = "mviKotlin" }
+mviKotlin-timetravel = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-timetravel", version.ref = "mviKotlin" }
+mviKotlin-extensions-reaktive = { group = "com.arkivanov.mvikotlin", name = "mvikotlin-extensions-reaktive", version.ref = "mviKotlin" }
+
+ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
+ktor-client-json = { group = "io.ktor", name = "ktor-client-json", version.ref = "ktor" }
+ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
+ktor-client-serialization = { group = "io.ktor", name = "ktor-client-serialization", version.ref = "ktor" }
+ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" }
+ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
+ktor-client-curl = { group = "io.ktor", name = "ktor-client-curl", version.ref = "ktor" }
+ktor-client-apache = { group = "io.ktor", name = "ktor-client-apache", version.ref = "ktor" }
+ktor-client-ios = { group = "io.ktor", name = "ktor-client-ios", version.ref = "ktor" }
+ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
+ktor-client-js = { group = "io.ktor", name = "ktor-client-js", version.ref = "ktor" }
+slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
+
+i18n4k-core = { group = "de.comahe.i18n4k", name = "i18n4k-core", version.ref = "i18n4k" }
+i18n4k-gradle-plugin = { group = "de.comahe.i18n4k", name = "i18n4k-gradle-plugin", version.ref = "i18n4k" }
+
+youtube-downloader = { group = "io.github.shabinder", name = "youtube-api-dl", version = "1.3" }
+fuzzy-wuzzy = { group = "io.github.shabinder", name = "fuzzywuzzy", version = "1.1" }
+mp3agic = { group = "com.mpatric", name = "mp3agic", version = "0.9.0" }
+kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" }
+storage-chooser = { group = "com.github.shabinder", name = "storage-chooser", version = "2.0.4.45" }
+accompanist-inset = { group = "com.google.accompanist", name = "accompanist-insets", version.ref = "accompanist" }
+android-app-notifier = { group = "com.github.amitbd1508", name = "AppUpdater", version = "4.1.0" }
+moko-parcelize = { group = "dev.icerock.moko", name = "parcelize", version.ref = "mokoParcelize" }
+jaffree = { group = "com.github.kokorin.jaffree", name = "jaffree", version = "2021.08.16" }
+multiplatform-settings = { group = "com.russhwolf", name = "multiplatform-settings-no-arg", version.ref = "multiplatformSettings" }
+
+countly-android = { group = "ly.count.android", name = "sdk", version = "20.11.8" }
+countly-desktop = { group = "ly.count.sdk", name = "java", version = "20.11.0" }
+
+stately-common = { group = "co.touchlab", name = "stately-common", version.ref = "statelyVersion" }
+stately-concurrency = { group = "co.touchlab", name = "stately-concurrency", version.ref = "statelyVersion" }
+stately-isolate = { group = "co.touchlab", name = "stately-isolate", version.ref = "statelyIsoVersion" }
+stately-iso-collections = { group = "co.touchlab", name = "stately-iso-collections", version.ref = "statelyIsoVersion" }
+
+sqldelight-runtime = { group = "com.squareup.sqldelight", name = "runtime", version.ref = "sqlDelight" }
+sqldelight-coroutines-extension = { group = "com.squareup.sqldelight", name = "coroutines-extensions", version.ref = "sqlDelight" }
+sqldelight-gradle-plugin = { group = "com.squareup.sqldelight", name = "gradle-plugin", version.ref = "sqlDelight" }
+sqldelight-driver = { group = "com.squareup.sqldelight", name = "sqlite-driver", version.ref = "sqlDelight" }
+sqldelight-android-driver = { group = "com.squareup.sqldelight", name = "android-driver", version.ref = "sqlDelight" }
+sqldelight-native-driver = { group = "com.squareup.sqldelight", name = "native-driver", version.ref = "sqlDelight" }
+sqlite-jdbc-driver = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqliteJdbcDriver" }
+
+koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" }
+koin-test = { group = "io.insert-koin", name = "koin-test", version.ref = "koin" }
+koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
+koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" }
+
+kotlin-js-wrappers-react = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-react", version = "17.0.2-pre.251-kotlin-1.5.31" }
+kotlin-js-wrappers-reactDom = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-react-dom", version = "17.0.2-pre.251-kotlin-1.5.31" }
+kotlin-js-wrappers-styled = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-styled", version = "5.3.1-pre.250-kotlin-1.5.31" }
+kotlin-js-wrappers-ext = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-extensions", version = "1.0.1-pre.251-kotlin-1.5.31" }
+
+androidx-activity = { group = "androidx.activity", name = "activity-compose", version = "1.3.1" }
+androidx-core = { group = "androidx.core", name = "core-ktx", version = "1.6.0" }
+androidx-palette = { group = "androidx.palette", name = "palette-ktx", version = "1.0.0" }
+androidx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "androidCoroutines" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version = "1.1.2" }
+androidx-expresso = { group = "androidx.test.espresso", name = "espresso-core", version = "3.3.0" }
+androidx-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version = "4.2.2" }
+androidx-lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "androidxLifecycle" }
+androidx-lifecycle-common = { group = "androidx.lifecycle", name = "lifecycle-common-java8", version.ref = "androidxLifecycle" }
+androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
+androidx-desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version = "1.1.5" }
+leak-canary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version = "2.7" }
+junit = { group = "junit", name = "junit", version = "4.13.2" }
+
+ktlint-gradle = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktLint" }
+
+[bundles]
+ktor = ["ktor-client-core","ktor-client-json","ktor-client-auth","ktor-client-logging","ktor-client-serialization"]
+stately = ["stately-common","stately-concurrency","stately-isolate","stately-iso-collections"]
+androidx-lifecycle = ["androidx-lifecycle-service","androidx-lifecycle-common","androidx-lifecycle-runtime"]
+androidx-common = ["androidx-activity","androidx-core"]
+kotlin-test = ["kotlin-kotlinTestCommon","kotlin-kotlinTestAnnotationsCommon"]
+sqldelight = ["sqldelight-runtime","sqldelight-coroutines-extension","sqldelight-driver"]
+mviKotlin = ["mviKotlin-dep","mviKotlin-main","mviKotlin-coroutines","mviKotlin-logging","mviKotlin-timetravel"]
+kotlinCommon = ["kotlin-coroutines", "kotlin-serialization", "kotlinx-serialization-json", "kotlinx-atomicfu"]
+essenty = ["essenty-lifecycle","essenty-instanceKeeper"]
+koin-android = ["koin-androidx-compose","koin-android"]
+kotlin-js-wrappers = ["kotlin-js-wrappers-react","kotlin-js-wrappers-reactDom","kotlin-js-wrappers-styled","kotlin-js-wrappers-ext"]
\ No newline at end of file
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
new file mode 100644
index 00000000..72f011b1
--- /dev/null
+++ b/buildSrc/settings.gradle.kts
@@ -0,0 +1,12 @@
+
+enableFeaturePreview("VERSION_CATALOGS")
+dependencyResolutionManagement {
+ @Suppress("UnstableApiUsage")
+ versionCatalogs {
+ create("deps") {
+ from(files("deps.versions.toml"))
+ }
+ }
+}
+
+rootProject.name = "spotiflyer-build"
diff --git a/buildSrc/src/main/kotlin/compiler-args.gradle.kts b/buildSrc/src/main/kotlin/compiler-args.gradle.kts
index 39907c63..34c5b8f5 100644
--- a/buildSrc/src/main/kotlin/compiler-args.gradle.kts
+++ b/buildSrc/src/main/kotlin/compiler-args.gradle.kts
@@ -6,10 +6,10 @@ kotlin {
sourceSets {
all {
languageSettings.apply {
- useExperimentalAnnotation("kotlin.RequiresOptIn")
- useExperimentalAnnotation("kotlin.Experimental")
- useExperimentalAnnotation("kotlin.time.ExperimentalTime")
- useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
+ optIn("kotlin.RequiresOptIn")
+ optIn("kotlin.Experimental")
+ optIn("kotlin.time.ExperimentalTime")
+ optIn("kotlinx.serialization.ExperimentalSerializationApi")
}
}
}
diff --git a/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts
index 8b324515..34dea94a 100644
--- a/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts
+++ b/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts
@@ -29,36 +29,23 @@ kotlin {
sourceSets {
all {
languageSettings.apply {
- useExperimentalAnnotation("androidx.compose.animation")
+ optIn("androidx.compose.animation")
}
}
named("commonMain") {
dependencies {
- // Decompose
- implementation(Decompose.decompose)
-
- // MVI
- implementation(MVIKotlin.coroutines)
- implementation(MVIKotlin.mvikotlin)
-
implementation(compose.ui)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.animation)
-
- implementation(Extras.kermit)
- implementation("dev.icerock.moko:parcelize:${Versions.mokoParcelize}")
- implementation(JetBrains.Kotlin.coroutines) {
- @Suppress("DEPRECATION")
- isForce = true
- }
+ implementation(Deps.kotlinCoroutines)
+ implementation(Deps.decompose)
}
}
named("androidMain") {
dependencies {
- implementation(Androidx.androidxActivity)
- implementation(Androidx.core)
+ implementation(Deps.androidXCommonBundle)
}
}
named("desktopMain") {
diff --git a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts
index c4a4889f..8f2e8408 100644
--- a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts
+++ b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts
@@ -42,23 +42,24 @@ kotlin {
sourceSets {
named("commonTest") {
dependencies {
- implementation(JetBrains.Kotlin.testCommon)
- implementation(JetBrains.Kotlin.testAnnotationsCommon)
+ implementation(Deps.kotlinTestBundle)
}
}
named("androidTest") {
dependencies {
- implementation(JetBrains.Kotlin.testJunit)
+ implementation(Deps.kotlinJunitTest)
}
}
named("desktopTest") {
dependencies {
- implementation(JetBrains.Kotlin.testJunit)
+ implementation(Deps.kotlinJunitTest)
}
}
named("jsTest") {
- dependencies {}
+ dependencies {
+ implementation(Deps.kotlinJSTest)
+ }
}
}
}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts
index 43462d25..54b92e72 100644
--- a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts
+++ b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts
@@ -25,7 +25,7 @@ plugins {
kotlin {
/*IOS Target Can be only built on Mac*/
- if(HostOS.isMac){
+ if (HostOS.isMac) {
val sdkName: String? = System.getenv("SDK_NAME")
val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos")
if (isiOSDevice) {
@@ -50,45 +50,25 @@ kotlin {
sourceSets {
named("commonMain") {
dependencies {
- // Decompose
- implementation(Decompose.decompose)
-
- // MVI
- implementation(MVIKotlin.coroutines)
- implementation(MVIKotlin.mvikotlin)
-
- // Koin
- implementation(Koin.core)
-
- implementation(Ktor.auth)
- implementation(Ktor.clientJson)
- implementation(Ktor.clientCore)
- implementation(Ktor.clientLogging)
- implementation(Ktor.clientSerialization)
-
- // Extras
- implementation(Extras.kermit)
- implementation(Serialization.json)
- implementation("co.touchlab:stately-common:1.1.7")
- implementation("dev.icerock.moko:parcelize:${Versions.mokoParcelize}")
- implementation(JetBrains.Kotlin.coroutines) {
- @Suppress("DEPRECATION")
- isForce = true
- }
+ implementation(Deps.ktorBundle)
+ implementation(Deps.kotlinxSerialization)
+ implementation(Deps.kotlinCoroutines)
+ implementation(Deps.mviKotlinBundle)
+ implementation(Deps.decompose)
+ implementation(Deps.koinCore)
}
}
named("androidMain") {
dependencies {
- implementation(Androidx.androidxActivity)
- implementation(Androidx.core)
implementation(compose.runtime)
implementation(compose.material)
implementation(compose.foundation)
implementation(compose.materialIconsExtended)
- implementation(Decompose.extensionsCompose)
- implementation(Ktor.clientAndroid)
- implementation(Koin.android)
+ implementation(Deps.androidXCommonBundle)
+ implementation(Deps.decomposeComposeExt)
+ implementation(Deps.ktorClientAndroid)
+ implementation(Deps.koinAndroidBundle)
}
}
@@ -99,27 +79,20 @@ kotlin {
implementation(compose.material)
implementation(compose.desktop.common)
implementation(compose.materialIconsExtended)
- implementation(Decompose.extensionsCompose)
- implementation(Ktor.clientApache)
- implementation(Ktor.slf4j)
+ implementation(Deps.decomposeComposeExt)
+ implementation(Deps.ktorClientApache)
+ implementation(Deps.slf4j)
}
}
named("jsMain") {
dependencies {
- implementation(Ktor.clientJs)
-
- /*with(KotlinJSWrappers) {
- implementation(enforcedPlatform(bom))
- implementation(kotlinReact)
- implementation(kotlinReactDom)
- implementation(kotlinStyled)
- }*/
+ implementation(Deps.ktorClientJS)
}
}
- if(HostOS.isMac){
- named("iosMain"){
+ if (HostOS.isMac) {
+ named("iosMain") {
dependencies {
- implementation(Ktor.clientIos)
+ implementation(Deps.ktorClientIOS)
}
}
}
diff --git a/common/compose/build.gradle.kts b/common/compose/build.gradle.kts
index c47e194a..ed4df484 100644
--- a/common/compose/build.gradle.kts
+++ b/common/compose/build.gradle.kts
@@ -40,7 +40,7 @@ kotlin {
implementation(project(":common:database"))
implementation(project(":common:data-models"))
implementation(project(":common:dependency-injection"))
- implementation(Decompose.extensionsCompose)
+ implementation(deps.decompose.extensions.compose)
}
}
}
diff --git a/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt b/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt
index e56a099e..a9eb45ce 100644
--- a/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt
+++ b/common/compose/src/androidMain/kotlin/com/shabinder/common/uikit/AndroidImages.kt
@@ -81,6 +81,9 @@ actual fun SpotifyLogo() = getCachedPainter(R.drawable.ic_spotify_logo)
@Composable
actual fun SaavnLogo() = getCachedPainter(R.drawable.ic_jio_saavn_logo)
+@Composable
+actual fun SoundCloudLogo() = getCachedPainter(R.drawable.ic_soundcloud)
+
@Composable
actual fun GaanaLogo() = getCachedPainter(R.drawable.ic_gaana)
diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/ExpectImages.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/ExpectImages.kt
index 6be14625..557bfb49 100644
--- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/ExpectImages.kt
+++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/ExpectImages.kt
@@ -58,6 +58,9 @@ expect fun SpotifyLogo(): Painter
@Composable
expect fun SaavnLogo(): Painter
+@Composable
+expect fun SoundCloudLogo(): Painter
+
@Composable
expect fun YoutubeLogo(): Painter
diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerMainUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerMainUi.kt
index 670106fb..eab0877b 100644
--- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerMainUi.kt
+++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerMainUi.kt
@@ -83,12 +83,14 @@ import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
import com.shabinder.common.models.DownloadRecord
import com.shabinder.common.models.Actions
+import com.shabinder.common.models.spotify.Source
import com.shabinder.common.translations.Strings
import com.shabinder.common.uikit.GaanaLogo
import com.shabinder.common.uikit.GithubLogo
import com.shabinder.common.uikit.ImageLoad
import com.shabinder.common.uikit.SaavnLogo
import com.shabinder.common.uikit.ShareImage
+import com.shabinder.common.uikit.SoundCloudLogo
import com.shabinder.common.uikit.SpotifyLogo
import com.shabinder.common.uikit.VerticalScrollbar
import com.shabinder.common.uikit.YoutubeLogo
@@ -319,6 +321,17 @@ fun AboutColumn(
)
)
}
+ Spacer(modifier = Modifier.padding(top = 8.dp))
+ Row(horizontalArrangement = Arrangement.Center, modifier = modifier.fillMaxWidth()) {
+ Icon(
+ SoundCloudLogo(),
+ "${Strings.open()} Sound Cloud",
+ tint = Color.Unspecified,
+ modifier = Modifier.clip(SpotiFlyerShapes.medium).clickable(
+ onClick = { Actions.instance.openPlatform("com.soundcloud.android", "https://soundcloud.com/") }
+ )
+ )
+ }
}
}
Spacer(modifier = Modifier.padding(top = 8.dp))
diff --git a/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt b/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt
index 2c4c1a91..125a1763 100644
--- a/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt
+++ b/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopImages.kt
@@ -21,15 +21,19 @@ package com.shabinder.common.uikit
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.loadXmlImageVector
-import androidx.compose.ui.res.vectorXmlResource
+import androidx.compose.ui.res.useResource
+import org.xml.sax.InputSource
@Composable
-internal actual fun imageVectorResource(id: T): ImageVector =
- vectorXmlResource(id as String)
+internal actual fun imageVectorResource(id: T): ImageVector {
+ val density = LocalDensity.current
+ return useResource(id as String) {
+ loadXmlImageVector(InputSource(it), density)
+ }
+}
@Composable
actual fun DownloadImageTick() {
@@ -82,6 +86,10 @@ actual fun SpotifyLogo() =
actual fun SaavnLogo() =
getCachedPainter("drawable/ic_jio_saavn_logo.xml")
+@Composable
+actual fun SoundCloudLogo() =
+ getCachedPainter("drawable/ic_soundcloud.xml")
+
@Composable
actual fun YoutubeLogo() =
getCachedPainter("drawable/ic_youtube.xml")
diff --git a/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopScrollBar.kt b/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopScrollBar.kt
index 9088ac4b..aad2af95 100644
--- a/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopScrollBar.kt
+++ b/common/compose/src/desktopMain/kotlin/com/shabinder/common/uikit/DesktopScrollBar.kt
@@ -12,7 +12,7 @@ import androidx.compose.ui.unit.dp
actual val MARGIN_SCROLLBAR: Dp = 8.dp
-actual typealias ScrollbarAdapter = androidx.compose.foundation.ScrollbarAdapter
+actual typealias ScrollbarAdapter = ScrollbarAdapter
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -23,8 +23,6 @@ actual fun rememberScrollbarAdapter(
): ScrollbarAdapter =
androidx.compose.foundation.rememberScrollbarAdapter(
scrollState = scrollState,
- itemCount = itemCount,
- averageItemSize = averageItemSize
)
@Composable
diff --git a/common/core-components/build.gradle.kts b/common/core-components/build.gradle.kts
index 9a618a93..92592eda 100644
--- a/common/core-components/build.gradle.kts
+++ b/common/core-components/build.gradle.kts
@@ -10,29 +10,37 @@ kotlin {
dependencies {
implementation(project(":common:data-models"))
implementation(project(":common:database"))
- api("org.jetbrains.kotlinx:atomicfu:0.16.2")
- api(MultiPlatformSettings.dep)
- implementation(MVIKotlin.rx)
+ with(deps) {
+ api(multiplatform.settings)
+ api(kotlinx.atomicfu)
+ implementation(mviKotlin.rx)
+ implementation(decompose.dep)
+ }
}
}
androidMain {
dependencies {
- implementation(Extras.mp3agic)
- implementation(Extras.Android.countly)
+ with(deps) {
+ implementation(mp3agic)
+ implementation(countly.android)
+ }
implementation(project(":ffmpeg:android-ffmpeg"))
}
}
desktopMain {
dependencies {
- implementation(Extras.mp3agic)
- implementation(Extras.Desktop.countly)
- implementation("com.github.kokorin.jaffree:jaffree:2021.08.16")
+ with(deps) {
+ implementation(mp3agic)
+ implementation(countly.desktop)
+ implementation(jaffree)
+ }
}
}
jsMain {
dependencies {
implementation(npm("browser-id3-writer", "4.4.0"))
implementation(npm("file-saver", "2.0.4"))
+ implementation(deps.kotlin.js.wrappers.ext)
}
}
}
diff --git a/common/core-components/src/androidMain/kotlin/com/shabinder/common/core_components/file_manager/AndroidFileManager.kt b/common/core-components/src/androidMain/kotlin/com/shabinder/common/core_components/file_manager/AndroidFileManager.kt
index 43d87554..1b30fc38 100644
--- a/common/core-components/src/androidMain/kotlin/com/shabinder/common/core_components/file_manager/AndroidFileManager.kt
+++ b/common/core-components/src/androidMain/kotlin/com/shabinder/common/core_components/file_manager/AndroidFileManager.kt
@@ -117,7 +117,7 @@ class AndroidFileManager(
try {
// Add Mp3 Tags and Add to Library
- if(trackDetails.audioFormat != AudioFormat.MP3)
+ if (trackDetails.audioFormat != AudioFormat.MP3)
throw InvalidDataException("Audio Format is ${trackDetails.audioFormat}, Needs Conversion!")
Mp3File(File(songFile.absolutePath))
@@ -166,7 +166,7 @@ class AndroidFileManager(
override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture =
withContext(dispatcherIO) {
- val cachePath = imageCacheDir() + getNameURL(url)
+ val cachePath = getImageCachePath(url)
Picture(
image = (loadCachedImage(cachePath, reqWidth, reqHeight) ?: freshImage(
url,
@@ -214,7 +214,7 @@ class AndroidFileManager(
// Decode and Cache Full Sized Image in Background
cacheImage(
BitmapFactory.decodeByteArray(input, 0, input.size),
- imageCacheDir() + getNameURL(url)
+ getImageCachePath(url)
)
}
bitmap // return Memory Efficient Bitmap
diff --git a/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/file_manager/FileManager.kt b/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/file_manager/FileManager.kt
index 6aedf2eb..d27662e9 100644
--- a/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/file_manager/FileManager.kt
+++ b/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/file_manager/FileManager.kt
@@ -25,10 +25,14 @@ import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.utils.removeIllegalChars
+import com.shabinder.common.utils.requireNotNull
import com.shabinder.database.Database
-import io.ktor.client.request.*
-import io.ktor.client.statement.*
-import io.ktor.http.*
+import io.ktor.client.HttpClient
+import io.ktor.client.request.HttpRequestBuilder
+import io.ktor.client.request.get
+import io.ktor.client.statement.HttpStatement
+import io.ktor.http.contentLength
+import io.ktor.http.isSuccess
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
@@ -80,12 +84,13 @@ fun FileManager.createDirectories() {
if (!defaultDir().contains("null${fileSeparator()}SpotiFlyer")) {
createDirectory(defaultDir())
createDirectory(imageCacheDir())
- createDirectory(defaultDir() + "Tracks/")
- createDirectory(defaultDir() + "Albums/")
- createDirectory(defaultDir() + "Playlists/")
- createDirectory(defaultDir() + "YT_Downloads/")
+ createDirectory(defaultDir() + "Tracks" + fileSeparator())
+ createDirectory(defaultDir() + "Albums" + fileSeparator())
+ createDirectory(defaultDir() + "Playlists" + fileSeparator())
+ createDirectory(defaultDir() + "YT_Downloads" + fileSeparator())
}
- } catch (ignored: Exception) { }
+ } catch (ignored: Exception) {
+ }
}
fun FileManager.finalOutputDir(
@@ -100,24 +105,50 @@ fun FileManager.finalOutputDir(
removeIllegalChars(subFolder) + this.fileSeparator()
} +
removeIllegalChars(itemName) + extension
-/*DIR Specific Operation End*/
-fun getNameURL(url: String): String {
- return url.substring(url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1, url.length)
- .replace('/', '_')
+fun FileManager.getImageCachePath(
+ url: String
+): String = imageCacheDir() + getNameFromURL(url, isImage = true)
+
+/*DIR Specific Operation End*/
+private fun getNameFromURL(url: String, isImage: Boolean = false): String {
+ val startIndex = url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1
+
+ var fileName = if (startIndex != -1)
+ url.substring(startIndex).replace('/', '_')
+ else url.substringAfterLast("/")
+
+ // Generify File Extensions
+ if (isImage) {
+ if (fileName.length - fileName.lastIndexOf(".") > 5) {
+ fileName += ".jpeg"
+ } else {
+ if (fileName.endsWith(".jpg"))
+ fileName = fileName.substringBeforeLast(".") + ".jpeg"
+ }
+ }
+
+ return fileName
}
-suspend fun downloadFile(url: String): Flow {
+suspend fun HttpClient.downloadFile(url: String) = downloadFile(url, this)
+
+suspend fun downloadFile(url: String, client: HttpClient? = null): Flow {
return flow {
- val client = createHttpClient()
- val response = client.get(url).execute()
- val data = ByteArray(response.contentLength()!!.toInt())
+ val httpClient = client ?: createHttpClient()
+ val response = httpClient.get(url).execute()
+ // Not all requests return Content Length
+ val data = kotlin.runCatching {
+ ByteArray(response.contentLength().requireNotNull().toInt())
+ }.getOrNull() ?: byteArrayOf()
var offset = 0
do {
// Set Length optimally, after how many kb you want a progress update, now it 0.25mb
val currentRead = response.content.readAvailable(data, offset, 2_50_000)
offset += currentRead
- val progress = (offset * 100f / data.size).roundToInt()
+ val progress = data.size.takeIf { it != 0 }?.let { fileSize ->
+ (offset * 100f / fileSize).roundToInt()
+ } ?: 0
emit(DownloadResult.Progress(progress))
} while (currentRead > 0)
if (response.status.isSuccess()) {
@@ -125,7 +156,10 @@ suspend fun downloadFile(url: String): Flow {
} else {
emit(DownloadResult.Error("File not downloaded"))
}
- client.close()
+
+ // Close Client if We Created One
+ if (client == null)
+ httpClient.close()
}.catch { e ->
e.printStackTrace()
emit(DownloadResult.Error(e.message ?: "File not downloaded"))
diff --git a/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt b/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt
index 1b7eb918..ec3e78fd 100644
--- a/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt
+++ b/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt
@@ -8,6 +8,8 @@ import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import io.ktor.client.request.*
+import io.ktor.client.statement.HttpResponse
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.native.concurrent.SharedImmutable
@@ -23,6 +25,18 @@ suspend fun isInternetAccessible(): Boolean {
}
}
+// If Fails returns Input Url
+suspend inline fun HttpClient.getFinalUrl(
+ url: String,
+ crossinline block: HttpRequestBuilder.() -> Unit = {}
+): String {
+ return withContext(dispatcherIO) {
+ runCatching {
+ get(url,block).call.request.url.toString()
+ }.getOrNull() ?: url
+ }
+}
+
fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
// https://github.com/Kotlin/kotlinx.serialization/issues/1450
install(JsonFeature) {
diff --git a/common/core-components/src/desktopMain/kotlin/com.shabinder.common.core_components/file_manager/DesktopFileManager.kt b/common/core-components/src/desktopMain/kotlin/com.shabinder.common.core_components/file_manager/DesktopFileManager.kt
index 0f7b11a9..6ef248dd 100644
--- a/common/core-components/src/desktopMain/kotlin/com.shabinder.common.core_components/file_manager/DesktopFileManager.kt
+++ b/common/core-components/src/desktopMain/kotlin/com.shabinder.common.core_components/file_manager/DesktopFileManager.kt
@@ -30,17 +30,20 @@ import com.shabinder.common.core_components.removeAllTags
import com.shabinder.common.core_components.setId3v1Tags
import com.shabinder.common.core_components.setId3v2TagsAndSaveFile
import com.shabinder.common.database.SpotiFlyerDatabase
+import com.shabinder.common.models.Actions
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.models.event.coroutines.SuspendableEvent
-import com.shabinder.common.models.event.coroutines.failure
import com.shabinder.common.models.event.coroutines.map
-import com.shabinder.common.models.Actions
import com.shabinder.database.Database
-import kotlinx.coroutines.*
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow
-import org.jetbrains.skija.Image
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.jetbrains.skia.Image
import org.koin.dsl.bind
import org.koin.dsl.module
import java.awt.image.BufferedImage
@@ -104,9 +107,11 @@ class DesktopFileManager(
override suspend fun cacheImage(image: Any, path: String): Unit = withContext(dispatcherIO) {
try {
+ val file = File(path)
+ if(!file.parentFile.exists()) createDirectories()
(image as? BufferedImage)?.let {
- ImageIO.write(it, "jpeg", File(path))
- }
+ ImageIO.write(it, "jpeg", file)
+ }
} catch (e: IOException) {
e.printStackTrace()
}
@@ -165,7 +170,7 @@ class DesktopFileManager(
}
SuspendableEvent.success(trackDetails.outputFilePath)
} catch (e: Throwable) {
- if(e is JaffreeException) Actions.instance.showPopUpMessage("No FFmpeg found at path.")
+ if (e is JaffreeException) Actions.instance.showPopUpMessage("No FFmpeg found at path.")
if (songFile.exists()) songFile.delete()
logger.e { "${songFile.absolutePath} could not be created" }
SuspendableEvent.error(e)
@@ -175,8 +180,7 @@ class DesktopFileManager(
override fun addToLibrary(path: String) {}
override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture {
- val cachePath = imageCacheDir() + getNameURL(url)
- var picture: ImageBitmap? = loadCachedImage(cachePath, reqWidth, reqHeight)
+ var picture: ImageBitmap? = loadCachedImage(getImageCachePath(url), reqWidth, reqHeight)
if (picture == null) picture = freshImage(url, reqWidth, reqHeight)
return Picture(image = picture)
}
@@ -205,7 +209,7 @@ class DesktopFileManager(
if (result != null) {
GlobalScope.launch(Dispatchers.IO) { // TODO Refactor
- cacheImage(result, imageCacheDir() + getNameURL(url))
+ cacheImage(result, getImageCachePath(url))
}
result.toImageBitmap()
} else null
diff --git a/common/data-models/build.gradle.kts b/common/data-models/build.gradle.kts
index 9b5d0f70..0db019de 100644
--- a/common/data-models/build.gradle.kts
+++ b/common/data-models/build.gradle.kts
@@ -25,9 +25,6 @@ plugins {
id("de.comahe.i18n4k")
}
-val statelyVersion = "1.1.7"
-val statelyIsoVersion = "1.1.7-a1"
-
i18n4k {
inputDirectory = "../../translations"
packageName = "com.shabinder.common.translations"
@@ -50,11 +47,13 @@ kotlin {
}
commonMain {
dependencies {
- implementation("co.touchlab:stately-concurrency:$statelyVersion")
- implementation("co.touchlab:stately-isolate:$statelyIsoVersion")
- implementation("co.touchlab:stately-iso-collections:$statelyIsoVersion")
- implementation(Extras.youtubeDownloader)
- api(Internationalization.dep)
+ with(deps) {
+ api(bundles.stately)
+ api(i18n4k.core)
+ api(kermit)
+ api(moko.parcelize)
+ implementation(youtube.downloader)
+ }
}
}
}
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt
index 4267ca97..10cc35a0 100644
--- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt
@@ -44,7 +44,7 @@ data class TrackDetails(
var audioQuality: AudioQuality = AudioQuality.KBPS192,
var audioFormat: AudioFormat = AudioFormat.MP4,
var outputFilePath: String, // UriString in Android
- var videoID: String? = null,
+ var videoID: String? = null, // will be used for purposes like Downloadable Link || VideoID etc. based on Provider
) : Parcelable {
val outputMp3Path get() = outputFilePath.substringBeforeLast(".") + ".mp3"
}
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/SpotiFlyerException.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/SpotiFlyerException.kt
index 960baf53..3415020d 100644
--- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/SpotiFlyerException.kt
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/SpotiFlyerException.kt
@@ -12,6 +12,11 @@ sealed class SpotiFlyerException(override val message: String) : Exception(messa
override val message: String = /*${Strings.mp3ConverterBusy()} */"CAUSE:$extraInfo"
) : SpotiFlyerException(message)
+ data class GeoLocationBlocked(
+ val extraInfo: String? = null,
+ override val message: String = "This Content is not Accessible from your Location, try using a VPN! \nCAUSE:$extraInfo"
+ ) : SpotiFlyerException(message)
+
data class UnknownReason(
val exception: Throwable? = null,
override val message: String = Strings.unknownError()
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/saavn/SaavnSong.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/saavn/SaavnSong.kt
index c4cfae65..6f2e7eb2 100644
--- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/saavn/SaavnSong.kt
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/saavn/SaavnSong.kt
@@ -20,7 +20,7 @@ data class SaavnSong @OptIn(ExperimentalSerializationApi::class) constructor(
// val explicit_content: Int = 0,
val has_lyrics: Boolean = false,
val id: String,
- val image: String,
+ val image: String = "",
val label: String? = null,
val label_url: String? = null,
val language: String,
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Badges.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Badges.kt
new file mode 100644
index 00000000..52d0f77d
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Badges.kt
@@ -0,0 +1,13 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Badges(
+ val pro: Boolean = false,
+ @SerialName("pro_unlimited")
+ val proUnlimited: Boolean = false,
+ val verified: Boolean = false
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/CreatorSubscription.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/CreatorSubscription.kt
new file mode 100644
index 00000000..2c0fd45a
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/CreatorSubscription.kt
@@ -0,0 +1,10 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class CreatorSubscription(
+ val product: Product = Product()
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Format.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Format.kt
new file mode 100644
index 00000000..13b054ac
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Format.kt
@@ -0,0 +1,14 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Format(
+ @SerialName("mime_type")
+ val mimeType: String = "",
+ val protocol: String = ""
+) {
+ val isProgressive get() = protocol == "progressive"
+}
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Media.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Media.kt
new file mode 100644
index 00000000..66ba4707
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Media.kt
@@ -0,0 +1,9 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Media(
+ val transcodings: List = emptyList()
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Product.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Product.kt
new file mode 100644
index 00000000..b82c5cf9
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Product.kt
@@ -0,0 +1,10 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Product(
+ val id: String = ""
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/PublisherMetadata.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/PublisherMetadata.kt
new file mode 100644
index 00000000..d4b51b8a
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/PublisherMetadata.kt
@@ -0,0 +1,24 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PublisherMetadata(
+ @SerialName("album_title")
+ val albumTitle: String = "",
+ val artist: String = "",
+ @SerialName("contains_music")
+ val containsMusic: Boolean = false,
+ val id: Int = 0,
+ val isrc: String = "",
+ val publisher: String = "",
+ @SerialName("release_title")
+ val releaseTitle: String = "",
+ @SerialName("upc_or_ean")
+ val upcOrEan: String = "",
+ val urn: String = "",
+ @SerialName("writer_composer")
+ val writerComposer: String = ""
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Transcoding.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Transcoding.kt
new file mode 100644
index 00000000..0bd313de
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Transcoding.kt
@@ -0,0 +1,22 @@
+package com.shabinder.common.models.soundcloud
+
+
+import com.shabinder.common.models.AudioFormat
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Transcoding(
+ val duration: Int = 0,
+ val format: Format = Format(),
+ val preset: String = "",
+ val quality: String = "", //sq == 128kbps //hq == 256kbps
+ val snipped: Boolean = false,
+ val url: String = ""
+) {
+ val audioFormat: AudioFormat = when {
+ preset.contains("mp3") -> AudioFormat.MP3
+ preset.contains("aac") || preset.contains("m4a") -> AudioFormat.MP4
+ preset.contains("flac") -> AudioFormat.FLAC
+ else -> AudioFormat.UNKNOWN
+ }
+}
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/User.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/User.kt
new file mode 100644
index 00000000..4ea07493
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/User.kt
@@ -0,0 +1,38 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class User(
+ @SerialName("avatar_url")
+ val avatarUrl: String = "",
+ val badges: Badges = Badges(),
+ val city: String = "",
+ @SerialName("country_code")
+ val countryCode: String = "",
+ @SerialName("first_name")
+ val firstName: String = "",
+ @SerialName("followers_count")
+ val followersCount: Int = 0,
+ @SerialName("full_name")
+ val fullName: String = "",
+ val id: Int = 0,
+ val kind: String = "",
+ @SerialName("last_modified")
+ val lastModified: String = "",
+ @SerialName("last_name")
+ val lastName: String = "",
+ val permalink: String = "",
+ @SerialName("permalink_url")
+ val permalinkUrl: String = "",
+ @SerialName("station_permalink")
+ val stationPermalink: String = "",
+ @SerialName("station_urn")
+ val stationUrn: String = "",
+ val uri: String = "",
+ val urn: String = "",
+ val username: String = "",
+ val verified: Boolean = false
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Visual.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Visual.kt
new file mode 100644
index 00000000..9e28aede
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Visual.kt
@@ -0,0 +1,14 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Visual(
+ @SerialName("entry_time")
+ val entryTime: Int = 0,
+ val urn: String = "",
+ @SerialName("visual_url")
+ val visualUrl: String = ""
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Visuals.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Visuals.kt
new file mode 100644
index 00000000..57ef84d2
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Visuals.kt
@@ -0,0 +1,13 @@
+package com.shabinder.common.models.soundcloud
+
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Visuals(
+ val enabled: Boolean = false,
+ //val tracking: Any = Any(),
+ val urn: String = "",
+ val visuals: List = listOf()
+)
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt
new file mode 100644
index 00000000..ddc9c128
--- /dev/null
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt
@@ -0,0 +1,166 @@
+package com.shabinder.common.models.soundcloud.resolvemodel
+
+import com.shabinder.common.models.AudioFormat
+import com.shabinder.common.models.soundcloud.Media
+import com.shabinder.common.models.soundcloud.PublisherMetadata
+import com.shabinder.common.models.soundcloud.User
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonClassDiscriminator
+
+@Serializable
+@JsonClassDiscriminator("kind")
+sealed class SoundCloudResolveResponseBase {
+ abstract val kind: String
+
+ @SerialName("playlist")
+ @Serializable
+ data class SoundCloudResolveResponsePlaylist(
+ @SerialName("artwork_url")
+ val artworkUrl: String = "",
+ @SerialName("calculated_artwork_url")
+ val calculatedArtworkUrl: String = "", //t500x500, t120x120 // "https://i1.sndcdn.com/artworks-pjsabv9w0EXW3lBJ-nvjDYg-large.jpg" // https://i1.sndcdn.com/artworks-pjsabv9w0EXW3lBJ-nvjDYg-t500x500.jpg
+ @SerialName("created_at")
+ val createdAt: String = "",
+ val description: String = "",
+ @SerialName("display_date")
+ val displayDate: String = "",
+ val duration: Int = 0,
+ override val kind: String = "",
+ @SerialName("embeddable_by")
+ val embeddableBy: String = "",
+ val genre: String = "",
+ val id: Int = 0,
+ @SerialName("is_album")
+ val isAlbum: Boolean = false,
+ @SerialName("label_name")
+ val labelName: String = "",
+ @SerialName("last_modified")
+ val lastModified: String = "",
+ val license: String = "",
+ @SerialName("likes_count")
+ val likesCount: Int = 0,
+ @SerialName("managed_by_feeds")
+ val managedByFeeds: Boolean = false,
+ val permalink: String = "",
+ @SerialName("permalink_url")
+ val permalinkUrl: String = "",
+ val `public`: Boolean = false,
+ @SerialName("published_at")
+ val publishedAt: String = "",
+ @SerialName("purchase_title")
+ val purchaseTitle: String = "",
+ @SerialName("purchase_url")
+ val purchaseUrl: String = "",
+ @SerialName("release_date")
+ val releaseDate: String = "",
+ @SerialName("reposts_count")
+ val repostsCount: Int = 0,
+ @SerialName("secret_token")
+ val secretToken: String = "",
+ @SerialName("set_type")
+ val setType: String = "",
+ val sharing: String = "",
+ @SerialName("tag_list")
+ val tagList: String = "",
+ val title: String = "", //"Top 50: Hip-hop & Rap"
+ @SerialName("track_count")
+ val trackCount: Int = 0,
+ var tracks: List = emptyList(),
+ val uri: String = "",
+ val user: User = User(),
+ @SerialName("user_id")
+ val userId: Int = 0
+ ) : SoundCloudResolveResponseBase()
+
+
+ @SerialName("track")
+ @Serializable
+ data class SoundCloudResolveResponseTrack(
+ @SerialName("artwork_url")
+ val artworkUrl: String = "",
+ val caption: String = "",
+ @SerialName("comment_count")
+ val commentCount: Int = 0,
+ val commentable: Boolean = false,
+ @SerialName("created_at")
+ val createdAt: String = "",
+ val description: String = "",
+ @SerialName("display_date")
+ val displayDate: String = "",
+ @SerialName("download_count")
+ val downloadCount: Int = 0,
+ val downloadable: Boolean = false,
+ val duration: Int = 0,
+ @SerialName("embeddable_by")
+ val embeddableBy: String = "",
+ @SerialName("full_duration")
+ val fullDuration: Int = 0,
+ val genre: String = "",
+ @SerialName("has_downloads_left")
+ val hasDownloadsLeft: Boolean = false,
+ val id: Int = 0,
+ override val kind: String = "",
+ @SerialName("label_name")
+ val labelName: String = "",
+ @SerialName("last_modified")
+ val lastModified: String = "",
+ val license: String = "",
+ @SerialName("likes_count")
+ val likesCount: Int = 0,
+ val media: Media = Media(),
+ @SerialName("monetization_model")
+ val monetizationModel: String = "",
+ val permalink: String = "",
+ @SerialName("permalink_url")
+ val permalinkUrl: String = "",
+ @SerialName("playback_count")
+ val playbackCount: Int = 0,
+ val policy: String = "",
+ val `public`: Boolean = false,
+ @SerialName("publisher_metadata")
+ val publisherMetadata: PublisherMetadata = PublisherMetadata(),
+ @SerialName("purchase_title")
+ val purchaseTitle: String = "",
+ @SerialName("purchase_url")
+ val purchaseUrl: String = "",
+ @SerialName("release_date")
+ val releaseDate: String = "",
+ @SerialName("reposts_count")
+ val repostsCount: Int = 0,
+ @SerialName("secret_token")
+ val secretToken: String = "",
+ val sharing: String = "",
+ val state: String = "",
+ @SerialName("station_permalink")
+ val stationPermalink: String = "",
+ @SerialName("station_urn")
+ val stationUrn: String = "",
+ val streamable: Boolean = false,
+ @SerialName("tag_list")
+ val tagList: String = "",
+ val title: String = "",
+ @SerialName("track_authorization")
+ val trackAuthorization: String = "",
+ @SerialName("track_format")
+ val trackFormat: String = "",
+ val uri: String = "",
+ val urn: String = "",
+ val user: User = User(),
+ @SerialName("user_id")
+ val userId: Int = 0,
+ val visuals: String = "",
+ @SerialName("waveform_url")
+ val waveformUrl: String = ""
+ ) : SoundCloudResolveResponseBase() {
+ fun getDownloadableLink(): Pair? {
+ return (media.transcodings.firstOrNull {
+ it.quality == "hq" && (it.format.isProgressive || it.url.contains("progressive"))
+ } ?: media.transcodings.firstOrNull {
+ it.quality == "sq" && (it.format.isProgressive || it.url.contains("progressive"))
+ })?.let {
+ it.url to it.audioFormat
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt
index fbaa3f7d..b20fe1d6 100644
--- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt
@@ -20,5 +20,6 @@ enum class Source {
Spotify,
YouTube,
Gaana,
- JioSaavn
+ JioSaavn,
+ SoundCloud
}
diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/utils/Utils.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/utils/Utils.kt
index 1bbad05f..cb685184 100644
--- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/utils/Utils.kt
+++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/utils/Utils.kt
@@ -8,6 +8,7 @@ val globalJson by lazy {
Json {
isLenient = true
ignoreUnknownKeys = true
+ coerceInputValues = true
}
}
diff --git a/common/data-models/src/main/res/drawable/ic_soundcloud.xml b/common/data-models/src/main/res/drawable/ic_soundcloud.xml
new file mode 100644
index 00000000..faaf1945
--- /dev/null
+++ b/common/data-models/src/main/res/drawable/ic_soundcloud.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/common/data-models/src/main/res/drawable/spotiflyer.png b/common/data-models/src/main/res/drawable/spotiflyer.png
new file mode 100644
index 00000000..74aebd66
Binary files /dev/null and b/common/data-models/src/main/res/drawable/spotiflyer.png differ
diff --git a/common/database/build.gradle.kts b/common/database/build.gradle.kts
index 45fc19bb..c4d095d1 100644
--- a/common/database/build.gradle.kts
+++ b/common/database/build.gradle.kts
@@ -34,31 +34,32 @@ kotlin {
implementation(project(":common:data-models"))
// SQL Delight
- implementation(SqlDelight.runtime)
- implementation(SqlDelight.coroutineExtensions)
-
- // koin
- implementation(Koin.test)
+ with(deps.sqldelight) {
+ implementation(runtime)
+ api(coroutines.extension)
+ }
}
}
androidMain {
dependencies {
- implementation(SqlDelight.androidDriver)
+ implementation(deps.sqldelight.android.driver)
}
}
desktopMain {
dependencies {
- implementation(SqlDelight.sqliteDriver)
- implementation(SqlDelight.jdbcDriver)
+ with(deps) {
+ implementation(sqldelight.driver)
+ implementation(sqlite.jdbc.driver)
+ }
}
}
if (HostOS.isMac) {
val iosMain by getting {
dependencies {
- implementation(SqlDelight.nativeDriver)
+ implementation(deps.sqldelight.native.driver)
}
}
}
diff --git a/common/dependency-injection/build.gradle.kts b/common/dependency-injection/build.gradle.kts
index 3ae71372..0a7234a2 100644
--- a/common/dependency-injection/build.gradle.kts
+++ b/common/dependency-injection/build.gradle.kts
@@ -14,8 +14,6 @@
* * along with this program. If not, see .
*/
-import org.jetbrains.compose.compose
-
plugins {
id("android-setup")
id("multiplatform-setup")
diff --git a/common/list/build.gradle.kts b/common/list/build.gradle.kts
index 0e9910be..60894115 100644
--- a/common/list/build.gradle.kts
+++ b/common/list/build.gradle.kts
@@ -30,7 +30,6 @@ kotlin {
implementation(project(":common:database"))
implementation(project(":common:providers"))
implementation(project(":common:core-components"))
- implementation(SqlDelight.coroutineExtensions)
}
}
}
diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt
index a6162851..a8667fc3 100644
--- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt
+++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt
@@ -174,12 +174,10 @@ internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependen
}
}
- private fun List.updateTracksStatuses(map: HashMap): List {
- val titleList = this.map { it.title }
- val updatedList = mutableListOf().also { it.addAll(this) }
-
- for (newTrack in map) {
- titleList.indexOf(newTrack.key).let { position ->
+ private fun List.updateTracksStatuses(map: Map): List {
+ val updatedList = ArrayList(this)
+ LinkedHashMap(map).forEach { newTrack ->
+ indexOfFirst { it.title == newTrack.key }.let { position ->
if (position != -1) {
updatedList.getOrNull(position)?.copy(
downloaded = newTrack.value,
diff --git a/common/main/build.gradle.kts b/common/main/build.gradle.kts
index 0e9910be..60894115 100644
--- a/common/main/build.gradle.kts
+++ b/common/main/build.gradle.kts
@@ -30,7 +30,6 @@ kotlin {
implementation(project(":common:database"))
implementation(project(":common:providers"))
implementation(project(":common:core-components"))
- implementation(SqlDelight.coroutineExtensions)
}
}
}
diff --git a/common/preference/build.gradle.kts b/common/preference/build.gradle.kts
index c9bec637..67bbc40b 100644
--- a/common/preference/build.gradle.kts
+++ b/common/preference/build.gradle.kts
@@ -30,7 +30,6 @@ kotlin {
implementation(project(":common:database"))
implementation(project(":common:core-components"))
implementation(project(":common:providers"))
- implementation(SqlDelight.coroutineExtensions)
}
}
}
diff --git a/common/providers/build.gradle.kts b/common/providers/build.gradle.kts
index b4309fba..79e289f8 100644
--- a/common/providers/build.gradle.kts
+++ b/common/providers/build.gradle.kts
@@ -12,23 +12,25 @@ kotlin {
sourceSets {
commonMain {
dependencies {
- implementation(project(":common:data-models"))
- implementation(project(":common:database"))
- implementation(project(":common:core-components"))
- implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.1")
- implementation(Extras.youtubeDownloader)
- implementation(Extras.fuzzyWuzzy)
+ with(deps) {
+ implementation(project(":common:data-models"))
+ implementation(project(":common:database"))
+ implementation(project(":common:core-components"))
+ implementation(youtube.downloader)
+ implementation(fuzzy.wuzzy)
+ implementation(kotlinx.datetime)
+ }
}
}
androidMain {
dependencies {
- implementation(Extras.mp3agic)
+ implementation(deps.mp3agic)
}
}
desktopMain {
dependencies {
- implementation(Extras.mp3agic)
- implementation("com.github.kokorin.jaffree:jaffree:2021.08.16")
+ implementation(deps.mp3agic)
+ implementation(deps.jaffree)
}
}
jsMain {
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt
index c2bceb41..87658110 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt
@@ -20,7 +20,12 @@ import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.preference_manager.PreferenceManager
import com.shabinder.common.database.DownloadRecordDatabaseQueries
-import com.shabinder.common.models.*
+import com.shabinder.common.models.AudioFormat
+import com.shabinder.common.models.AudioQuality
+import com.shabinder.common.models.PlatformQueryResult
+import com.shabinder.common.models.SpotiFlyerException
+import com.shabinder.common.models.TrackDetails
+import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.flatMapError
import com.shabinder.common.models.event.coroutines.onSuccess
@@ -28,9 +33,9 @@ import com.shabinder.common.models.event.coroutines.success
import com.shabinder.common.models.spotify.Source
import com.shabinder.common.providers.gaana.GaanaProvider
import com.shabinder.common.providers.saavn.SaavnProvider
+import com.shabinder.common.providers.sound_cloud.SoundCloudProvider
import com.shabinder.common.providers.spotify.SpotifyProvider
import com.shabinder.common.providers.youtube.YoutubeProvider
-import com.shabinder.common.providers.youtube.get
import com.shabinder.common.providers.youtube_music.YoutubeMusic
import com.shabinder.common.providers.youtube_to_mp3.requests.YoutubeMp3
import com.shabinder.common.utils.appendPadded
@@ -45,6 +50,7 @@ class FetchPlatformQueryResult(
private val spotifyProvider: SpotifyProvider,
private val youtubeProvider: YoutubeProvider,
private val saavnProvider: SaavnProvider,
+ private val soundCloudProvider: SoundCloudProvider,
private val youtubeMusic: YoutubeMusic,
private val youtubeMp3: YoutubeMp3,
val fileManager: FileManager,
@@ -66,7 +72,7 @@ class FetchPlatformQueryResult(
link.contains("youtube.com", true) || link.contains("youtu.be", true) ->
youtubeProvider.query(link)
- // Jio Saavn
+ // JioSaavn
link.contains("saavn", true) ->
saavnProvider.query(link)
@@ -74,6 +80,10 @@ class FetchPlatformQueryResult(
link.contains("gaana", true) ->
gaanaProvider.query(link)
+ // SoundCloud
+ link.contains("soundcloud", true) ->
+ soundCloudProvider.query(link)
+
else -> {
SuspendableEvent.error(SpotiFlyerException.LinkInvalid(link))
}
@@ -122,7 +132,7 @@ class FetchPlatformQueryResult(
ytMp3Link.component2()?.stackTraceToString()
?: "couldn't fetch link for ${track.videoID} ,trying manual extraction"
)
- appendLine("Trying Local Extraction")
+ //appendLine("Trying Local Extraction")
null
} else {
audioFormat = AudioFormat.MP3
@@ -130,6 +140,20 @@ class FetchPlatformQueryResult(
}
}
}
+ Source.SoundCloud -> {
+ audioFormat = track.audioFormat
+ soundCloudProvider.getDownloadURL(track).let {
+ if (it is SuspendableEvent.Failure || it.component1().isNullOrEmpty()) {
+ appendPadded(
+ "SoundCloud Provider Failed for ${track.title}:",
+ it.component2()?.stackTraceToString()
+ ?: "couldn't fetch link for ${track.trackUrl}"
+ )
+ null
+ } else
+ it.component1()
+ }
+ }
else -> {
appendPadded(
"Invalid Arguments",
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt
index 44c67ea3..18e250a2 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt
@@ -2,6 +2,7 @@ package com.shabinder.common.providers
import com.shabinder.common.providers.gaana.GaanaProvider
import com.shabinder.common.providers.saavn.SaavnProvider
+import com.shabinder.common.providers.sound_cloud.SoundCloudProvider
import com.shabinder.common.providers.spotify.SpotifyProvider
import com.shabinder.common.providers.spotify.token_store.TokenStore
import com.shabinder.common.providers.youtube.YoutubeProvider
@@ -16,7 +17,8 @@ fun providersModule(enableNetworkLogs: Boolean) = module {
single { GaanaProvider(get(), get(), get()) }
single { SaavnProvider(get(), get(), get()) }
single { YoutubeProvider(get(), get(), get()) }
+ single { SoundCloudProvider(get(), get(), get()) }
single { YoutubeMp3(get(), get()) }
single { YoutubeMusic(get(), get(), get(), get(), get()) }
- single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
+ single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
}
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/GaanaProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/GaanaProvider.kt
index 622524cb..438365ec 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/GaanaProvider.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/GaanaProvider.kt
@@ -19,6 +19,7 @@ package com.shabinder.common.providers.gaana
import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir
+import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException
@@ -124,8 +125,7 @@ class GaanaProvider(
title = it.track_title,
artists = it.artist.map { artist -> artist?.name.toString() },
durationSec = it.duration,
- albumArtPath = fileManager.imageCacheDir() + (it.artworkLink.substringBeforeLast('/')
- .substringAfterLast('/')) + ".jpeg",
+ albumArtPath = fileManager.getImageCachePath(it.artworkLink),
albumName = it.album_title,
genre = it.genre?.mapNotNull { genre -> genre?.name } ?: emptyList(),
year = it.release_date,
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/requests/GaanaRequests.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/requests/GaanaRequests.kt
index ab244d1e..933b06f4 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/requests/GaanaRequests.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/gaana/requests/GaanaRequests.kt
@@ -25,11 +25,13 @@ import com.shabinder.common.models.gaana.GaanaSong
import io.ktor.client.*
import io.ktor.client.request.*
-private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990"
-private val BASE_URL get() = "${corsApi}https://api.gaana.com"
-
interface GaanaRequests {
+ companion object {
+ private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990"
+ private val BASE_URL get() = "${corsApi}https://api.gaana.com"
+ }
+
val httpClient: HttpClient
/*
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/SaavnProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/SaavnProvider.kt
index 4c807462..a6c7e836 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/SaavnProvider.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/SaavnProvider.kt
@@ -3,6 +3,7 @@ package com.shabinder.common.providers.saavn
import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir
+import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.*
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.saavn.SaavnSong
@@ -28,7 +29,7 @@ class SaavnProvider(
).apply {
val pageLink = fullLink.substringAfter("saavn.com/").substringBefore("?")
when {
- pageLink.contains("/song/", true) -> {
+ pageLink.contains("song/", true) -> {
getSong(fullLink).value.let {
folderType = "Tracks"
subFolder = ""
@@ -37,7 +38,7 @@ class SaavnProvider(
coverUrl = it.image.replace("http:", "https:")
}
}
- pageLink.contains("/album/", true) -> {
+ pageLink.contains("album/", true) -> {
getAlbum(fullLink).value.let {
folderType = "Albums"
subFolder = removeIllegalChars(it.title)
@@ -46,7 +47,7 @@ class SaavnProvider(
coverUrl = it.image.replace("http:", "https:")
}
}
- pageLink.contains("/featured/", true) -> { // Playlist
+ pageLink.contains("featured/", true) -> { // Playlist
getPlaylist(fullLink).value.let {
folderType = "Playlists"
subFolder = removeIllegalChars(it.listname)
@@ -68,7 +69,7 @@ class SaavnProvider(
artists = it.artistMap.keys.toMutableSet().apply { addAll(it.singers.split(",")) }.toList(),
durationSec = it.duration.toInt(),
albumName = it.album,
- albumArtPath = fileManager.imageCacheDir() + (it.image.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg",
+ albumArtPath = fileManager.getImageCachePath(it.image),
year = it.year,
comment = it.copyright_text,
trackUrl = it.perma_url,
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/requests/JioSaavnRequests.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/requests/JioSaavnRequests.kt
index a9d6faec..ca914ab7 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/requests/JioSaavnRequests.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/saavn/requests/JioSaavnRequests.kt
@@ -18,9 +18,16 @@ import io.github.shabinder.utils.getBoolean
import io.github.shabinder.utils.getJsonArray
import io.github.shabinder.utils.getJsonObject
import io.github.shabinder.utils.getString
-import io.ktor.client.*
-import io.ktor.client.request.*
-import kotlinx.serialization.json.*
+import io.ktor.client.HttpClient
+import io.ktor.client.request.get
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.buildJsonArray
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.put
import kotlin.collections.set
interface JioSaavnRequests {
@@ -32,9 +39,9 @@ interface JioSaavnRequests {
trackName: String,
trackArtists: List,
preferredQuality: AudioQuality
- ): SuspendableEvent, Throwable> = searchForSong(trackName).map { songs ->
- val bestMatch = sortByBestMatch(songs, trackName, trackArtists).keys.firstOrNull() ?:
- throw SpotiFlyerException.DownloadLinkFetchFailed("No SAAVN Match Found for $trackName")
+ ): SuspendableEvent, Throwable> = searchForSong(trackName).map { songs ->
+ val bestMatch = sortByBestMatch(songs, trackName, trackArtists).keys.firstOrNull()
+ ?: throw SpotiFlyerException.DownloadLinkFetchFailed("No SAAVN Match Found for $trackName")
var audioQuality: AudioQuality = AudioQuality.KBPS160
val m4aLink: String by getSongFromID(bestMatch).map { song ->
@@ -46,7 +53,7 @@ interface JioSaavnRequests {
song.media_url.requireNotNull().replaceAfterLast("_", "${optimalQuality.kbps}.mp4")
}
- Pair(m4aLink,audioQuality)
+ Pair(m4aLink, audioQuality)
}
suspend fun searchForSong(
@@ -235,8 +242,8 @@ interface JioSaavnRequests {
for (result in tracks) {
var hasCommonWord = false
- val resultName = result.title.lowercase().replace("/", " ")
- val trackNameWords = trackName.lowercase().split(" ")
+ val resultName = result.title.toLowerCase().replace("/", " ")
+ val trackNameWords = trackName.toLowerCase().split(" ")
for (nameWord in trackNameWords) {
if (nameWord.isNotBlank() && FuzzySearch.partialRatio(nameWord, resultName) > 85) hasCommonWord = true
@@ -256,11 +263,11 @@ interface JioSaavnRequests {
// String Containing All Artist Names from JioSaavn Search Result
val artistListString = mutableSetOf().apply {
result.more_info?.singers?.split(",")?.let { addAll(it) }
- result.more_info?.primary_artists?.lowercase()?.split(",")?.let { addAll(it) }
+ result.more_info?.primary_artists?.toLowerCase()?.split(",")?.let { addAll(it) }
}.joinToString(" , ")
for (artist in trackArtists) {
- if (FuzzySearch.partialRatio(artist.lowercase(), artistListString) > 85)
+ if (FuzzySearch.partialRatio(artist.toLowerCase(), artistListString) > 85)
artistMatchNumber++
}
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt
new file mode 100644
index 00000000..d74087d5
--- /dev/null
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt
@@ -0,0 +1,115 @@
+package com.shabinder.common.providers.sound_cloud
+
+import co.touchlab.kermit.Kermit
+import com.shabinder.common.core_components.file_manager.FileManager
+import com.shabinder.common.core_components.file_manager.finalOutputDir
+import com.shabinder.common.core_components.file_manager.getImageCachePath
+import com.shabinder.common.models.AudioFormat
+import com.shabinder.common.models.AudioQuality
+import com.shabinder.common.models.DownloadStatus
+import com.shabinder.common.models.PlatformQueryResult
+import com.shabinder.common.models.TrackDetails
+import com.shabinder.common.models.event.coroutines.SuspendableEvent
+import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponsePlaylist
+import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponseTrack
+import com.shabinder.common.models.spotify.Source
+import com.shabinder.common.providers.sound_cloud.requests.SoundCloudRequests
+import com.shabinder.common.providers.sound_cloud.requests.doAuthenticatedRequest
+import com.shabinder.common.utils.requireNotNull
+import io.github.shabinder.utils.getString
+import io.ktor.client.HttpClient
+import kotlinx.serialization.json.JsonObject
+
+class SoundCloudProvider(
+ private val logger: Kermit,
+ private val fileManager: FileManager,
+ override val httpClient: HttpClient,
+) : SoundCloudRequests {
+ suspend fun query(fullURL: String) = SuspendableEvent {
+ PlatformQueryResult(
+ folderType = "",
+ subFolder = "",
+ title = "",
+ coverUrl = "",
+ trackList = listOf(),
+ Source.SoundCloud
+ ).apply {
+ when (val response = fetchResult(fullURL)) {
+ is SoundCloudResolveResponseTrack -> {
+ folderType = "Tracks"
+ subFolder = ""
+ trackList = listOf(response).toTrackDetailsList(folderType, subFolder)
+ coverUrl = response.artworkUrl
+ title = response.title
+ }
+ is SoundCloudResolveResponsePlaylist -> {
+ folderType = "Playlists"
+ subFolder = response.title
+ trackList = response.tracks.toTrackDetailsList(folderType, subFolder)
+ coverUrl = response.artworkUrl.ifBlank { response.calculatedArtworkUrl }
+ title = response.title
+ }
+ }
+ }
+ }
+
+ suspend fun getDownloadURL(trackDetails: TrackDetails) = SuspendableEvent {
+ doAuthenticatedRequest(trackDetails.videoID.requireNotNull()).getString("url")
+ }
+
+
+ private fun List.toTrackDetailsList(
+ type: String,
+ subFolder: String
+ ): List =
+ map {
+ val downloadableInfo = it.getDownloadableLink()
+ TrackDetails(
+ title = it.title,
+ //trackNumber = it.track_number,
+ genre = listOf(it.genre),
+ artists = /*it.artists?.map { artist -> artist?.name.toString() } ?:*/ listOf(it.user.username.ifBlank { it.genre }),
+ albumArtists = /*it.album?.artists?.mapNotNull { artist -> artist?.name } ?:*/ emptyList(),
+ durationSec = (it.duration / 1000),
+ albumArtPath = fileManager.getImageCachePath(it.artworkUrl.formatArtworkUrl()),
+ albumName = "", //it.album?.name,
+ year = runCatching { it.displayDate.substring(0, 4) }.getOrNull(),
+ comment = it.caption,
+ trackUrl = it.permalinkUrl,
+ downloaded = it.updateStatusIfPresent(type, subFolder),
+ source = Source.SoundCloud,
+ albumArtURL = it.artworkUrl.formatArtworkUrl(),
+ outputFilePath = fileManager.finalOutputDir(
+ it.title,
+ type,
+ subFolder,
+ fileManager.defaultDir()/*,".m4a"*/
+ ),
+ audioQuality = AudioQuality.KBPS128,
+ videoID = downloadableInfo?.first,
+ audioFormat = downloadableInfo?.second ?: AudioFormat.MP3
+ )
+ }
+
+ private fun SoundCloudResolveResponseTrack.updateStatusIfPresent(
+ folderType: String,
+ subFolder: String
+ ): DownloadStatus {
+ return if (fileManager.isPresent(
+ fileManager.finalOutputDir(
+ title,
+ folderType,
+ subFolder,
+ fileManager.defaultDir()
+ )
+ )
+ ) { // Download Already Present!!
+ DownloadStatus.Downloaded
+ } else
+ DownloadStatus.NotDownloaded
+ }
+
+ private fun String.formatArtworkUrl(): String {
+ return substringBeforeLast("-") + "-t500x500." + substringAfterLast(".")
+ }
+}
\ No newline at end of file
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt
new file mode 100644
index 00000000..004e49b4
--- /dev/null
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt
@@ -0,0 +1,138 @@
+package com.shabinder.common.providers.sound_cloud.requests
+
+import com.shabinder.common.core_components.utils.getFinalUrl
+import com.shabinder.common.models.SpotiFlyerException
+import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase
+import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponsePlaylist
+import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponseTrack
+import io.ktor.client.HttpClient
+import io.ktor.client.features.ClientRequestException
+import io.ktor.client.request.get
+import io.ktor.client.request.parameter
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.supervisorScope
+import kotlinx.serialization.InternalSerializationApi
+
+interface SoundCloudRequests {
+
+ val httpClient: HttpClient
+
+ suspend fun fetchResult(url: String): SoundCloudResolveResponseBase {
+ @Suppress("NAME_SHADOWING")
+ var url = url
+
+ // Fetch Full URL if Input is Shortened URL from App
+ if (url.contains("soundcloud.app"))
+ url = httpClient.getFinalUrl(url)
+
+ return getResponseObj(url).run {
+ when (this) {
+ is SoundCloudResolveResponseTrack -> {
+ getTrack()
+ }
+ is SoundCloudResolveResponsePlaylist -> {
+ populatePlaylist()
+ }
+ else -> throw SpotiFlyerException.FeatureNotImplementedYet()
+ }
+ }
+ }
+
+ @Suppress("NAME_SHADOWING")
+ suspend fun SoundCloudResolveResponseTrack.getTrack() = apply {
+ val track = populateTrackInfo()
+
+ if (track.policy == "BLOCK")
+ throw SpotiFlyerException.GeoLocationBlocked(extraInfo = "Use VPN to access ${track.title}")
+
+ if (!track.streamable)
+ throw SpotiFlyerException.LinkInvalid("\nSound Cloud Reports that ${track.title} is not streamable !\n")
+
+ return track
+ }
+
+ @Suppress("NAME_SHADOWING")
+ suspend fun SoundCloudResolveResponsePlaylist.populatePlaylist(): SoundCloudResolveResponsePlaylist = apply {
+ supervisorScope {
+ try {
+ tracks = tracks.map {
+ async {
+ runCatching {
+ it.populateTrackInfo()
+ }.getOrNull() ?: it
+ }
+ }.awaitAll()
+ } catch (e: Throwable) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+
+ private suspend fun SoundCloudResolveResponseTrack.populateTrackInfo(): SoundCloudResolveResponseTrack {
+ if (media.transcodings.isNotEmpty())
+ return this
+
+ val infoURL = URLS.TRACK_INFO.buildURL(id.toString())
+ return httpClient.get(infoURL) {
+ parameter("client_id", CLIENT_ID)
+ }
+ }
+
+ private suspend fun getResponseObj(url: String, clientID: String = CLIENT_ID): SoundCloudResolveResponseBase {
+ val itemURL = URLS.RESOLVE.buildURL(url)
+ val resp: SoundCloudResolveResponseBase = try {
+ httpClient.get(itemURL) {
+ parameter("client_id", clientID)
+ }
+ } catch (e: ClientRequestException) {
+ if (clientID != ALT_CLIENT_ID)
+ return getResponseObj(url, ALT_CLIENT_ID)
+ throw e
+ }
+ val tracksPresent = (resp is SoundCloudResolveResponsePlaylist && resp.tracks.isNotEmpty())
+
+ if (!tracksPresent && clientID != ALT_CLIENT_ID)
+ return getResponseObj(ALT_CLIENT_ID)
+
+ return resp
+ }
+
+ @Suppress("unused")
+ companion object {
+ private enum class URLS(val buildURL: (arg: String) -> String) {
+ RESOLVE({ "https://api-v2.soundcloud.com/resolve?url=$it}" }),
+ PLAYLIST_LIKED({ "https://api-v2.soundcloud.com/users/$it/playlists/liked_and_owned?limit=200" }),
+ FAVORITES({ "'https://api-v2.soundcloud.com/users/$it/track_likes?limit=200" }),
+ COMMENTED({ "https://api-v2.soundcloud.com/users/$it/comments" }),
+ TRACKS({ "https://api-v2.soundcloud.com/users/$it/tracks?limit=200" }),
+ ALL({ "https://api-v2.soundcloud.com/profile/soundcloud:users:$it?limit=200" }),
+ TRACK_INFO({ "https://api-v2.soundcloud.com/tracks/$it" }),
+ ORIGINAL_DOWNLOAD({ "https://api-v2.soundcloud.com/tracks/$it/download" }),
+ USER({ "https://api-v2.soundcloud.com/users/$it" }),
+ ME({ "https://api-v2.soundcloud.com/me?oauth_token=$it" }),
+ }
+
+ const val CLIENT_ID = "a3e059563d7fd3372b49b37f00a00bcf"
+ const val ALT_CLIENT_ID = "2t9loNQH90kzJcsFCODdigxfp325aq4z"
+ }
+}
+
+@OptIn(InternalSerializationApi::class)
+suspend inline fun SoundCloudRequests.doAuthenticatedRequest(url: String): T {
+ var clientID: String = SoundCloudRequests.CLIENT_ID
+ return try {
+ httpClient.get(url) {
+ parameter("client_id", clientID)
+ }
+ } catch (e: ClientRequestException) {
+ if (clientID != SoundCloudRequests.ALT_CLIENT_ID) {
+ clientID = SoundCloudRequests.ALT_CLIENT_ID
+ return httpClient.get(url) {
+ parameter("client_id", clientID)
+ }
+ }
+ throw e
+ }
+}
\ No newline at end of file
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt
index c7706153..1973e890 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt
@@ -19,6 +19,7 @@ package com.shabinder.common.providers.spotify
import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir
+import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.core_components.utils.createHttpClient
import com.shabinder.common.models.*
import com.shabinder.common.models.event.coroutines.SuspendableEvent
@@ -79,7 +80,7 @@ class SpotifyProvider(
if (type == "episode" || type == "show") {
throw SpotiFlyerException.FeatureNotImplementedYet(
- "Support for Spotify's ${type.uppercase()} isn't implemented yet"
+ "Support for Spotify's ${type.toUpperCase()} isn't implemented yet"
)
}
@@ -201,9 +202,7 @@ class SpotifyProvider(
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
albumArtists = it.album?.artists?.mapNotNull { artist -> artist?.name } ?: emptyList(),
durationSec = (it.duration_ms / 1000).toInt(),
- albumArtPath = fileManager.imageCacheDir() + (it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast(
- '/'
- ) + ".jpeg",
+ albumArtPath = fileManager.getImageCachePath(it.album?.images?.firstOrNull()?.url ?: ""),
albumName = it.album?.name,
year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}",
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube/YoutubeProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube/YoutubeProvider.kt
index 4fea8918..7e5714c6 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube/YoutubeProvider.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube/YoutubeProvider.kt
@@ -19,6 +19,7 @@ package com.shabinder.common.providers.youtube
import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.file_manager.finalOutputDir
+import com.shabinder.common.core_components.file_manager.getImageCachePath
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException
@@ -30,7 +31,7 @@ import io.github.shabinder.YoutubeDownloader
import io.github.shabinder.models.YoutubeVideo
import io.github.shabinder.models.formats.Format
import io.github.shabinder.models.quality.AudioQuality
-import io.ktor.client.*
+import io.ktor.client.HttpClient
class YoutubeProvider(
private val httpClient: HttpClient,
@@ -108,13 +109,14 @@ class YoutubeProvider(
title = name
trackList = videos.map {
+ val imageURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg"
TrackDetails(
title = it.title ?: "N/A",
artists = listOf(it.author ?: "N/A"),
durationSec = it.lengthSeconds,
- albumArtPath = fileManager.imageCacheDir() + it.videoId + ".jpeg",
+ albumArtPath = fileManager.getImageCachePath(imageURL),
source = Source.YouTube,
- albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
+ albumArtURL = imageURL,
downloaded = if (fileManager.isPresent(
fileManager.finalOutputDir(
itemName = it.title ?: "N/A",
@@ -155,7 +157,7 @@ class YoutubeProvider(
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video.videoDetails
- val name = detail.title?.replace(detail.author?.uppercase() ?: "", "", true)
+ val name = detail.title?.replace(detail.author?.toUpperCase() ?: "", "", true)
?: detail.title ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
@@ -163,9 +165,9 @@ class YoutubeProvider(
title = name,
artists = listOf(detail.author ?: "N/A"),
durationSec = detail.lengthSeconds,
- albumArtPath = fileManager.imageCacheDir() + "$searchId.jpeg",
+ albumArtPath = fileManager.getImageCachePath(coverUrl),
source = Source.YouTube,
- albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
+ albumArtURL = coverUrl,
downloaded = if (fileManager.isPresent(
fileManager.finalOutputDir(
itemName = name,
@@ -179,7 +181,12 @@ class YoutubeProvider(
else {
DownloadStatus.NotDownloaded
},
- outputFilePath = fileManager.finalOutputDir(name, folderType, subFolder, fileManager.defaultDir()/*,".m4a"*/),
+ outputFilePath = fileManager.finalOutputDir(
+ name,
+ folderType,
+ subFolder,
+ fileManager.defaultDir()/*,".m4a"*/
+ ),
videoID = searchId
)
)
diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube_music/YoutubeMusic.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube_music/YoutubeMusic.kt
index 7ea1676d..a2c10e54 100644
--- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube_music/YoutubeMusic.kt
+++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/youtube_music/YoutubeMusic.kt
@@ -18,19 +18,33 @@ package com.shabinder.common.providers.youtube_music
import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.file_manager.FileManager
-import com.shabinder.common.models.*
+import com.shabinder.common.models.AudioFormat
+import com.shabinder.common.models.AudioQuality
+import com.shabinder.common.models.SpotiFlyerException
+import com.shabinder.common.models.TrackDetails
+import com.shabinder.common.models.YoutubeTrack
+import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.flatMap
-import com.shabinder.common.models.event.coroutines.flatMapError
import com.shabinder.common.models.event.coroutines.map
import com.shabinder.common.providers.youtube.YoutubeProvider
-import com.shabinder.common.providers.youtube.get
import com.shabinder.common.providers.youtube_to_mp3.requests.YoutubeMp3
import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch
-import io.ktor.client.*
-import io.ktor.client.request.*
-import io.ktor.http.*
-import kotlinx.serialization.json.*
+import io.ktor.client.HttpClient
+import io.ktor.client.request.headers
+import io.ktor.client.request.post
+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.collections.set
import kotlin.math.absoluteValue
@@ -50,7 +64,7 @@ class YoutubeMusic constructor(
suspend fun findMp3SongDownloadURLYT(
trackDetails: TrackDetails,
preferredQuality: AudioQuality = fileManager.preferenceManager.audioQuality
- ): SuspendableEvent, Throwable> {
+ ): SuspendableEvent, Throwable> {
return getYTIDBestMatch(trackDetails).flatMap { videoID ->
// As YT compress Audio hence there is no benefit of quality for more than 192
val optimalQuality =
@@ -69,7 +83,7 @@ class YoutubeMusic constructor(
}
}*/.map {
trackDetails.audioFormat = AudioFormat.MP3
- Pair(it,optimalQuality)
+ Pair(it, optimalQuality)
}
}
}
@@ -168,7 +182,7 @@ class YoutubeMusic constructor(
! 4 - Duration (hh:mm:ss)
!
! We blindly gather all the details we get our hands on, then
- ! cherry pick the details we need based on their index numbers,
+ ! cherry-pick the details we need based on their index numbers,
! we do so only if their Type is 'Song' or 'Video
*/
@@ -180,7 +194,7 @@ class YoutubeMusic constructor(
/*
Filter Out dummies here itself
! 'musicResponsiveListItemFlexColumnRenderer' should have more that one
- ! sub-block, if not its a dummy, why does the YTM response contain dummies?
+ ! sub-block, if not it is a dummy, why does the YTM response contain dummies?
! I have no clue. We skip these.
! Remember that we appended the linkBlock to result, treating that like the
@@ -189,7 +203,7 @@ class YoutubeMusic constructor(
*/
for (detailArray in result.subList(0, result.size - 1)) {
for (detail in detailArray.jsonArray) {
- if (detail.jsonObject["musicResponsiveListItemFlexColumnRenderer"]?.jsonObject?.size ?: 0 < 2) continue
+ if ((detail.jsonObject["musicResponsiveListItemFlexColumnRenderer"]?.jsonObject?.size ?: 0) < 2) continue
// if not a dummy, collect All Variables
val details =
@@ -262,8 +276,8 @@ class YoutubeMusic constructor(
// most song results on youtube go by $artist - $songName or artist1/artist2
var hasCommonWord = false
- val resultName = result.name?.lowercase()?.replace("-", " ")?.replace("/", " ") ?: ""
- val trackNameWords = trackName.lowercase().split(" ")
+ val resultName = result.name?.toLowerCase()?.replace("-", " ")?.replace("/", " ") ?: ""
+ val trackNameWords = trackName.toLowerCase().split(" ")
for (nameWord in trackNameWords) {
if (nameWord.isNotBlank() && FuzzySearch.partialRatio(
@@ -287,8 +301,8 @@ class YoutubeMusic constructor(
if (result.type == "Song") {
for (artist in trackArtists) {
if (FuzzySearch.ratio(
- artist.lowercase(),
- result.artist?.lowercase() ?: ""
+ artist.toLowerCase(),
+ result.artist?.toLowerCase() ?: ""
) > 85
)
artistMatchNumber++
@@ -296,8 +310,8 @@ class YoutubeMusic constructor(
} else { // i.e. is a Video
for (artist in trackArtists) {
if (FuzzySearch.partialRatio(
- artist.lowercase(),
- result.name?.lowercase() ?: ""
+ artist.toLowerCase(),
+ result.name?.toLowerCase() ?: ""
) > 85
)
artistMatchNumber++
diff --git a/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt b/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt
index 8767770a..a06d8471 100644
--- a/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt
+++ b/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt
@@ -1,10 +1,15 @@
package com.shabinder.common.providers
+import com.shabinder.common.core_components.utils.createHttpClient
+import com.shabinder.common.core_components.utils.getFinalUrl
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.providers.utils.CommonUtils
import com.shabinder.common.providers.utils.SpotifyUtils
import com.shabinder.common.providers.utils.SpotifyUtils.toTrackDetailsList
import io.github.shabinder.runBlocking
+import io.ktor.client.request.get
+import io.ktor.client.statement.HttpResponse
+import kotlinx.serialization.InternalSerializationApi
import kotlin.test.Test
class TestSpotifyTrackMatching {
@@ -17,7 +22,15 @@ class TestSpotifyTrackMatching {
private val spotifyToken: String?
get() = null
-// get() = "BQB41HqrLcrh5eRYaL97GvaH6tRe-1EktQ8VGTWUQuFnYVWBEoTcF7T_8ogqVn1GHl9HCcMiQ0HBT-ybC74"
+
+ // get() = "BQB41HqrLcrh5eRYaL97GvaH6tRe-1EktQ8VGTWUQuFnYVWBEoTcF7T_8ogqVn1GHl9HCcMiQ0HBT-ybC74"
+ @OptIn(InternalSerializationApi::class)
+ @Test
+ fun testRandomThing() = runBlocking {
+ val res = createHttpClient().getFinalUrl("https://soundcloud.app.goo.gl/vrBzR")
+ println(res)
+ }
+
@Test
fun matchVideo() = runBlocking {
diff --git a/common/root/build.gradle.kts b/common/root/build.gradle.kts
index 8687f531..1bbe64e2 100644
--- a/common/root/build.gradle.kts
+++ b/common/root/build.gradle.kts
@@ -33,9 +33,10 @@ fun org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer.generateFramewor
export(project(":common:providers"))
export(project(":common:list"))
export(project(":common:preference"))
- export(Decompose.decompose)
- export(MVIKotlin.mvikotlinMain)
- export(MVIKotlin.mvikotlinLogging)
+ with(deps) {
+ export(decompose.dep)
+ export(bundles.mviKotlin)
+ }
}
}
@@ -71,7 +72,6 @@ kotlin {
implementation(project(":common:providers"))
implementation(project(":common:core-components"))
implementation(project(":common:preference"))
- implementation(SqlDelight.coroutineExtensions)
}
}
}
@@ -86,9 +86,10 @@ kotlin {
api(project(":common:list"))
api(project(":common:main"))
api(project(":common:preference"))
- api(Decompose.decompose)
- api(MVIKotlin.mvikotlinMain)
- api(MVIKotlin.mvikotlinLogging)
+ with(deps) {
+ api(decompose.dep)
+ api(bundles.mviKotlin)
+ }
}
}
}
@@ -100,8 +101,11 @@ val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val targetName = "ios"
- val framework = kotlin.targets.getByName(targetName)
- .binaries.getFramework(mode)
+ val framework =
+ kotlin.targets.getByName(
+ targetName
+ )
+ .binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
diff --git a/console-app/build.gradle.kts b/console-app/build.gradle.kts
index c3da4280..da6958db 100644
--- a/console-app/build.gradle.kts
+++ b/console-app/build.gradle.kts
@@ -19,38 +19,39 @@ application {
}
dependencies {
- implementation(Koin.core)
- implementation(project(":common:database"))
- implementation(project(":common:data-models"))
- implementation(project(":common:dependency-injection"))
- implementation(project(":common:root"))
- implementation(project(":common:main"))
- implementation(project(":common:list"))
- implementation(project(":common:list"))
+ with(deps) {
+ implementation(Koin.core)
+ implementation(project(":common:database"))
+ implementation(project(":common:data-models"))
+ implementation(project(":common:dependency-injection"))
+ implementation(project(":common:root"))
+ implementation(project(":common:main"))
+ implementation(project(":common:list"))
+ implementation(project(":common:list"))
- // Decompose
- implementation(Decompose.decompose)
- implementation(Decompose.extensionsCompose)
+ // Decompose
+ implementation(Decompose.decompose)
+ implementation(Decompose.extensionsCompose)
- // MVI
- implementation(MVIKotlin.mvikotlin)
- implementation(MVIKotlin.mvikotlinMain)
+ // MVI
+ implementation(MVIKotlin.mvikotlin)
+ implementation(MVIKotlin.mvikotlinMain)
- // Koin
- implementation(Koin.core)
+ // Koin
+ implementation(Koin.core)
- // Matomo
- implementation("org.piwik.java.tracking:matomo-java-tracker:1.6")
+ // Matomo
- implementation(Ktor.slf4j)
- implementation(Ktor.clientCore)
- implementation(Ktor.clientJson)
- implementation(Ktor.clientApache)
- implementation(Ktor.clientLogging)
- implementation(Ktor.clientSerialization)
- implementation(Serialization.json)
- // testDeps
- testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.5.21")
+ implementation(Ktor.slf4j)
+ implementation(Ktor.clientCore)
+ implementation(Ktor.clientJson)
+ implementation(Ktor.clientApache)
+ implementation(Ktor.clientLogging)
+ implementation(Ktor.clientSerialization)
+ implementation(Serialization.json)
+ // testDeps
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.5.21")
+ }
}
tasks.withType().configureEach {
kotlinOptions {
diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts
index 0ccbe2dc..b176fd86 100644
--- a/desktop/build.gradle.kts
+++ b/desktop/build.gradle.kts
@@ -32,9 +32,12 @@ kotlin {
kotlinOptions.jvmTarget = "1.8"
}
}
-
+ tasks.named("jvmProcessResources") {
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ }
sourceSets {
val jvmMain by getting {
+ resources.srcDirs("../common/data-models/src/main/res")
dependencies {
implementation(compose.desktop.currentOs)
implementation(project(":common:database"))
@@ -44,19 +47,21 @@ kotlin {
implementation(project(":common:compose"))
implementation(project(":common:providers"))
implementation(project(":common:root"))
- implementation("com.github.kokorin.jaffree:jaffree:2021.08.16")
- // Decompose
- implementation(Decompose.decompose)
- implementation(Decompose.extensionsCompose)
+ with(deps) {
+ implementation(jaffree)
- // MVI
- implementation(MVIKotlin.mvikotlin)
- implementation(MVIKotlin.mvikotlinMain)
-
- // Koin
- implementation(Koin.core)
+ with(decompose) {
+ implementation(dep)
+ implementation(extensions.compose)
+ }
+ with(mviKotlin) {
+ implementation(dep)
+ implementation(main)
+ }
+ implementation(koin.core)
+ }
}
}
val jvmTest by getting
diff --git a/desktop/src/jvmMain/resources/drawable/ic_arrow.xml b/desktop/src/jvmMain/resources/drawable/ic_arrow.xml
deleted file mode 100644
index a426c154..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_arrow.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml b/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml
deleted file mode 100644
index deadedca..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_download_arrow.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_error.xml b/desktop/src/jvmMain/resources/drawable/ic_error.xml
deleted file mode 100644
index 9ebd8a0e..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_error.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_gaana.xml b/desktop/src/jvmMain/resources/drawable/ic_gaana.xml
deleted file mode 100644
index 28d27c3c..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_gaana.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_github.xml b/desktop/src/jvmMain/resources/drawable/ic_github.xml
deleted file mode 100644
index 0e14b28a..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_github.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_heart.xml b/desktop/src/jvmMain/resources/drawable/ic_heart.xml
deleted file mode 100644
index 92f9beb0..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_heart.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_indian_rupee.xml b/desktop/src/jvmMain/resources/drawable/ic_indian_rupee.xml
deleted file mode 100644
index 637c6b56..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_indian_rupee.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_instagram.xml b/desktop/src/jvmMain/resources/drawable/ic_instagram.xml
deleted file mode 100644
index 1cd9bc2d..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_instagram.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_jio_saavn_logo.xml b/desktop/src/jvmMain/resources/drawable/ic_jio_saavn_logo.xml
deleted file mode 100644
index 1a84ca9a..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_jio_saavn_logo.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_linkedin.xml b/desktop/src/jvmMain/resources/drawable/ic_linkedin.xml
deleted file mode 100644
index 8b177562..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_linkedin.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_mug.xml b/desktop/src/jvmMain/resources/drawable/ic_mug.xml
deleted file mode 100644
index c8260781..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_mug.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_musicplaceholder.xml b/desktop/src/jvmMain/resources/drawable/ic_musicplaceholder.xml
deleted file mode 100644
index 7d304388..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_musicplaceholder.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_opencollective_icon.xml b/desktop/src/jvmMain/resources/drawable/ic_opencollective_icon.xml
deleted file mode 100644
index b1ac9100..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_opencollective_icon.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_paypal_logo.xml b/desktop/src/jvmMain/resources/drawable/ic_paypal_logo.xml
deleted file mode 100644
index 933369b5..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_paypal_logo.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_refreshgradient.xml b/desktop/src/jvmMain/resources/drawable/ic_refreshgradient.xml
deleted file mode 100644
index 47805ea8..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_refreshgradient.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_round_cancel_24.xml b/desktop/src/jvmMain/resources/drawable/ic_round_cancel_24.xml
deleted file mode 100644
index a5eacb39..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_round_cancel_24.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_share_open.xml b/desktop/src/jvmMain/resources/drawable/ic_share_open.xml
deleted file mode 100644
index dbd7c1a4..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_share_open.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_song_placeholder.xml b/desktop/src/jvmMain/resources/drawable/ic_song_placeholder.xml
deleted file mode 100644
index 04a9c803..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_song_placeholder.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_spotiflyer_logo.xml b/desktop/src/jvmMain/resources/drawable/ic_spotiflyer_logo.xml
deleted file mode 100644
index 8712d43c..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_spotiflyer_logo.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_spotify_logo.xml b/desktop/src/jvmMain/resources/drawable/ic_spotify_logo.xml
deleted file mode 100644
index e773449a..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_spotify_logo.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_tick.xml b/desktop/src/jvmMain/resources/drawable/ic_tick.xml
deleted file mode 100644
index 47903522..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_tick.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_youtube.xml b/desktop/src/jvmMain/resources/drawable/ic_youtube.xml
deleted file mode 100644
index 728779f1..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_youtube.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/ic_youtube_music_logo.xml b/desktop/src/jvmMain/resources/drawable/ic_youtube_music_logo.xml
deleted file mode 100644
index 7e723917..00000000
--- a/desktop/src/jvmMain/resources/drawable/ic_youtube_music_logo.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/music.xml b/desktop/src/jvmMain/resources/drawable/music.xml
deleted file mode 100644
index 04a9c803..00000000
--- a/desktop/src/jvmMain/resources/drawable/music.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
diff --git a/desktop/src/jvmMain/resources/drawable/spotiflyer.png b/desktop/src/jvmMain/resources/drawable/spotiflyer.png
index d4aba36e..74aebd66 100644
Binary files a/desktop/src/jvmMain/resources/drawable/spotiflyer.png and b/desktop/src/jvmMain/resources/drawable/spotiflyer.png differ
diff --git a/desktop/src/jvmMain/resources/font/montserrat_light.ttf b/desktop/src/jvmMain/resources/font/montserrat_light.ttf
deleted file mode 100644
index 990857de..00000000
Binary files a/desktop/src/jvmMain/resources/font/montserrat_light.ttf and /dev/null differ
diff --git a/desktop/src/jvmMain/resources/font/montserrat_medium.ttf b/desktop/src/jvmMain/resources/font/montserrat_medium.ttf
deleted file mode 100644
index 6e079f69..00000000
Binary files a/desktop/src/jvmMain/resources/font/montserrat_medium.ttf and /dev/null differ
diff --git a/desktop/src/jvmMain/resources/font/montserrat_regular.ttf b/desktop/src/jvmMain/resources/font/montserrat_regular.ttf
deleted file mode 100644
index 8d443d5d..00000000
Binary files a/desktop/src/jvmMain/resources/font/montserrat_regular.ttf and /dev/null differ
diff --git a/desktop/src/jvmMain/resources/font/montserrat_semibold.ttf b/desktop/src/jvmMain/resources/font/montserrat_semibold.ttf
deleted file mode 100644
index f8a43f2b..00000000
Binary files a/desktop/src/jvmMain/resources/font/montserrat_semibold.ttf and /dev/null differ
diff --git a/desktop/src/jvmMain/resources/font/pristine_script.ttf b/desktop/src/jvmMain/resources/font/pristine_script.ttf
deleted file mode 100644
index e8d3e494..00000000
Binary files a/desktop/src/jvmMain/resources/font/pristine_script.ttf and /dev/null differ
diff --git a/ffmpeg/android-ffmpeg/build.gradle.kts b/ffmpeg/android-ffmpeg/build.gradle.kts
index 24a62ad6..be58fa56 100644
--- a/ffmpeg/android-ffmpeg/build.gradle.kts
+++ b/ffmpeg/android-ffmpeg/build.gradle.kts
@@ -13,8 +13,6 @@ android {
minSdk = Versions.minSdkVersion
targetSdk = Versions.targetSdkVersion
-// versionCode = Versions.versionCode
-// versionName = Versions.versionName
/*ndk {
abiFilters.addAll(setOf("x86", "x86_64", "armeabi-v7a", "arm64-v8a"))
diff --git a/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/CpuArchHelper.java b/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/CpuArchHelper.java
index a363885e..19e681cc 100644
--- a/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/CpuArchHelper.java
+++ b/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/CpuArchHelper.java
@@ -2,6 +2,7 @@ package nl.bravobit.ffmpeg;
import android.os.Build;
+@SuppressWarnings("deprecation")
public class CpuArchHelper {
public static final String X86_CPU = "x86";
public static final String X86_64_CPU = "x86_64";
diff --git a/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFcommandExecuteAsyncTask.java b/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFcommandExecuteAsyncTask.java
index 7ec21b44..83f93c79 100644
--- a/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFcommandExecuteAsyncTask.java
+++ b/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFcommandExecuteAsyncTask.java
@@ -1,7 +1,6 @@
package nl.bravobit.ffmpeg;
import android.os.AsyncTask;
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -9,10 +8,12 @@ import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.TimeoutException;
+@SuppressWarnings("deprecation")
class FFcommandExecuteAsyncTask extends AsyncTask implements FFtask {
private final String[] cmd;
- private Map environment;
+ private final Map environment;
+ private final StringBuilder outputStringBuilder = new StringBuilder();
private final FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler;
private final ShellCommand shellCommand;
private final long timeout;
@@ -39,6 +40,7 @@ class FFcommandExecuteAsyncTask extends AsyncTask i
@Override
protected CommandResult doInBackground(Void... params) {
+ CommandResult ret = CommandResult.getDummyFailureResponse();
try {
process = shellCommand.run(cmd, environment);
if (process == null) {
@@ -46,16 +48,19 @@ class FFcommandExecuteAsyncTask extends AsyncTask i
}
Log.d("Running publishing updates method");
checkAndUpdateProcess();
- return CommandResult.getOutputFromProcess(process);
+ ret = CommandResult.getOutputFromProcess(process);
+ outputStringBuilder.append(ret.output);
} catch (TimeoutException e) {
Log.e("FFmpeg binary timed out", e);
- return new CommandResult(false, e.getMessage());
+ ret = new CommandResult(false, e.getMessage());
+ outputStringBuilder.append(ret.output);
} catch (Exception e) {
Log.e("Error running FFmpeg binary", e);
} finally {
Util.destroyProcess(process);
}
- return CommandResult.getDummyFailureResponse();
+ output = outputStringBuilder.toString();
+ return ret;
}
@Override
@@ -68,7 +73,6 @@ class FFcommandExecuteAsyncTask extends AsyncTask i
@Override
protected void onPostExecute(CommandResult commandResult) {
if (ffmpegExecuteResponseHandler != null) {
- output += commandResult.output;
if (commandResult.success) {
ffmpegExecuteResponseHandler.onSuccess(output);
} else {
@@ -107,7 +111,7 @@ class FFcommandExecuteAsyncTask extends AsyncTask i
return;
}
- output += line + "\n";
+ outputStringBuilder.append(line); outputStringBuilder.append("\n");
publishProgress(line);
}
} catch (IOException e) {
@@ -139,4 +143,4 @@ class FFcommandExecuteAsyncTask extends AsyncTask i
e.printStackTrace();
}
}
-}
+}
\ No newline at end of file
diff --git a/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFprobe.java b/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFprobe.java
index 447229d6..76bf63b6 100644
--- a/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFprobe.java
+++ b/ffmpeg/android-ffmpeg/src/main/java/nl/bravobit/ffmpeg/FFprobe.java
@@ -6,6 +6,7 @@ import android.os.AsyncTask;
import java.io.File;
import java.util.Map;
+@SuppressWarnings("deprecation")
public class FFprobe implements FFbinaryInterface {
private final FFbinaryContextProvider context;
@@ -22,12 +23,7 @@ public class FFprobe implements FFbinaryInterface {
public static FFprobe getInstance(final Context context) {
if (instance == null) {
- instance = new FFprobe(new FFbinaryContextProvider() {
- @Override
- public Context provide() {
- return context;
- }
- });
+ instance = new FFprobe(() -> context);
}
return instance;
}
diff --git a/gradle.properties b/gradle.properties
index b2589535..61c33e1d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,7 +22,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m
+org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
diff --git a/maintenance-tasks/build.gradle.kts b/maintenance-tasks/build.gradle.kts
index b0395302..440b0d2c 100644
--- a/maintenance-tasks/build.gradle.kts
+++ b/maintenance-tasks/build.gradle.kts
@@ -18,16 +18,15 @@ application {
}
dependencies {
- implementation(Ktor.slf4j)
- implementation(Ktor.clientCore)
- implementation(Ktor.clientJson)
- implementation(Ktor.clientApache)
- implementation(Ktor.clientLogging)
- implementation(Ktor.clientSerialization)
- implementation(Serialization.json)
+ with(deps) {
+ implementation(slf4j.simple)
+ implementation(bundles.ktor)
+ implementation(ktor.client.apache)
+ implementation(kotlinx.serialization.json)
- // testDeps
- testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.5.21")
+ // testDep
+ testImplementation(kotlin.kotlinTestJunit)
+ }
}
tasks.test {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ed7aeddd..755545b7 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,6 +14,16 @@
* * along with this program. If not, see .
*/
+enableFeaturePreview("VERSION_CATALOGS")
+dependencyResolutionManagement {
+ @Suppress("UnstableApiUsage")
+ versionCatalogs {
+ create("deps") {
+ from(files("buildSrc/deps.versions.toml"))
+ }
+ }
+}
+
rootProject.name = "spotiflyer"
include(
@@ -32,5 +42,5 @@ include(
":desktop",
":web-app",
//":console-app",
- ":maintenance-tasks"
+ ":maintenance-tasks",
)
diff --git a/web-app/build.gradle.kts b/web-app/build.gradle.kts
index e13b2509..fb0a4424 100644
--- a/web-app/build.gradle.kts
+++ b/web-app/build.gradle.kts
@@ -21,20 +21,17 @@ plugins {
group = "com.shabinder"
version = "0.1"
-repositories {
- mavenCentral()
- //maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers")
-}
-
dependencies {
- implementation("org.jetbrains.kotlin:kotlin-stdlib-js:1.5.21")
- implementation(Koin.core)
- implementation(Extras.kermit)
- implementation(Decompose.decompose)
- implementation(MVIKotlin.mvikotlin)
- implementation(MVIKotlin.coroutines)
- implementation(MVIKotlin.mvikotlinMain)
- implementation(MVIKotlin.mvikotlinLogging)
+ with(deps) {
+ implementation(koin.core)
+ implementation(decompose.dep)
+ implementation(ktor.client.js)
+ with(bundles) {
+ implementation(mviKotlin)
+ implementation(ktor)
+ implementation(kotlin.js.wrappers)
+ }
+ }
implementation(project(":common:root"))
implementation(project(":common:main"))
implementation(project(":common:list"))
@@ -43,27 +40,7 @@ dependencies {
implementation(project(":common:providers"))
implementation(project(":common:core-components"))
implementation(project(":common:dependency-injection"))
- implementation("co.touchlab:stately-common:1.1.7")
- implementation("dev.icerock.moko:parcelize:${Versions.mokoParcelize}")
- // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1")
-
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2") {
- // https://youtrack.jetbrains.com/issue/KTOR-2670
- @Suppress("DEPRECATION")
- isForce = true
- }
-
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1-native-mt") {
- @Suppress("DEPRECATION")
- isForce = true
- }
-
- with(KotlinJSWrappers) {
- implementation(enforcedPlatform(bom))
- implementation(kotlinReact)
- implementation(kotlinReactDom)
- implementation(kotlinStyled)
- }
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-js:${deps.kotlin.kotlinGradlePlugin.get().versionConstraint.requiredVersion}")
}
kotlin {
@@ -85,4 +62,10 @@ kotlin {
}
binaries.executable()
}
+ // WorkAround: https://youtrack.jetbrains.com/issue/KT-49124
+ rootProject.plugins.withType {
+ rootProject.the().apply {
+ resolution("@webpack-cli/serve", "1.5.2")
+ }
+ }
}
\ No newline at end of file
diff --git a/web-app/src/main/kotlin/App.kt b/web-app/src/main/kotlin/App.kt
index 0f2ee177..f8cacb7b 100644
--- a/web-app/src/main/kotlin/App.kt
+++ b/web-app/src/main/kotlin/App.kt
@@ -23,21 +23,25 @@ import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.core_components.file_manager.DownloadProgressFlow
import com.shabinder.common.core_components.preference_manager.PreferenceManager
+import com.shabinder.common.di.ApplicationInit
import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.database.Database
import extras.renderableChild
-import react.*
+import react.PropsWithChildren
+import react.RBuilder
+import react.RComponent
+import react.State
import root.RootR
-external interface AppProps : RProps {
+external interface AppProps : PropsWithChildren {
var dependencies: AppDependencies
}
@Suppress("FunctionName")
-fun RBuilder.App(attrs: AppProps.() -> Unit): ReactElement {
+fun RBuilder.App(attrs: AppProps.() -> Unit) {
return child(App::class) {
this.attrs(attrs)
}
@@ -46,7 +50,7 @@ fun RBuilder.App(attrs: AppProps.() -> Unit): ReactElement {
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED", "NON_EXPORTABLE_TYPE")
@OptIn(ExperimentalJsExport::class)
@JsExport
-class App(props: AppProps) : RComponent(props) {
+class App(props: AppProps) : RComponent(props) {
private val lifecycle = LifecycleRegistry()
private val ctx = DefaultComponentContext(lifecycle = lifecycle)
@@ -62,6 +66,7 @@ class App(props: AppProps) : RComponent(props) {
override val fetchQuery = dependencies.fetchPlatformQueryResult
override val fileManager = dependencies.fileManager
override val analyticsManager = dependencies.analyticsManager
+ override val appInit: ApplicationInit = dependencies.appInit
override val preferenceManager: PreferenceManager = dependencies.preferenceManager
override val database: Database? = fileManager.db
override val downloadProgressFlow = DownloadProgressFlow
diff --git a/web-app/src/main/kotlin/client.kt b/web-app/src/main/kotlin/client.kt
index 1efce0ae..50ca5c69 100644
--- a/web-app/src/main/kotlin/client.kt
+++ b/web-app/src/main/kotlin/client.kt
@@ -18,6 +18,7 @@ import co.touchlab.kermit.Kermit
import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.preference_manager.PreferenceManager
+import com.shabinder.common.di.ApplicationInit
import com.shabinder.common.di.initKoin
import com.shabinder.common.providers.FetchPlatformQueryResult
import kotlinx.browser.document
@@ -42,6 +43,7 @@ object AppDependencies : KoinComponent {
val fetchPlatformQueryResult: FetchPlatformQueryResult
val preferenceManager: PreferenceManager
val analyticsManager: AnalyticsManager
+ val appInit: ApplicationInit
init {
initKoin()
fileManager = get()
@@ -49,5 +51,6 @@ object AppDependencies : KoinComponent {
fetchPlatformQueryResult = get()
preferenceManager = get()
analyticsManager = get()
+ appInit = get()
}
}
\ No newline at end of file
diff --git a/web-app/src/main/kotlin/extras/RenderableComponent.kt b/web-app/src/main/kotlin/extras/RenderableComponent.kt
index cc3731a0..dc37157c 100644
--- a/web-app/src/main/kotlin/extras/RenderableComponent.kt
+++ b/web-app/src/main/kotlin/extras/RenderableComponent.kt
@@ -18,16 +18,16 @@ package extras
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.ValueObserver
+import react.PropsWithChildren
import react.RComponent
-import react.RProps
-import react.RState
+import react.State
import react.setState
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED", "NON_EXPORTABLE_TYPE")
@OptIn(ExperimentalJsExport::class)
@JsExport
-abstract class RenderableComponent(
+abstract class RenderableComponent(
props: Props,
initialState: S
) : RComponent, S>(props) {
@@ -60,7 +60,6 @@ abstract class RenderableComponent(
}
-
protected class Subscription(
val value: Value,
val observer: ValueObserver
@@ -72,8 +71,8 @@ abstract class RenderableComponent(
@JsExport
class RStateWrapper(
var model: T
-) : RState
+) : State
-external interface Props : RProps {
+external interface Props : PropsWithChildren {
var component: T
}
\ No newline at end of file
diff --git a/web-app/src/main/kotlin/home/IconList.kt b/web-app/src/main/kotlin/home/IconList.kt
index 1b751793..af947314 100644
--- a/web-app/src/main/kotlin/home/IconList.kt
+++ b/web-app/src/main/kotlin/home/IconList.kt
@@ -17,39 +17,49 @@
package home
import Styles
-import kotlinx.css.*
+import kotlinx.css.borderRadius
+import kotlinx.css.height
+import kotlinx.css.margin
+import kotlinx.css.px
+import kotlinx.css.width
import kotlinx.html.id
-import react.*
+import react.PropsWithChildren
+import react.RBuilder
import react.dom.attrs
-import styled.*
+import react.functionComponent
+import styled.css
+import styled.styledA
+import styled.styledDiv
+import styled.styledForm
+import styled.styledImg
-external interface IconListProps : RProps {
- var iconsAndPlatforms: Map
- var isBadge:Boolean
+external interface IconListProps : PropsWithChildren {
+ var iconsAndPlatforms: Map
+ var isBadge: Boolean
}
@Suppress("FunctionName")
-fun RBuilder.IconList(handler:IconListProps.() -> Unit): ReactElement {
- return child(iconList){
+fun RBuilder.IconList(handler: IconListProps.() -> Unit) {
+ return child(iconList) {
attrs {
handler()
}
}
}
-private val iconList = functionalComponent("IconList") { props ->
+private val iconList = functionComponent("IconList") { props ->
styledDiv {
css {
margin(18.px)
- if(props.isBadge) {
- classes = mutableListOf("info-banners")
+ if (props.isBadge) {
+ classes.add("info-banners")
}
- + Styles.makeRow
+ +Styles.makeRow
}
val firstElem = props.iconsAndPlatforms.keys.elementAt(1)
- for((icon,platformLink) in props.iconsAndPlatforms){
- if(icon == firstElem && props.isBadge){
+ for ((icon, platformLink) in props.iconsAndPlatforms) {
+ if (icon == firstElem && props.isBadge) {
//
styledForm {
attrs {
@@ -57,13 +67,13 @@ private val iconList = functionalComponent("IconList") { props ->
}
}
}
- styledA(href = platformLink,target="_blank"){
+ styledA(href = platformLink, target = "_blank") {
styledImg {
attrs {
src = icon
}
css {
- classes = mutableListOf("glow-button")
+ classes.add("glow-button")
margin(8.px)
if (!props.isBadge) {
height = 42.px
diff --git a/web-app/src/main/kotlin/home/Message.kt b/web-app/src/main/kotlin/home/Message.kt
index 4896de97..04f31a45 100644
--- a/web-app/src/main/kotlin/home/Message.kt
+++ b/web-app/src/main/kotlin/home/Message.kt
@@ -18,34 +18,32 @@ package home
import kotlinx.css.em
import kotlinx.css.fontSize
+import react.PropsWithChildren
import react.RBuilder
-import react.RProps
-import react.ReactElement
-import react.child
-import react.functionalComponent
+import react.functionComponent
import styled.css
import styled.styledDiv
import styled.styledH1
-external interface MessageProps : RProps {
+external interface MessageProps : PropsWithChildren {
var text: String
}
@Suppress("FunctionName")
-fun RBuilder.Message(handler:MessageProps.() -> Unit): ReactElement {
- return child(message){
+fun RBuilder.Message(handler: MessageProps.() -> Unit) {
+ return child(message) {
attrs {
handler()
}
}
}
-private val message = functionalComponent("Message") { props->
+private val message = functionComponent("Message") { props ->
styledDiv {
styledH1 {
- + props.text
+ +props.text
css {
- classes = mutableListOf("headingTitle")
+ classes.add("headingTitle")
fontSize = 2.6.em
}
}
diff --git a/web-app/src/main/kotlin/home/Searchbar.kt b/web-app/src/main/kotlin/home/Searchbar.kt
index c611f1a3..30b9bdaa 100644
--- a/web-app/src/main/kotlin/home/Searchbar.kt
+++ b/web-app/src/main/kotlin/home/Searchbar.kt
@@ -22,32 +22,35 @@ import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onKeyDownFunction
import org.w3c.dom.HTMLInputElement
+import react.PropsWithChildren
import react.RBuilder
-import react.RProps
-import react.child
import react.dom.attrs
-import react.functionalComponent
-import styled.*
+import react.functionComponent
+import styled.css
+import styled.styledButton
+import styled.styledDiv
+import styled.styledImg
+import styled.styledInput
-external interface SearchbarProps : RProps {
+external interface SearchbarProps : PropsWithChildren {
var link: String
- var search:(String)->Unit
- var onLinkChange:(String)->Unit
+ var search: (String) -> Unit
+ var onLinkChange: (String) -> Unit
}
@Suppress("FunctionName")
-fun RBuilder.SearchBar(handler:SearchbarProps.() -> Unit) = child(searchbar){
+fun RBuilder.SearchBar(handler: SearchbarProps.() -> Unit) = child(searchbar) {
attrs {
handler()
}
}
-val searchbar = functionalComponent("SearchBar"){ props ->
- styledDiv{
+val searchbar = functionComponent("SearchBar") { props ->
+ styledDiv {
css {
- classes = mutableListOf("searchBox")
+ classes.add("searchBox")
}
- styledInput(type = InputType.url){
+ styledInput(type = InputType.url) {
attrs {
placeholder = "Search"
onChangeFunction = {
@@ -55,30 +58,30 @@ val searchbar = functionalComponent("SearchBar"){ props ->
props.onLinkChange(target.value)
}
this.onKeyDownFunction = {
- if(it.asDynamic().key == "Enter") {
- if(props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms")
+ if (it.asDynamic().key == "Enter") {
+ if (props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms")
else props.search(props.link)
}
}
value = props.link
}
css {
- classes = mutableListOf("searchInput")
+ classes.add("searchInput")
}
}
styledButton {
attrs {
onClickFunction = {
- if(props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms")
+ if (props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms")
else props.search(props.link)
}
}
css {
- classes = mutableListOf("searchButton")
+ classes.add("searchButton")
}
styledImg(src = "search.svg") {
css {
- classes = mutableListOf("search-icon")
+ classes.add("search-icon")
}
}
}
diff --git a/web-app/src/main/kotlin/list/CircularProgressBar.kt b/web-app/src/main/kotlin/list/CircularProgressBar.kt
index 165a072d..42c51c6a 100644
--- a/web-app/src/main/kotlin/list/CircularProgressBar.kt
+++ b/web-app/src/main/kotlin/list/CircularProgressBar.kt
@@ -23,42 +23,44 @@ import kotlinx.css.justifyContent
import kotlinx.css.marginBottom
import kotlinx.css.px
import kotlinx.css.width
+import react.PropsWithChildren
import react.RBuilder
-import react.RProps
-import react.ReactElement
-import react.child
-import react.functionalComponent
+import react.functionComponent
import styled.css
import styled.styledDiv
import styled.styledSpan
@Suppress("FunctionName")
-fun RBuilder.CircularProgressBar(handler: CircularProgressBarProps.() -> Unit): ReactElement {
- return child(circularProgressBar){
+fun RBuilder.CircularProgressBar(handler: CircularProgressBarProps.() -> Unit) {
+ return child(circularProgressBar) {
attrs {
handler()
}
}
}
-external interface CircularProgressBarProps : RProps {
- var progress:Int
+external interface CircularProgressBarProps : PropsWithChildren {
+ var progress: Int
}
-private val circularProgressBar = functionalComponent("Circular-Progress-Bar") { props->
+private val circularProgressBar = functionComponent("Circular-Progress-Bar") { props ->
styledDiv {
styledSpan { +"${props.progress}%" }
- styledDiv{
+ styledDiv {
css {
- classes = mutableListOf("left-half-clipper")
+ classes.add("left-half-clipper")
}
- styledDiv{ css { classes = mutableListOf("first50-bar") } }
- styledDiv{ css { classes = mutableListOf("value-bar") } }
+ styledDiv { css { classes.add("first50-bar") } }
+ styledDiv { css { classes.add("value-bar") } }
}
- css{
+ css {
display = Display.flex
justifyContent = JustifyContent.center
- classes = mutableListOf("progress-circle","p${props.progress}").apply { if(props.progress>50) add("over50") }
+ classes.addAll(
+ mutableListOf(
+ "progress-circle",
+ "p${props.progress}"
+ ).apply { if (props.progress > 50) add("over50") })
width = 50.px
marginBottom = 65.px
}
diff --git a/web-app/src/main/kotlin/list/CoverImage.kt b/web-app/src/main/kotlin/list/CoverImage.kt
index da2d489b..419e1503 100644
--- a/web-app/src/main/kotlin/list/CoverImage.kt
+++ b/web-app/src/main/kotlin/list/CoverImage.kt
@@ -16,32 +16,45 @@
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 react.*
+import react.PropsWithChildren
+import react.RBuilder
import react.dom.attrs
+import react.functionComponent
import styled.css
import styled.styledDiv
import styled.styledH1
import styled.styledImg
-external interface CoverImageProps : RProps {
+external interface CoverImageProps : PropsWithChildren {
var coverImageURL: String
var coverName: String
}
@Suppress("FunctionName")
-fun RBuilder.CoverImage(handler: CoverImageProps.() -> Unit): ReactElement {
- return child(coverImage){
+fun RBuilder.CoverImage(handler: CoverImageProps.() -> Unit) {
+ return child(coverImage) {
attrs {
handler()
}
}
}
-private val coverImage = functionalComponent("CoverImage"){ props ->
+private val coverImage = functionComponent("CoverImage") { props ->
styledDiv {
- styledImg(src= props.coverImageURL){
+ styledImg(src = props.coverImageURL) {
css {
height = 220.px
width = 220.px
diff --git a/web-app/src/main/kotlin/list/DownloadAllButton.kt b/web-app/src/main/kotlin/list/DownloadAllButton.kt
index 6e79af2f..2dd4a0e9 100644
--- a/web-app/src/main/kotlin/list/DownloadAllButton.kt
+++ b/web-app/src/main/kotlin/list/DownloadAllButton.kt
@@ -16,52 +16,65 @@
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.js.onClickFunction
-import react.*
+import react.PropsWithChildren
+import react.RBuilder
import react.dom.attrs
+import react.functionComponent
+import react.useEffect
+import react.useState
import styled.css
import styled.styledDiv
import styled.styledH5
import styled.styledImg
-external interface DownloadAllButtonProps : RProps {
- var isActive:Boolean
- var link : String
- var downloadAll:()->Unit
+external interface DownloadAllButtonProps : PropsWithChildren {
+ var isActive: Boolean
+ var link: String
+ var downloadAll: () -> Unit
}
@Suppress("FunctionName")
-fun RBuilder.DownloadAllButton(handler: DownloadAllButtonProps.() -> Unit): ReactElement {
- return child(downloadAllButton){
+fun RBuilder.DownloadAllButton(handler: DownloadAllButtonProps.() -> Unit) {
+ return child(downloadAllButton) {
attrs {
handler()
}
}
}
-private val downloadAllButton = functionalComponent("DownloadAllButton") { props->
+private val downloadAllButton = functionComponent("DownloadAllButton") { props ->
- val (isClicked,setClicked) = useState(false)
+ val (isClicked, setClicked) = useState(false)
- useEffect(mutableListOf(props.link)){
+ useEffect(mutableListOf(props.link)) {
setClicked(false)
}
- if(props.isActive){
- if(isClicked) {
- styledDiv{
+ if (props.isActive) {
+ if (isClicked) {
+ styledDiv {
css {
display = Display.flex
alignItems = Align.center
justifyContent = JustifyContent.center
height = 52.px
}
- LoadingSpinner { }
+ LoadingSpinner { }
}
- }
- else{
+ } else {
styledDiv {
attrs {
onClickFunction = {
@@ -71,9 +84,9 @@ private val downloadAllButton = functionalComponent("Dow
}
styledDiv {
- styledImg(src = "download.svg",alt = "Download All Button") {
+ styledImg(src = "download.svg", alt = "Download All Button") {
css {
- classes = mutableListOf("download-all-icon")
+ classes.add("download-all-icon")
height = 32.px
}
}
@@ -82,7 +95,7 @@ private val downloadAllButton = functionalComponent("Dow
attrs {
id = "download-all-text"
}
- + "Download All"
+ +"Download All"
css {
whiteSpace = WhiteSpace.nowrap
fontSize = 15.px
@@ -90,13 +103,13 @@ private val downloadAllButton = functionalComponent("Dow
}
css {
- classes = mutableListOf("download-icon")
+ classes.add("download-icon")
display = Display.flex
alignItems = Align.center
}
}
css {
- classes = mutableListOf("download-button")
+ classes.add("download-button")
display = Display.flex
alignItems = Align.center
}
diff --git a/web-app/src/main/kotlin/list/DownloadButton.kt b/web-app/src/main/kotlin/list/DownloadButton.kt
index 922105c8..0b0f03a8 100644
--- a/web-app/src/main/kotlin/list/DownloadButton.kt
+++ b/web-app/src/main/kotlin/list/DownloadButton.kt
@@ -17,31 +17,37 @@
package list
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 react.*
+import react.PropsWithChildren
+import react.RBuilder
import react.dom.attrs
+import react.functionComponent
import styled.css
import styled.styledDiv
import styled.styledImg
@Suppress("FunctionName")
-fun RBuilder.DownloadButton(handler: DownloadButtonProps.() -> Unit): ReactElement {
- return child(downloadButton){
+fun RBuilder.DownloadButton(handler: DownloadButtonProps.() -> Unit) {
+ return child(downloadButton) {
attrs {
handler()
}
}
}
-external interface DownloadButtonProps : RProps {
- var onClick:()->Unit
- var status :DownloadStatus
+external interface DownloadButtonProps : PropsWithChildren {
+ var onClick: () -> Unit
+ var status: DownloadStatus
}
-private val downloadButton = functionalComponent("Circular-Progress-Bar") { props->
+private val downloadButton = functionComponent("Circular-Progress-Bar") { props ->
styledDiv {
- val src = when(props.status){
+ val src = when (props.status) {
is DownloadStatus.NotDownloaded -> "download-gradient.svg"
is DownloadStatus.Downloaded -> "check.svg"
is DownloadStatus.Failed -> "error.svg"
@@ -59,7 +65,7 @@ private val downloadButton = functionalComponent("Circular-
}
}
css {
- classes = mutableListOf("glow-button")
+ classes.add("glow-button")
borderRadius = 100.px
}
}
diff --git a/web-app/src/main/kotlin/list/ListScreen.kt b/web-app/src/main/kotlin/list/ListScreen.kt
index 4fb0a767..ab9f3a51 100644
--- a/web-app/src/main/kotlin/list/ListScreen.kt
+++ b/web-app/src/main/kotlin/list/ListScreen.kt
@@ -84,7 +84,7 @@ class ListScreen(
}
css {
- classes = mutableListOf("list-screen")
+ classes.add("list-screen")
display = Display.flex
padding(8.px)
flexDirection = FlexDirection.column
diff --git a/web-app/src/main/kotlin/list/LoadingAnim.kt b/web-app/src/main/kotlin/list/LoadingAnim.kt
index f618c280..9d738f94 100644
--- a/web-app/src/main/kotlin/list/LoadingAnim.kt
+++ b/web-app/src/main/kotlin/list/LoadingAnim.kt
@@ -24,43 +24,41 @@ import kotlinx.css.flexGrow
import kotlinx.css.height
import kotlinx.css.px
import kotlinx.css.width
+import react.PropsWithChildren
import react.RBuilder
-import react.RProps
-import react.ReactElement
-import react.child
-import react.functionalComponent
+import react.functionComponent
import styled.css
import styled.styledDiv
@Suppress("FunctionName")
-fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement {
- return child(loadingAnim){
+fun RBuilder.LoadingAnim(handler: PropsWithChildren.() -> Unit) {
+ return child(loadingAnim) {
attrs {
handler()
}
}
}
-private val loadingAnim = functionalComponent("Loading Animation") {
- styledDiv{
+private val loadingAnim = functionComponent("Loading Animation") {
+ styledDiv {
css {
flexGrow = 1.0
display = Display.flex
alignItems = Align.center
}
styledDiv {
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube1") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube2") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube3") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube4") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube5") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube6") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube7") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube8") } }
- styledDiv { css { classes = mutableListOf("sk-cube sk-cube9") } }
+ styledDiv { css { classes.add("sk-cube sk-cube1") } }
+ styledDiv { css { classes.add("sk-cube sk-cube2") } }
+ styledDiv { css { classes.add("sk-cube sk-cube3") } }
+ styledDiv { css { classes.add("sk-cube sk-cube4") } }
+ styledDiv { css { classes.add("sk-cube sk-cube5") } }
+ styledDiv { css { classes.add("sk-cube sk-cube6") } }
+ styledDiv { css { classes.add("sk-cube sk-cube7") } }
+ styledDiv { css { classes.add("sk-cube sk-cube8") } }
+ styledDiv { css { classes.add("sk-cube sk-cube9") } }
css {
- classes = mutableListOf("sk-cube-grid")
+ classes.add("sk-cube-grid")
height = 60.px
width = 60.px
}
diff --git a/web-app/src/main/kotlin/list/LoadingSpinner.kt b/web-app/src/main/kotlin/list/LoadingSpinner.kt
index a03cb107..2bd9cece 100644
--- a/web-app/src/main/kotlin/list/LoadingSpinner.kt
+++ b/web-app/src/main/kotlin/list/LoadingSpinner.kt
@@ -19,31 +19,29 @@ package list
import kotlinx.css.marginRight
import kotlinx.css.px
import kotlinx.css.width
+import react.PropsWithChildren
import react.RBuilder
-import react.RProps
-import react.ReactElement
-import react.child
-import react.functionalComponent
+import react.functionComponent
import styled.css
import styled.styledDiv
@Suppress("FunctionName")
-fun RBuilder.LoadingSpinner(handler: RProps.() -> Unit): ReactElement {
- return child(loadingSpinner){
+fun RBuilder.LoadingSpinner(handler: PropsWithChildren.() -> Unit) {
+ return child(loadingSpinner) {
attrs {
handler()
}
}
}
-private val loadingSpinner = functionalComponent("Loading-Spinner") {
+private val loadingSpinner = functionComponent("Loading-Spinner") {
styledDiv {
- styledDiv{}
- styledDiv{}
- styledDiv{}
- styledDiv{}
- css{
- classes = mutableListOf("lds-ring")
+ styledDiv {}
+ styledDiv {}
+ styledDiv {}
+ styledDiv {}
+ css {
+ classes.add("lds-ring")
width = 50.px
marginRight = 8.px
}
diff --git a/web-app/src/main/kotlin/list/TrackItem.kt b/web-app/src/main/kotlin/list/TrackItem.kt
index 1d66d6b3..df04c40a 100644
--- a/web-app/src/main/kotlin/list/TrackItem.kt
+++ b/web-app/src/main/kotlin/list/TrackItem.kt
@@ -18,30 +18,61 @@ package list
import com.shabinder.common.models.DownloadStatus
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 react.*
+import react.PropsWithChildren
+import react.RBuilder
import react.dom.attrs
-import styled.*
+import react.functionComponent
+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 {
- var details:TrackDetails
- var downloadTrack:(TrackDetails)->Unit
+external interface TrackItemProps : PropsWithChildren {
+ var details: TrackDetails
+ var downloadTrack: (TrackDetails) -> Unit
}
@Suppress("FunctionName")
-fun RBuilder.TrackItem(handler: TrackItemProps.() -> Unit): ReactElement {
- return child(trackItem){
+fun RBuilder.TrackItem(handler: TrackItemProps.() -> Unit) {
+ return child(trackItem) {
attrs {
handler()
}
}
}
-private val trackItem = functionalComponent("Track-Item"){ props ->
- val (downloadStatus,setDownloadStatus) = useState(props.details.downloaded)
+private val trackItem = functionComponent("Track-Item") { props ->
+ val (downloadStatus, setDownloadStatus) = useState(props.details.downloaded)
val details = props.details
- useEffect(listOf(props.details)){
+ useEffect(listOf(props.details)) {
setDownloadStatus(props.details.downloaded)
}
styledDiv {
@@ -63,14 +94,14 @@ private val trackItem = functionalComponent("Track-Item"){ props
flexDirection = FlexDirection.column
margin(8.px)
}
- styledDiv{
+ styledDiv {
css {
height = 40.px
alignItems = Align.center
display = Display.flex
}
styledH3 {
- + details.title
+ +details.title
css {
padding(8.px)
fontSize = 1.3.em
@@ -87,7 +118,7 @@ private val trackItem = functionalComponent("Track-Item"){ props
display = Display.flex
}
styledH4 {
- + details.artists.joinToString(",")
+ +details.artists.joinToString(",")
css {
flexGrow = 1.0
padding(8.px)
@@ -109,12 +140,12 @@ private val trackItem = functionalComponent("Track-Item"){ props
whiteSpace = WhiteSpace.nowrap
overflow = Overflow.hidden
}
- + "${details.durationSec/60} min, ${details.durationSec%60} sec"
+ +"${details.durationSec / 60} min, ${details.durationSec % 60} sec"
}
}
}
- when(downloadStatus){
- is DownloadStatus.NotDownloaded ->{
+ when (downloadStatus) {
+ is DownloadStatus.NotDownloaded -> {
DownloadButton {
onClick = {
setDownloadStatus(DownloadStatus.Queued)
@@ -152,7 +183,7 @@ private val trackItem = functionalComponent("Track-Item"){ props
css {
alignItems = Align.center
- display =Display.flex
+ display = Display.flex
paddingRight = 16.px
}
}
diff --git a/web-app/src/main/kotlin/navbar/NavBar.kt b/web-app/src/main/kotlin/navbar/NavBar.kt
index afd781c8..4903a57e 100644
--- a/web-app/src/main/kotlin/navbar/NavBar.kt
+++ b/web-app/src/main/kotlin/navbar/NavBar.kt
@@ -16,36 +16,55 @@
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.js.onBlurFunction
import kotlinx.html.js.onClickFunction
-import react.*
+import react.RBuilder
+import react.RProps
import react.dom.attrs
-import styled.*
+import react.functionComponent
+import styled.css
+import styled.styledA
+import styled.styledDiv
+import styled.styledH1
+import styled.styledImg
+import styled.styledNav
@Suppress("FunctionName")
-fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{
- return child(navBar){
+fun RBuilder.NavBar(handler: NavBarProps.() -> Unit) {
+ return child(navBar) {
attrs {
handler()
}
}
}
-external interface NavBarProps:RProps{
+external interface NavBarProps : RProps {
var isBackVisible: Boolean
var popBackToHomeScreen: () -> Unit
}
-private val navBar = functionalComponent("NavBar") { props ->
+private val navBar = functionComponent("NavBar") { props ->
styledNav {
css {
+NavBarStyles.nav
}
- styledDiv{
+ styledDiv {
attrs {
onClickFunction = {
props.popBackToHomeScreen()
@@ -54,22 +73,22 @@ private val navBar = functionalComponent("NavBar") { props ->
props.popBackToHomeScreen()
}
}
- styledImg(src = "left-arrow.svg",alt = "Back Arrow"){
+ styledImg(src = "left-arrow.svg", alt = "Back Arrow") {
css {
height = 42.px
width = 42.px
- display = if(props.isBackVisible) Display.inline else Display.none
+ display = if (props.isBackVisible) Display.inline else Display.none
filter = "invert(100)"
marginRight = 12.px
}
}
}
- styledA(href = "https://shabinder.github.io/SpotiFlyer/",target="_blank") {
+ styledA(href = "https://shabinder.github.io/SpotiFlyer/", target = "_blank") {
css {
display = Display.flex
alignItems = Align.center
}
- styledImg(src = "spotiflyer.svg",alt = "Logo") {
+ styledImg(src = "spotiflyer.svg", alt = "Logo") {
css {
height = 42.px
width = 42.px
@@ -80,7 +99,7 @@ private val navBar = functionalComponent("NavBar") { props ->
attrs {
id = "appName"
}
- css{
+ css {
fontSize = 46.px
margin(horizontal = 14.px)
}
@@ -93,7 +112,7 @@ private val navBar = functionalComponent("NavBar") { props ->
setCorsMode(corsProxy)
}*/
- styledDiv{
+ styledDiv {
/*styledH4 { + "Extension" }
@@ -125,8 +144,8 @@ private val navBar = functionalComponent("NavBar") { props ->
}
}*/
- styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){
- styledImg(src = "github.svg"){
+ styledA(href = "https://github.com/Shabinder/SpotiFlyer/") {
+ styledImg(src = "github.svg") {
css {
height = 42.px
width = 42.px
diff --git a/web-app/src/main/kotlin/root/RootR.kt b/web-app/src/main/kotlin/root/RootR.kt
index 5b4a76af..b7a1c4d5 100644
--- a/web-app/src/main/kotlin/root/RootR.kt
+++ b/web-app/src/main/kotlin/root/RootR.kt
@@ -26,7 +26,6 @@ import home.HomeScreen
import list.ListScreen
import navbar.NavBar
import react.RBuilder
-import react.RState
class RootR(props: Props) : RenderableComponent(
props = props,
@@ -58,4 +57,4 @@ class RootR(props: Props) : RenderableComponent
-) : RState
+) : react.State