mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 01:04:31 +01:00
Code Cleaned & Ktlint Added
This commit is contained in:
parent
46e5e89a2e
commit
ccea676b77
@ -58,7 +58,6 @@ import com.shabinder.common.models.TrackDetails
|
|||||||
import com.shabinder.common.root.SpotiFlyerRoot
|
import com.shabinder.common.root.SpotiFlyerRoot
|
||||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||||
import com.shabinder.common.uikit.*
|
import com.shabinder.common.uikit.*
|
||||||
import com.shabinder.database.Database
|
|
||||||
import com.shabinder.spotiflyer.utils.*
|
import com.shabinder.spotiflyer.utils.*
|
||||||
import com.tonyodev.fetch2.Status
|
import com.tonyodev.fetch2.Status
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
`kotlin-dsl`
|
`kotlin-dsl`
|
||||||
|
id("org.jlleitschuh.gradle.ktlint")
|
||||||
|
id("org.jlleitschuh.gradle.ktlint-idea")
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
`kotlin-dsl`
|
`kotlin-dsl`
|
||||||
//`kotlin-dsl-precompiled-script-plugins`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "com.shabinder"
|
group = "com.shabinder"
|
||||||
@ -28,6 +27,7 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven(url = "https://jitpack.io")
|
maven(url = "https://jitpack.io")
|
||||||
|
maven(url = "https://plugins.gradle.org/m2/")
|
||||||
maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers")
|
maven(url = "https://dl.bintray.com/kotlin/kotlin-js-wrappers")
|
||||||
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||||
}
|
}
|
||||||
@ -37,6 +37,7 @@ dependencies {
|
|||||||
implementation("com.google.gms:google-services:4.3.5")
|
implementation("com.google.gms:google-services:4.3.5")
|
||||||
implementation("com.google.firebase:perf-plugin:1.3.5")
|
implementation("com.google.firebase:perf-plugin:1.3.5")
|
||||||
implementation("com.google.firebase:firebase-crashlytics-gradle:2.5.1")
|
implementation("com.google.firebase:firebase-crashlytics-gradle:2.5.1")
|
||||||
|
implementation("org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}")
|
||||||
implementation(JetBrains.Compose.gradlePlugin)
|
implementation(JetBrains.Compose.gradlePlugin)
|
||||||
implementation(JetBrains.Kotlin.gradlePlugin)
|
implementation(JetBrains.Kotlin.gradlePlugin)
|
||||||
implementation(JetBrains.Kotlin.serialization)
|
implementation(JetBrains.Kotlin.serialization)
|
||||||
|
@ -21,9 +21,12 @@ object Versions {
|
|||||||
const val kotlinVersion = "1.4.31"
|
const val kotlinVersion = "1.4.31"
|
||||||
|
|
||||||
const val coroutinesVersion = "1.4.2"
|
const val coroutinesVersion = "1.4.2"
|
||||||
//const val compose = "1.0.0-alpha12"
|
|
||||||
|
|
||||||
const val coilVersion = "0.4.1"
|
const val coilVersion = "0.4.1"
|
||||||
|
|
||||||
|
// Code Formatting
|
||||||
|
const val ktLint = "10.0.0"
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
const val koin = "3.0.1-beta-1"
|
const val koin = "3.0.1-beta-1"
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
|
id("ktlint-setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -43,5 +44,4 @@ android {
|
|||||||
res.srcDirs("src/androidMain/res")
|
res.srcDirs("src/androidMain/res")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,31 @@
|
|||||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.common.di
|
plugins {
|
||||||
|
id("org.jlleitschuh.gradle.ktlint")
|
||||||
sealed class NetworkResponse<out T> {
|
id("org.jlleitschuh.gradle.ktlint-idea")
|
||||||
data class Success<T>(val value:T):NetworkResponse<T>()
|
}
|
||||||
data class Error(val message:String):NetworkResponse<Nothing>()
|
|
||||||
|
subprojects {
|
||||||
|
apply(plugin = "org.jlleitschuh.gradle.ktlint")
|
||||||
|
apply(plugin = "org.jlleitschuh.gradle.ktlint-idea")
|
||||||
|
repositories {
|
||||||
|
// Required to download KtLint
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
ktlint {
|
||||||
|
android.set(true)
|
||||||
|
outputToConsole.set(true)
|
||||||
|
ignoreFailures.set(true)
|
||||||
|
coloredOutput.set(true)
|
||||||
|
verbose.set(true)
|
||||||
|
filter {
|
||||||
|
exclude("**/generated/**")
|
||||||
|
exclude("**/build/**")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Optionally configure plugin
|
||||||
|
/*configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
||||||
|
debug.set(true)
|
||||||
|
}*/
|
||||||
}
|
}
|
@ -18,6 +18,7 @@ plugins {
|
|||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-multiplatform")
|
id("kotlin-multiplatform")
|
||||||
id("org.jetbrains.compose")
|
id("org.jetbrains.compose")
|
||||||
|
id("ktlint-setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("kotlin-multiplatform")
|
id("kotlin-multiplatform")
|
||||||
|
id("ktlint-setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -47,9 +48,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
named("jsTest") {
|
named("jsTest") {
|
||||||
dependencies {
|
dependencies {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,17 +14,11 @@
|
|||||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.compose
|
|
||||||
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.kotlin
|
|
||||||
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.sourceSets
|
|
||||||
import org.gradle.kotlin.dsl.withType
|
|
||||||
import org.jetbrains.compose.compose
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// id("com.android.library")
|
|
||||||
id("android-setup")
|
id("android-setup")
|
||||||
id("kotlin-multiplatform")
|
id("kotlin-multiplatform")
|
||||||
id("org.jetbrains.compose")
|
id("org.jetbrains.compose")
|
||||||
|
id("ktlint-setup")
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -37,9 +31,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
named("commonMain") {
|
named("commonMain") {
|
||||||
dependencies {
|
dependencies {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
named("androidMain") {
|
named("androidMain") {
|
||||||
|
@ -21,7 +21,13 @@ package com.shabinder.common.uikit
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -68,7 +74,6 @@ actual fun pristineFont() = FontFamily(
|
|||||||
Font(R.font.pristine_script, FontWeight.Bold)
|
Font(R.font.pristine_script, FontWeight.Bold)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun DownloadImageTick() {
|
actual fun DownloadImageTick() {
|
||||||
Image(
|
Image(
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
@file:Suppress("FunctionName")
|
@file:Suppress("FunctionName")
|
||||||
package com.shabinder.common.uikit
|
package com.shabinder.common.uikit
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import com.shabinder.common.di.Picture
|
import com.shabinder.common.di.Picture
|
||||||
|
@ -17,16 +17,30 @@
|
|||||||
package com.shabinder.common.uikit
|
package com.shabinder.common.uikit
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material.ExtendedFloatingActionButton
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -35,7 +49,6 @@ import com.shabinder.common.di.Picture
|
|||||||
import com.shabinder.common.list.SpotiFlyerList
|
import com.shabinder.common.list.SpotiFlyerList
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SpotiFlyerListContent(
|
fun SpotiFlyerListContent(
|
||||||
@ -44,8 +57,6 @@ fun SpotiFlyerListContent(
|
|||||||
) {
|
) {
|
||||||
val model by component.models.collectAsState(SpotiFlyerList.State())
|
val model by component.models.collectAsState(SpotiFlyerList.State())
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
// TODO Better Null Handling
|
// TODO Better Null Handling
|
||||||
val result = model.queryResult
|
val result = model.queryResult
|
||||||
@ -60,7 +71,7 @@ fun SpotiFlyerListContent(
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
content = {
|
content = {
|
||||||
item {
|
item {
|
||||||
CoverImage(result.title, result.coverUrl, coroutineScope,component::loadImage)
|
CoverImage(result.title, result.coverUrl, component::loadImage)
|
||||||
}
|
}
|
||||||
itemsIndexed(model.trackList) { index, item ->
|
itemsIndexed(model.trackList) { index, item ->
|
||||||
TrackCard(
|
TrackCard(
|
||||||
@ -124,9 +135,13 @@ fun TrackCard(
|
|||||||
CircularProgressIndicator(progress = 100f, color = colorAccent)
|
CircularProgressIndicator(progress = 100f, color = colorAccent)
|
||||||
}
|
}
|
||||||
is DownloadStatus.NotDownloaded -> {
|
is DownloadStatus.NotDownloaded -> {
|
||||||
DownloadImageArrow(Modifier.clickable(onClick = {
|
DownloadImageArrow(
|
||||||
|
Modifier.clickable(
|
||||||
|
onClick = {
|
||||||
downloadTrack()
|
downloadTrack()
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +151,6 @@ fun TrackCard(
|
|||||||
fun CoverImage(
|
fun CoverImage(
|
||||||
title: String,
|
title: String,
|
||||||
coverURL: String,
|
coverURL: String,
|
||||||
scope: CoroutineScope,
|
|
||||||
loadImage: suspend (String) -> Picture,
|
loadImage: suspend (String) -> Picture,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@ -160,7 +174,6 @@ fun CoverImage(
|
|||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
//color = colorAccent,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/*scope.launch {
|
/*scope.launch {
|
||||||
|
@ -17,26 +17,54 @@
|
|||||||
package com.shabinder.common.uikit
|
package com.shabinder.common.uikit
|
||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedButton
|
||||||
|
import androidx.compose.material.Tab
|
||||||
|
import androidx.compose.material.TabPosition
|
||||||
|
import androidx.compose.material.TabRow
|
||||||
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
|
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextField
|
||||||
import androidx.compose.material.TextFieldDefaults.textFieldColors
|
import androidx.compose.material.TextFieldDefaults.textFieldColors
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.History
|
import androidx.compose.material.icons.outlined.History
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.rounded.*
|
import androidx.compose.material.icons.rounded.CardGiftcard
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
|
import androidx.compose.material.icons.rounded.Flag
|
||||||
|
import androidx.compose.material.icons.rounded.Share
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
@ -146,10 +174,15 @@ fun SearchPanel(
|
|||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||||
modifier = modifier.padding(12.dp).fillMaxWidth()
|
modifier = modifier.padding(12.dp).fillMaxWidth()
|
||||||
.border(
|
.border(
|
||||||
BorderStroke(2.dp, Brush.horizontalGradient(listOf(
|
BorderStroke(
|
||||||
|
2.dp,
|
||||||
|
Brush.horizontalGradient(
|
||||||
|
listOf(
|
||||||
colorPrimary,
|
colorPrimary,
|
||||||
colorAccent
|
colorAccent
|
||||||
))),
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
RoundedCornerShape(30.dp)
|
RoundedCornerShape(30.dp)
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(size = 30.dp),
|
shape = RoundedCornerShape(size = 30.dp),
|
||||||
@ -168,10 +201,15 @@ fun SearchPanel(
|
|||||||
onSearch(link)
|
onSearch(link)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(
|
border = BorderStroke(
|
||||||
|
1.dp,
|
||||||
|
Brush.horizontalGradient(
|
||||||
|
listOf(
|
||||||
colorPrimary,
|
colorPrimary,
|
||||||
colorAccent
|
colorAccent
|
||||||
)))
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text(text = "Search", style = SpotiFlyerTypography.h6, modifier = Modifier.padding(4.dp))
|
Text(text = "Search", style = SpotiFlyerTypography.h6, modifier = Modifier.padding(4.dp))
|
||||||
}
|
}
|
||||||
@ -199,28 +237,35 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
"Open Spotify",
|
"Open Spotify",
|
||||||
tint = Color.Unspecified,
|
tint = Color.Unspecified,
|
||||||
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
||||||
onClick = { openPlatform("com.spotify.music","http://open.spotify.com") })
|
onClick = { openPlatform("com.spotify.music", "http://open.spotify.com") }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
Spacer(modifier = modifier.padding(start = 16.dp))
|
Spacer(modifier = modifier.padding(start = 16.dp))
|
||||||
Icon(imageVector = GaanaLogo(),
|
Icon(
|
||||||
|
imageVector = GaanaLogo(),
|
||||||
"Open Gaana",
|
"Open Gaana",
|
||||||
tint = Color.Unspecified,
|
tint = Color.Unspecified,
|
||||||
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
||||||
onClick = { openPlatform("com.gaana","http://gaana.com") })
|
onClick = { openPlatform("com.gaana", "http://gaana.com") }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
Spacer(modifier = modifier.padding(start = 16.dp))
|
Spacer(modifier = modifier.padding(start = 16.dp))
|
||||||
Icon(imageVector = YoutubeLogo(),
|
Icon(
|
||||||
|
imageVector = YoutubeLogo(),
|
||||||
"Open Youtube",
|
"Open Youtube",
|
||||||
tint = Color.Unspecified,
|
tint = Color.Unspecified,
|
||||||
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
||||||
onClick = { openPlatform("com.google.android.youtube","http://m.youtube.com") })
|
onClick = { openPlatform("com.google.android.youtube", "http://m.youtube.com") }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
Spacer(modifier = modifier.padding(start = 12.dp))
|
Spacer(modifier = modifier.padding(start = 12.dp))
|
||||||
Icon(imageVector = YoutubeMusicLogo(),
|
Icon(
|
||||||
|
imageVector = YoutubeMusicLogo(),
|
||||||
"Open Youtube Music",
|
"Open Youtube Music",
|
||||||
tint = Color.Unspecified,
|
tint = Color.Unspecified,
|
||||||
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
|
||||||
onClick = { openPlatform("com.google.android.apps.youtube.music","https://music.youtube.com/") })
|
onClick = { openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,9 +282,11 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
color = colorAccent
|
color = colorAccent
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.padding(top = 6.dp))
|
Spacer(modifier = Modifier.padding(top = 6.dp))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically,
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth().clickable(
|
modifier = Modifier.fillMaxWidth().clickable(
|
||||||
onClick = { openPlatform("","http://github.com/Shabinder/SpotiFlyer") })
|
onClick = { openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }
|
||||||
|
)
|
||||||
.padding(vertical = 6.dp)
|
.padding(vertical = 6.dp)
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = GithubLogo(), "Open Project Repo", tint = Color(0xFFCCCCCC))
|
Icon(imageVector = GithubLogo(), "Open Project Repo", tint = Color(0xFFCCCCCC))
|
||||||
@ -293,9 +340,11 @@ fun AboutColumn(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||||
.clickable(onClick = {
|
.clickable(
|
||||||
|
onClick = {
|
||||||
shareApp()
|
shareApp()
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Rounded.Share, "Share SpotiFlyer App")
|
Icon(Icons.Rounded.Share, "Share SpotiFlyer App")
|
||||||
@ -325,7 +374,8 @@ fun HistoryColumn(
|
|||||||
Crossfade(list) {
|
Crossfade(list) {
|
||||||
if (it.isEmpty()) {
|
if (it.isEmpty()) {
|
||||||
Column(Modifier.padding(bottom = 32.dp).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(Modifier.padding(bottom = 32.dp).fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Icon(Icons.Outlined.Info,"No History Available Yet",modifier = Modifier.size(80.dp),
|
Icon(
|
||||||
|
Icons.Outlined.Info, "No History Available Yet", modifier = Modifier.size(80.dp),
|
||||||
colorOffWhite
|
colorOffWhite
|
||||||
)
|
)
|
||||||
Text("No History Available", style = SpotiFlyerTypography.h4.copy(fontWeight = FontWeight.Light), textAlign = TextAlign.Center)
|
Text("No History Available", style = SpotiFlyerTypography.h4.copy(fontWeight = FontWeight.Light), textAlign = TextAlign.Center)
|
||||||
@ -375,15 +425,16 @@ fun DownloadRecordItem(
|
|||||||
Image(
|
Image(
|
||||||
imageVector = ShareImage(),
|
imageVector = ShareImage(),
|
||||||
"Research",
|
"Research",
|
||||||
modifier = Modifier.clickable(onClick = {
|
modifier = Modifier.clickable(
|
||||||
|
onClick = {
|
||||||
// if(!isOnline(ctx)) showDialog("Check Your Internet Connection") else
|
// if(!isOnline(ctx)) showDialog("Check Your Internet Connection") else
|
||||||
onItemClicked(item.link)
|
onItemClicked(item.link)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeCategoryTabIndicator(
|
fun HomeCategoryTabIndicator(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -16,11 +16,24 @@
|
|||||||
|
|
||||||
package com.shabinder.common.uikit
|
package com.shabinder.common.uikit
|
||||||
|
|
||||||
import androidx.compose.animation.core.*
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
import androidx.compose.animation.core.Spring.StiffnessLow
|
import androidx.compose.animation.core.Spring.StiffnessLow
|
||||||
|
import androidx.compose.animation.core.animateDp
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TopAppBar
|
import androidx.compose.material.TopAppBar
|
||||||
|
@ -20,7 +20,7 @@ import androidx.compose.material.MaterialTheme
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SpotiFlyerTheme(content: @Composable() () -> Unit) {
|
fun SpotiFlyerTheme(content: @Composable () -> Unit) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colors = SpotiFlyerColors,
|
colors = SpotiFlyerColors,
|
||||||
typography = SpotiFlyerTypography,
|
typography = SpotiFlyerTypography,
|
||||||
|
@ -23,7 +23,6 @@ import androidx.compose.ui.text.font.FontFamily
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
|
||||||
expect fun montserratFont(): FontFamily
|
expect fun montserratFont(): FontFamily
|
||||||
expect fun pristineFont(): FontFamily
|
expect fun pristineFont(): FontFamily
|
||||||
|
|
||||||
|
@ -17,7 +17,13 @@
|
|||||||
package com.shabinder.common.uikit.splash
|
package com.shabinder.common.uikit.splash
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -29,7 +35,11 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.shabinder.common.uikit.*
|
import com.shabinder.common.uikit.HeartIcon
|
||||||
|
import com.shabinder.common.uikit.SpotiFlyerLogo
|
||||||
|
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||||
|
import com.shabinder.common.uikit.colorAccent
|
||||||
|
import com.shabinder.common.uikit.colorPrimary
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
private const val SplashWaitTime: Long = 2000
|
private const val SplashWaitTime: Long = 2000
|
||||||
|
@ -40,8 +40,10 @@ import kotlin.math.pow
|
|||||||
*/
|
*/
|
||||||
fun Modifier.verticalGradientScrim(
|
fun Modifier.verticalGradientScrim(
|
||||||
color: Color,
|
color: Color,
|
||||||
/*@FloatRange(from = 0.0, to = 1.0)*/ startYPercentage: Float = 0f,
|
/*@FloatRange(from = 0.0, to = 1.0)*/
|
||||||
/*@FloatRange(from = 0.0, to = 1.0)*/ endYPercentage: Float = 1f,
|
startYPercentage: Float = 0f,
|
||||||
|
/*@FloatRange(from = 0.0, to = 1.0)*/
|
||||||
|
endYPercentage: Float = 1f,
|
||||||
decay: Float = 1.0f,
|
decay: Float = 1.0f,
|
||||||
numStops: Int = 16,
|
numStops: Int = 16,
|
||||||
fixedHeight: Float? = null
|
fixedHeight: Float? = null
|
||||||
|
@ -19,7 +19,12 @@ package com.shabinder.common.uikit
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -97,7 +102,6 @@ actual fun ShareImage():ImageVector = vectorXmlResource("drawable/ic_share_open.
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun PlaceHolderImage(): ImageVector = vectorXmlResource("drawable/music.xml")
|
actual fun PlaceHolderImage(): ImageVector = vectorXmlResource("drawable/music.xml")
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun SpotiFlyerLogo(): ImageVector =
|
actual fun SpotiFlyerLogo(): ImageVector =
|
||||||
vectorXmlResource("drawable/ic_spotiflyer_logo.xml")
|
vectorXmlResource("drawable/ic_spotiflyer_logo.xml")
|
||||||
|
@ -41,7 +41,6 @@ data class TrackDetails(
|
|||||||
var videoID: String? = null,
|
var videoID: String? = null,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class DownloadStatus : Parcelable {
|
sealed class DownloadStatus : Parcelable {
|
||||||
@Parcelize object Downloaded : DownloadStatus()
|
@Parcelize object Downloaded : DownloadStatus()
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package com.shabinder.common.models.gaana
|
package com.shabinder.common.models.gaana
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -37,4 +37,5 @@ data class Album(
|
|||||||
var release_date_precision: String? = null,
|
var release_date_precision: String? = null,
|
||||||
var tracks: PagingObjectTrack? = null,
|
var tracks: PagingObjectTrack? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null
|
||||||
|
)
|
||||||
|
@ -25,4 +25,5 @@ data class Artist(
|
|||||||
var id: String? = null,
|
var id: String? = null,
|
||||||
var name: String? = null,
|
var name: String? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null
|
||||||
|
)
|
||||||
|
@ -21,4 +21,5 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Copyright(
|
data class Copyright(
|
||||||
var text: String? = null,
|
var text: String? = null,
|
||||||
var type: String? = null)
|
var type: String? = null
|
||||||
|
)
|
||||||
|
@ -21,4 +21,5 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Followers(
|
data class Followers(
|
||||||
var href: String? = null,
|
var href: String? = null,
|
||||||
var total: Int? = null)
|
var total: Int? = null
|
||||||
|
)
|
||||||
|
@ -22,4 +22,5 @@ import kotlinx.serialization.Serializable
|
|||||||
data class Image(
|
data class Image(
|
||||||
var width: Int? = null,
|
var width: Int? = null,
|
||||||
var height: Int? = null,
|
var height: Int? = null,
|
||||||
var url: String? = null)
|
var url: String? = null
|
||||||
|
)
|
||||||
|
@ -24,4 +24,5 @@ data class LinkedTrack(
|
|||||||
var href: String? = null,
|
var href: String? = null,
|
||||||
var id: String? = null,
|
var id: String? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null
|
||||||
|
)
|
||||||
|
@ -26,4 +26,5 @@ data class PagingObjectPlaylistTrack(
|
|||||||
var next: String? = null,
|
var next: String? = null,
|
||||||
var offset: Int = 0,
|
var offset: Int = 0,
|
||||||
var previous: String? = null,
|
var previous: String? = null,
|
||||||
var total: Int = 0)
|
var total: Int = 0
|
||||||
|
)
|
||||||
|
@ -26,4 +26,5 @@ data class PagingObjectTrack(
|
|||||||
var next: String? = null,
|
var next: String? = null,
|
||||||
var offset: Int = 0,
|
var offset: Int = 0,
|
||||||
var previous: String? = null,
|
var previous: String? = null,
|
||||||
var total: Int = 0)
|
var total: Int = 0
|
||||||
|
)
|
||||||
|
@ -34,4 +34,5 @@ data class Playlist(
|
|||||||
var snapshot_id: String? = null,
|
var snapshot_id: String? = null,
|
||||||
var tracks: PagingObjectPlaylistTrack? = null,
|
var tracks: PagingObjectPlaylistTrack? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null
|
||||||
|
)
|
||||||
|
@ -23,4 +23,5 @@ data class PlaylistTrack(
|
|||||||
var added_at: String? = null,
|
var added_at: String? = null,
|
||||||
var added_by: UserPublic? = null,
|
var added_by: UserPublic? = null,
|
||||||
var track: Track? = null,
|
var track: Track? = null,
|
||||||
var is_local: Boolean? = null)
|
var is_local: Boolean? = null
|
||||||
|
)
|
||||||
|
@ -40,4 +40,3 @@ data class Track(
|
|||||||
var popularity: Int? = null,
|
var popularity: Int? = null,
|
||||||
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
|
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,4 +30,5 @@ data class UserPrivate(
|
|||||||
var images: List<Image?>? = null,
|
var images: List<Image?>? = null,
|
||||||
var product: String,
|
var product: String,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null
|
||||||
|
)
|
||||||
|
@ -27,4 +27,5 @@ data class UserPublic(
|
|||||||
var id: String? = null,
|
var id: String? = null,
|
||||||
var images: List<Image?>? = null,
|
var images: List<Image?>? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null
|
||||||
|
)
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package com.shabinder.common.models.wynk
|
package com.shabinder.common.models.wynk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Use Kotlinx JSON Parsing as in YT Music
|
// Use Kotlinx JSON Parsing as in YT Music
|
||||||
data class ShortURLWynk(
|
data class ShortURLWynk(
|
||||||
val actualTotal: Int,
|
val actualTotal: Int,
|
||||||
|
@ -21,7 +21,6 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.mpatric.mp3agic.Mp3File
|
import com.mpatric.mp3agic.Mp3File
|
||||||
@ -63,16 +62,11 @@ actual class Dir actual constructor(
|
|||||||
actual fun createDirectory(dirPath: String) {
|
actual fun createDirectory(dirPath: String) {
|
||||||
val yourAppDir = File(dirPath)
|
val yourAppDir = File(dirPath)
|
||||||
|
|
||||||
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
|
if (!yourAppDir.exists() && !yourAppDir.isDirectory) { // create empty directory
|
||||||
{ // create empty directory
|
if (yourAppDir.mkdirs()) { logger.i { "$dirPath created" } } else {
|
||||||
if (yourAppDir.mkdirs())
|
|
||||||
{logger.i{"$dirPath created"}}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.e { "Unable to create Dir: $dirPath!" }
|
logger.e { "Unable to create Dir: $dirPath!" }
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
logger.i { "$dirPath already exists" }
|
logger.i { "$dirPath already exists" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +145,8 @@ actual class Dir actual constructor(
|
|||||||
logger.d { "Scanning File" }
|
logger.d { "Scanning File" }
|
||||||
MediaScannerConnection.scanFile(
|
MediaScannerConnection.scanFile(
|
||||||
appContext,
|
appContext,
|
||||||
listOf(path).toTypedArray(), null,null)
|
listOf(path).toTypedArray(), null, null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual suspend fun loadImage(url: String): Picture {
|
actual suspend fun loadImage(url: String): Picture {
|
||||||
|
@ -24,7 +24,6 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
|||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.shabinder.common.database.appContext
|
import com.shabinder.common.database.appContext
|
||||||
@ -83,17 +82,17 @@ class ConnectionLiveData(context: Context = appContext) : LiveData<Boolean>() {
|
|||||||
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network)
|
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network)
|
||||||
*/
|
*/
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
Log.d(TAG, "onAvailable: ${network}")
|
Log.d(TAG, "onAvailable: $network")
|
||||||
val networkCapabilities = cm.getNetworkCapabilities(network)
|
val networkCapabilities = cm.getNetworkCapabilities(network)
|
||||||
val hasInternetCapability = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET)
|
val hasInternetCapability = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET)
|
||||||
Log.d(TAG, "onAvailable: ${network}, $hasInternetCapability")
|
Log.d(TAG, "onAvailable: $network, $hasInternetCapability")
|
||||||
if (hasInternetCapability == true) {
|
if (hasInternetCapability == true) {
|
||||||
// check if this network actually has internet
|
// check if this network actually has internet
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory)
|
val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory)
|
||||||
if (hasInternet) {
|
if (hasInternet) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Log.d(TAG, "onAvailable: adding network. ${network}")
|
Log.d(TAG, "onAvailable: adding network. $network")
|
||||||
validNetworks.add(network)
|
validNetworks.add(network)
|
||||||
checkValidNetworks()
|
checkValidNetworks()
|
||||||
}
|
}
|
||||||
@ -107,11 +106,10 @@ class ConnectionLiveData(context: Context = appContext) : LiveData<Boolean>() {
|
|||||||
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network)
|
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network)
|
||||||
*/
|
*/
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
Log.d(TAG, "onLost: ${network}")
|
Log.d(TAG, "onLost: $network")
|
||||||
validNetworks.remove(network)
|
validNetworks.remove(network)
|
||||||
checkValidNetworks()
|
checkValidNetworks()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +16,11 @@
|
|||||||
|
|
||||||
package com.shabinder.common.di
|
package com.shabinder.common.di
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
@ -23,7 +23,7 @@ import com.shabinder.common.models.DownloadStatus
|
|||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.models.spotify.Source
|
import com.shabinder.common.models.spotify.Source
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ -17,15 +17,22 @@
|
|||||||
package com.shabinder.common.di.worker
|
package com.shabinder.common.di.worker
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.*
|
import android.app.DownloadManager
|
||||||
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
|
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
@ -33,18 +40,28 @@ import androidx.core.net.toUri
|
|||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.github.kiulian.downloader.model.formats.Format
|
import com.github.kiulian.downloader.model.formats.Format
|
||||||
import com.shabinder.common.database.R.*
|
|
||||||
import com.shabinder.common.di.Dir
|
import com.shabinder.common.di.Dir
|
||||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||||
|
import com.shabinder.common.di.R
|
||||||
import com.shabinder.common.di.getData
|
import com.shabinder.common.di.getData
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.tonyodev.fetch2.*
|
import com.tonyodev.fetch2.Download
|
||||||
|
import com.tonyodev.fetch2.Error
|
||||||
|
import com.tonyodev.fetch2.Fetch
|
||||||
|
import com.tonyodev.fetch2.FetchListener
|
||||||
|
import com.tonyodev.fetch2.NetworkType
|
||||||
|
import com.tonyodev.fetch2.Priority
|
||||||
|
import com.tonyodev.fetch2.Request
|
||||||
|
import com.tonyodev.fetch2.Status
|
||||||
import com.tonyodev.fetch2core.DownloadBlock
|
import com.tonyodev.fetch2core.DownloadBlock
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class ForegroundService : Service(), CoroutineScope {
|
class ForegroundService : Service(), CoroutineScope {
|
||||||
@ -78,7 +95,6 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
private val ytDownloader: YoutubeDownloader
|
private val ytDownloader: YoutubeDownloader
|
||||||
get() = fetcher.youtubeProvider.ytDownloader
|
get() = fetcher.youtubeProvider.ytDownloader
|
||||||
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? = null
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
|
|
||||||
@SuppressLint("UnspecifiedImmutableFlag")
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
@ -117,9 +133,11 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadObjects: ArrayList<TrackDetails>? = (it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
val downloadObjects: ArrayList<TrackDetails>? = (
|
||||||
|
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
||||||
"object"
|
"object"
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
downloadObjects?.let { list ->
|
downloadObjects?.let { list ->
|
||||||
downloadObjects.size.let { size ->
|
downloadObjects.size.let { size ->
|
||||||
@ -175,7 +193,6 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun downloadTrack(videoID: String, track: TrackDetails) {
|
private fun downloadTrack(videoID: String, track: TrackDetails) {
|
||||||
launch {
|
launch {
|
||||||
try {
|
try {
|
||||||
@ -193,19 +210,19 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun enqueueDownload(url: String, track: TrackDetails) {
|
private fun enqueueDownload(url: String, track: TrackDetails) {
|
||||||
val request = Request(url, track.outputFilePath).apply {
|
val request = Request(url, track.outputFilePath).apply {
|
||||||
priority = Priority.NORMAL
|
priority = Priority.NORMAL
|
||||||
networkType = NetworkType.ALL
|
networkType = NetworkType.ALL
|
||||||
}
|
}
|
||||||
fetch.enqueue(request,
|
fetch.enqueue(
|
||||||
|
request,
|
||||||
{ request1 ->
|
{ request1 ->
|
||||||
requestMap[request1] = track
|
requestMap[request1] = track
|
||||||
logger.d(tag) { "Enqueuing Download" }
|
logger.d(tag) { "Enqueuing Download" }
|
||||||
},
|
},
|
||||||
{ error ->
|
{ error ->
|
||||||
logger.d(tag){"Enqueuing Error:${error.throwable.toString()}"}
|
logger.d(tag) { "Enqueuing Error:${error.throwable}" }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -322,8 +339,7 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
launch {
|
launch {
|
||||||
requestMap[download.request]?.run {
|
requestMap[download.request]?.run {
|
||||||
allTracksStatus[title] = DownloadStatus.Downloading(download.progress)
|
allTracksStatus[title] = DownloadStatus.Downloading(download.progress)
|
||||||
logger.d(tag){"${title} ETA: ${etaInMilliSeconds / 1000} sec"}
|
logger.d(tag) { "$title ETA: ${etaInMilliSeconds / 1000} sec" }
|
||||||
|
|
||||||
|
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
action = "Progress"
|
action = "Progress"
|
||||||
@ -375,8 +391,6 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the method that can be called to update the Notification
|
* This is the method that can be called to update the Notification
|
||||||
*/
|
*/
|
||||||
@ -467,7 +481,7 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run {
|
private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run {
|
||||||
setSmallIcon(drawable.ic_download_arrow)
|
setSmallIcon(R.drawable.ic_download_arrow)
|
||||||
setContentTitle("Total: $total Completed:$converted Failed:$failed")
|
setContentTitle("Total: $total Completed:$converted Failed:$failed")
|
||||||
setSilent(true)
|
setSilent(true)
|
||||||
setStyle(
|
setStyle(
|
||||||
@ -479,7 +493,7 @@ class ForegroundService : Service(),CoroutineScope{
|
|||||||
addLine(messageList[messageList.size - 5])
|
addLine(messageList[messageList.size - 5])
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
addAction(drawable.ic_round_cancel_24,"Exit",cancelIntent)
|
addAction(R.drawable.ic_round_cancel_24, "Exit", cancelIntent)
|
||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,10 +23,13 @@ import com.shabinder.common.di.providers.GaanaProvider
|
|||||||
import com.shabinder.common.di.providers.SpotifyProvider
|
import com.shabinder.common.di.providers.SpotifyProvider
|
||||||
import com.shabinder.common.di.providers.YoutubeMp3
|
import com.shabinder.common.di.providers.YoutubeMp3
|
||||||
import com.shabinder.common.di.providers.YoutubeMusic
|
import com.shabinder.common.di.providers.YoutubeMusic
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.json.*
|
import io.ktor.client.features.json.JsonFeature
|
||||||
import io.ktor.client.features.json.serializer.*
|
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||||
import io.ktor.client.features.logging.*
|
import io.ktor.client.features.logging.DEFAULT
|
||||||
|
import io.ktor.client.features.logging.LogLevel
|
||||||
|
import io.ktor.client.features.logging.Logger
|
||||||
|
import io.ktor.client.features.logging.Logging
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import org.koin.dsl.KoinAppDeclaration
|
import org.koin.dsl.KoinAppDeclaration
|
||||||
@ -51,10 +54,12 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
|
|||||||
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) }
|
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val kotlinxSerializer = KotlinxSerializer( Json {
|
val kotlinxSerializer = KotlinxSerializer(
|
||||||
|
Json {
|
||||||
isLenient = true
|
isLenient = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient {
|
fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient {
|
||||||
install(JsonFeature) {
|
install(JsonFeature) {
|
||||||
|
@ -22,9 +22,10 @@ import com.shabinder.common.di.utils.removeIllegalChars
|
|||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.HttpStatement
|
||||||
import io.ktor.http.*
|
import io.ktor.http.contentLength
|
||||||
|
import io.ktor.http.isSuccess
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
@ -22,13 +22,12 @@ import com.shabinder.common.di.providers.SpotifyProvider
|
|||||||
import com.shabinder.common.di.providers.YoutubeMp3
|
import com.shabinder.common.di.providers.YoutubeMp3
|
||||||
import com.shabinder.common.di.providers.YoutubeMusic
|
import com.shabinder.common.di.providers.YoutubeMusic
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.database.Database
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class FetchPlatformQueryResult(
|
class FetchPlatformQueryResult(
|
||||||
private val gaanaProvider: GaanaProvider,
|
private val gaanaProvider: GaanaProvider,
|
||||||
val spotifyProvider: SpotifyProvider,
|
private val spotifyProvider: SpotifyProvider,
|
||||||
val youtubeProvider: YoutubeProvider,
|
val youtubeProvider: YoutubeProvider,
|
||||||
val youtubeMusic: YoutubeMusic,
|
val youtubeMusic: YoutubeMusic,
|
||||||
val youtubeMp3: YoutubeMp3,
|
val youtubeMp3: YoutubeMp3,
|
||||||
|
@ -18,8 +18,7 @@ package com.shabinder.common.di
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.database.Database
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.*
|
|
||||||
|
|
||||||
expect class YoutubeProvider(
|
expect class YoutubeProvider(
|
||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
|
@ -19,9 +19,13 @@ package com.shabinder.common.di.gaana
|
|||||||
import com.shabinder.common.di.currentPlatform
|
import com.shabinder.common.di.currentPlatform
|
||||||
import com.shabinder.common.models.AllPlatforms
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.corsProxy
|
import com.shabinder.common.models.corsProxy
|
||||||
import com.shabinder.common.models.gaana.*
|
import com.shabinder.common.models.gaana.GaanaAlbum
|
||||||
import io.ktor.client.*
|
import com.shabinder.common.models.gaana.GaanaArtistDetails
|
||||||
import io.ktor.client.request.*
|
import com.shabinder.common.models.gaana.GaanaArtistTracks
|
||||||
|
import com.shabinder.common.models.gaana.GaanaPlaylist
|
||||||
|
import com.shabinder.common.models.gaana.GaanaSong
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
|
||||||
val corsApi get() = if (currentPlatform is AllPlatforms.Js) {
|
val corsApi get() = if (currentPlatform is AllPlatforms.Js) {
|
||||||
corsProxy.url
|
corsProxy.url
|
||||||
|
@ -25,7 +25,7 @@ import com.shabinder.common.models.PlatformQueryResult
|
|||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.models.gaana.GaanaTrack
|
import com.shabinder.common.models.gaana.GaanaTrack
|
||||||
import com.shabinder.common.models.spotify.Source
|
import com.shabinder.common.models.spotify.Source
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
|
|
||||||
class GaanaProvider(
|
class GaanaProvider(
|
||||||
override val httpClient: HttpClient,
|
override val httpClient: HttpClient,
|
||||||
@ -64,22 +64,14 @@ class GaanaProvider(
|
|||||||
trackList = listOf(),
|
trackList = listOf(),
|
||||||
Source.Gaana
|
Source.Gaana
|
||||||
)
|
)
|
||||||
|
logger.i { "GAANA SEARCH: $type - $link" }
|
||||||
with(result) {
|
with(result) {
|
||||||
when (type) {
|
when (type) {
|
||||||
"song" -> {
|
"song" -> {
|
||||||
getGaanaSong(seokey = link).tracks.firstOrNull()?.also {
|
getGaanaSong(seokey = link).tracks.firstOrNull()?.also {
|
||||||
folderType = "Tracks"
|
folderType = "Tracks"
|
||||||
subFolder = ""
|
subFolder = ""
|
||||||
if (dir.isPresent(
|
it.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
it.track_title,
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)) {//Download Already Present!!
|
|
||||||
it.downloaded = DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
|
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||||
title = it.track_title
|
title = it.track_title
|
||||||
coverUrl = it.artworkLink
|
coverUrl = it.artworkLink
|
||||||
@ -90,17 +82,7 @@ class GaanaProvider(
|
|||||||
folderType = "Albums"
|
folderType = "Albums"
|
||||||
subFolder = link
|
subFolder = link
|
||||||
it.tracks.forEach { track ->
|
it.tracks.forEach { track ->
|
||||||
if (dir.isPresent(
|
track.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
track.track_title,
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {//Download Already Present!!
|
|
||||||
track.downloaded = DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
|
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
title = link
|
title = link
|
||||||
@ -112,17 +94,7 @@ class GaanaProvider(
|
|||||||
folderType = "Playlists"
|
folderType = "Playlists"
|
||||||
subFolder = link
|
subFolder = link
|
||||||
it.tracks.forEach { track ->
|
it.tracks.forEach { track ->
|
||||||
if (dir.isPresent(
|
track.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
track.track_title,
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {//Download Already Present!!
|
|
||||||
track.downloaded = DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
|
trackList = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
title = link
|
title = link
|
||||||
@ -134,7 +106,6 @@ class GaanaProvider(
|
|||||||
folderType = "Artist"
|
folderType = "Artist"
|
||||||
subFolder = link
|
subFolder = link
|
||||||
coverUrl = gaanaPlaceholderImageUrl
|
coverUrl = gaanaPlaceholderImageUrl
|
||||||
val artistDetails =
|
|
||||||
getGaanaArtistDetails(seokey = link).artist.firstOrNull()
|
getGaanaArtistDetails(seokey = link).artist.firstOrNull()
|
||||||
?.also {
|
?.also {
|
||||||
title = it.name
|
title = it.name
|
||||||
@ -142,22 +113,13 @@ class GaanaProvider(
|
|||||||
}
|
}
|
||||||
getGaanaArtistTracks(seokey = link).also {
|
getGaanaArtistTracks(seokey = link).also {
|
||||||
it.tracks?.forEach { track ->
|
it.tracks?.forEach { track ->
|
||||||
if (dir.isPresent(
|
track.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
track.track_title,
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {//Download Already Present!!
|
|
||||||
track.downloaded = DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList()
|
trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {//TODO Handle Error}
|
else -> {
|
||||||
|
// TODO Handle Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -180,4 +142,17 @@ class GaanaProvider(
|
|||||||
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
private fun GaanaTrack.updateStatusIfPresent(folderType: String, subFolder: String) {
|
||||||
|
if (dir.isPresent(
|
||||||
|
dir.finalOutputDir(
|
||||||
|
track_title,
|
||||||
|
folderType,
|
||||||
|
subFolder,
|
||||||
|
dir.defaultDir()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) { // Download Already Present!!
|
||||||
|
downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,11 @@
|
|||||||
package com.shabinder.common.di.providers
|
package com.shabinder.common.di.providers
|
||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.di.*
|
import com.shabinder.common.di.Dir
|
||||||
|
import com.shabinder.common.di.TokenStore
|
||||||
|
import com.shabinder.common.di.currentPlatform
|
||||||
|
import com.shabinder.common.di.finalOutputDir
|
||||||
|
import com.shabinder.common.di.kotlinxSerializer
|
||||||
import com.shabinder.common.di.spotify.SpotifyRequests
|
import com.shabinder.common.di.spotify.SpotifyRequests
|
||||||
import com.shabinder.common.di.spotify.authenticateSpotify
|
import com.shabinder.common.di.spotify.authenticateSpotify
|
||||||
import com.shabinder.common.models.AllPlatforms
|
import com.shabinder.common.models.AllPlatforms
|
||||||
@ -27,10 +31,10 @@ import com.shabinder.common.models.spotify.Album
|
|||||||
import com.shabinder.common.models.spotify.Image
|
import com.shabinder.common.models.spotify.Image
|
||||||
import com.shabinder.common.models.spotify.Source
|
import com.shabinder.common.models.spotify.Source
|
||||||
import com.shabinder.common.models.spotify.Track
|
import com.shabinder.common.models.spotify.Track
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.*
|
import io.ktor.client.features.defaultRequest
|
||||||
import io.ktor.client.features.json.*
|
import io.ktor.client.features.json.JsonFeature
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.header
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -55,8 +59,7 @@ class SpotifyProvider(
|
|||||||
return if (token == null) {
|
return if (token == null) {
|
||||||
logger.d { "Please Check your Network Connection" }
|
logger.d { "Please Check your Network Connection" }
|
||||||
null
|
null
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
logger.d { "Spotify Provider Created with $token" }
|
logger.d { "Spotify Provider Created with $token" }
|
||||||
httpClient = HttpClient {
|
httpClient = HttpClient {
|
||||||
defaultRequest {
|
defaultRequest {
|
||||||
@ -89,7 +92,6 @@ class SpotifyProvider(
|
|||||||
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
||||||
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
|
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
|
||||||
|
|
||||||
|
|
||||||
if (type == "Error" || link == "Error") {
|
if (type == "Error" || link == "Error") {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -123,21 +125,13 @@ class SpotifyProvider(
|
|||||||
getTrack(link).also {
|
getTrack(link).also {
|
||||||
folderType = "Tracks"
|
folderType = "Tracks"
|
||||||
subFolder = ""
|
subFolder = ""
|
||||||
if (dir.isPresent(
|
it.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
it.name.toString(),
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {//Download Already Present!!
|
|
||||||
it.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
|
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||||
title = it.name.toString()
|
title = it.name.toString()
|
||||||
coverUrl = (it.album?.images?.elementAtOrNull(1)?.url
|
coverUrl = (
|
||||||
?: it.album?.images?.elementAtOrNull(0)?.url).toString()
|
it.album?.images?.elementAtOrNull(1)?.url
|
||||||
|
?: it.album?.images?.elementAtOrNull(0)?.url
|
||||||
|
).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,17 +140,7 @@ class SpotifyProvider(
|
|||||||
folderType = "Albums"
|
folderType = "Albums"
|
||||||
subFolder = albumObject.name.toString()
|
subFolder = albumObject.name.toString()
|
||||||
albumObject.tracks?.items?.forEach {
|
albumObject.tracks?.items?.forEach {
|
||||||
if (dir.isPresent(
|
it.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
it.name.toString(),
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {//Download Already Present!!
|
|
||||||
it.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
it.album = Album(
|
it.album = Album(
|
||||||
images = listOf(
|
images = listOf(
|
||||||
Image(
|
Image(
|
||||||
@ -172,8 +156,10 @@ class SpotifyProvider(
|
|||||||
} else {
|
} else {
|
||||||
trackList = it
|
trackList = it
|
||||||
title = albumObject.name.toString()
|
title = albumObject.name.toString()
|
||||||
coverUrl = (albumObject.images?.elementAtOrNull(1)?.url
|
coverUrl = (
|
||||||
?: albumObject.images?.elementAtOrNull(0)?.url).toString()
|
albumObject.images?.elementAtOrNull(1)?.url
|
||||||
|
?: albumObject.images?.elementAtOrNull(0)?.url
|
||||||
|
).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,17 +172,7 @@ class SpotifyProvider(
|
|||||||
// log("Tracks Fetched", playlistObject.tracks?.items?.size.toString())
|
// log("Tracks Fetched", playlistObject.tracks?.items?.size.toString())
|
||||||
playlistObject.tracks?.items?.forEach {
|
playlistObject.tracks?.items?.forEach {
|
||||||
it.track?.let { it1 ->
|
it.track?.let { it1 ->
|
||||||
if (dir.isPresent(
|
it1.updateStatusIfPresent(folderType, subFolder)
|
||||||
dir.finalOutputDir(
|
|
||||||
it1.name.toString(),
|
|
||||||
folderType,
|
|
||||||
subFolder,
|
|
||||||
dir.defaultDir()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {//Download Already Present!!
|
|
||||||
it1.downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
|
|
||||||
}
|
|
||||||
tempTrackList.add(it1)
|
tempTrackList.add(it1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,4 +233,17 @@ class SpotifyProvider(
|
|||||||
outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
private fun Track.updateStatusIfPresent(folderType: String, subFolder: String) {
|
||||||
|
if (dir.isPresent(
|
||||||
|
dir.finalOutputDir(
|
||||||
|
name.toString(),
|
||||||
|
folderType,
|
||||||
|
subFolder,
|
||||||
|
dir.defaultDir()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) { // Download Already Present!!
|
||||||
|
downloaded = com.shabinder.common.models.DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,10 +21,7 @@ import com.shabinder.common.di.Dir
|
|||||||
import com.shabinder.common.di.currentPlatform
|
import com.shabinder.common.di.currentPlatform
|
||||||
import com.shabinder.common.di.youtubeMp3.Yt1sMp3
|
import com.shabinder.common.di.youtubeMp3.Yt1sMp3
|
||||||
import com.shabinder.common.models.AllPlatforms
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.CorsProxy
|
import io.ktor.client.HttpClient
|
||||||
import com.shabinder.common.models.corsProxy
|
|
||||||
import com.shabinder.database.Database
|
|
||||||
import io.ktor.client.*
|
|
||||||
|
|
||||||
class YoutubeMp3(
|
class YoutubeMp3(
|
||||||
override val httpClient: HttpClient,
|
override val httpClient: HttpClient,
|
||||||
|
@ -21,10 +21,21 @@ import com.shabinder.common.di.gaana.corsApi
|
|||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.models.YoutubeTrack
|
import com.shabinder.common.models.YoutubeTrack
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.headers
|
||||||
import io.ktor.http.*
|
import io.ktor.client.request.post
|
||||||
import kotlinx.serialization.json.*
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.contentType
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.contentOrNull
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
||||||
@ -65,8 +76,10 @@ class YoutubeMusic constructor(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for(contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray
|
for (
|
||||||
?: listOf()){
|
contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray
|
||||||
|
?: listOf()
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* apparently content Blocks without an 'overlay' field don't have linkBlocks
|
* apparently content Blocks without an 'overlay' field don't have linkBlocks
|
||||||
* I have no clue what they are and why there even exist
|
* I have no clue what they are and why there even exist
|
||||||
@ -219,7 +232,6 @@ class YoutubeMusic constructor(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Find artist match
|
// Find artist match
|
||||||
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
||||||
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
||||||
@ -259,7 +271,9 @@ class YoutubeMusic constructor(
|
|||||||
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
||||||
}
|
}
|
||||||
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
||||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap()
|
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||||
|
logger.d(tag) { "Match Found for $trackName - ${!it.isNullOrEmpty()}" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getYoutubeMusicResponse(query: String): String {
|
private suspend fun getYoutubeMusicResponse(query: String): String {
|
||||||
|
@ -19,13 +19,13 @@ package com.shabinder.common.di.spotify
|
|||||||
import com.shabinder.common.di.isInternetAvailable
|
import com.shabinder.common.di.isInternetAvailable
|
||||||
import com.shabinder.common.di.kotlinxSerializer
|
import com.shabinder.common.di.kotlinxSerializer
|
||||||
import com.shabinder.common.models.spotify.TokenData
|
import com.shabinder.common.models.spotify.TokenData
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.auth.*
|
import io.ktor.client.features.auth.Auth
|
||||||
import io.ktor.client.features.auth.providers.*
|
import io.ktor.client.features.auth.providers.basic
|
||||||
import io.ktor.client.features.json.*
|
import io.ktor.client.features.json.JsonFeature
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.forms.FormDataContent
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.post
|
||||||
import io.ktor.http.*
|
import io.ktor.http.Parameters
|
||||||
|
|
||||||
suspend fun authenticateSpotify(): TokenData? {
|
suspend fun authenticateSpotify(): TokenData? {
|
||||||
return if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
|
return if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
|
||||||
|
@ -21,8 +21,8 @@ import com.shabinder.common.models.spotify.Album
|
|||||||
import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
|
import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
|
||||||
import com.shabinder.common.models.spotify.Playlist
|
import com.shabinder.common.models.spotify.Playlist
|
||||||
import com.shabinder.common.models.spotify.Track
|
import com.shabinder.common.models.spotify.Track
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.get
|
||||||
|
|
||||||
private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1"
|
private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1"
|
||||||
|
|
||||||
|
@ -21,12 +21,19 @@ package com.shabinder.common.di.utils
|
|||||||
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
||||||
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
|
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
|
||||||
|
|
||||||
import io.ktor.utils.io.core.*
|
import io.ktor.utils.io.core.Closeable
|
||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.channels.*
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.selects.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlin.coroutines.*
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.selects.select
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class ParallelExecutor(
|
class ParallelExecutor(
|
||||||
parentContext: CoroutineContext,
|
parentContext: CoroutineContext,
|
||||||
@ -38,12 +45,10 @@ class ParallelExecutor(
|
|||||||
private val killQueue = Channel<Unit>(Channel.UNLIMITED)
|
private val killQueue = Channel<Unit>(Channel.UNLIMITED)
|
||||||
private val operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS)
|
private val operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS)
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
startOrStopProcessors(expectedCount = concurrentOperationLimit.value, actualCount = 0)
|
startOrStopProcessors(expectedCount = concurrentOperationLimit.value, actualCount = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (!isClosed.compareAndSet(expect = false, update = true))
|
if (!isClosed.compareAndSet(expect = false, update = true))
|
||||||
return
|
return
|
||||||
@ -55,7 +60,6 @@ class ParallelExecutor(
|
|||||||
coroutineContext.cancel(cause)
|
coroutineContext.cancel(cause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun CoroutineScope.launchProcessor() = launch {
|
private fun CoroutineScope.launchProcessor() = launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
val operation = select<Operation<*>?> {
|
val operation = select<Operation<*>?> {
|
||||||
@ -67,7 +71,6 @@ class ParallelExecutor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun <Result> execute(block: suspend () -> Result): Result =
|
suspend fun <Result> execute(block: suspend () -> Result): Result =
|
||||||
withContext(coroutineContext) {
|
withContext(coroutineContext) {
|
||||||
val operation = Operation(block)
|
val operation = Operation(block)
|
||||||
@ -76,7 +79,6 @@ class ParallelExecutor(
|
|||||||
operation.result.await()
|
operation.result.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this.
|
// TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this.
|
||||||
fun setConcurrentOperationLimit(limit: Int) {
|
fun setConcurrentOperationLimit(limit: Int) {
|
||||||
require(limit >= 1) { "'limit' must be greater than zero: $limit" }
|
require(limit >= 1) { "'limit' must be greater than zero: $limit" }
|
||||||
@ -85,7 +87,6 @@ class ParallelExecutor(
|
|||||||
startOrStopProcessors(expectedCount = limit, actualCount = concurrentOperationLimit.getAndSet(limit))
|
startOrStopProcessors(expectedCount = limit, actualCount = concurrentOperationLimit.getAndSet(limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun startOrStopProcessors(expectedCount: Int, actualCount: Int) {
|
private fun startOrStopProcessors(expectedCount: Int, actualCount: Int) {
|
||||||
if (expectedCount == actualCount)
|
if (expectedCount == actualCount)
|
||||||
return
|
return
|
||||||
@ -105,7 +106,6 @@ class ParallelExecutor(
|
|||||||
repeat(-change) { killQueue.offer(Unit) }
|
repeat(-change) { killQueue.offer(Unit) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Operation<Result>(
|
private class Operation<Result>(
|
||||||
private val block: suspend () -> Result,
|
private val block: suspend () -> Result,
|
||||||
) {
|
) {
|
||||||
@ -114,12 +114,10 @@ class ParallelExecutor(
|
|||||||
|
|
||||||
val result: Deferred<Result> get() = _result
|
val result: Deferred<Result> get() = _result
|
||||||
|
|
||||||
|
|
||||||
suspend fun execute() {
|
suspend fun execute() {
|
||||||
try {
|
try {
|
||||||
_result.complete(block())
|
_result.complete(block())
|
||||||
}
|
} catch (e: Throwable) {
|
||||||
catch (e: Throwable) {
|
|
||||||
_result.completeExceptionally(e)
|
_result.completeExceptionally(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package com.shabinder.common.di.utils
|
package com.shabinder.common.di.utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removing Illegal Chars from File Name
|
* Removing Illegal Chars from File Name
|
||||||
* **/
|
* **/
|
||||||
|
@ -17,10 +17,10 @@
|
|||||||
package com.shabinder.common.di.youtubeMp3
|
package com.shabinder.common.di.youtubeMp3
|
||||||
|
|
||||||
import com.shabinder.common.di.gaana.corsApi
|
import com.shabinder.common.di.gaana.corsApi
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.forms.FormDataContent
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.post
|
||||||
import io.ktor.http.*
|
import io.ktor.http.Parameters
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
@ -44,20 +44,24 @@ interface Yt1sMp3 {
|
|||||||
* */
|
* */
|
||||||
private suspend fun getKey(videoID: String): String {
|
private suspend fun getKey(videoID: String): String {
|
||||||
val response: JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") {
|
val response: JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") {
|
||||||
body = FormDataContent(Parameters.build {
|
body = FormDataContent(
|
||||||
|
Parameters.build {
|
||||||
append("q", "https://www.youtube.com/watch?v=$videoID")
|
append("q", "https://www.youtube.com/watch?v=$videoID")
|
||||||
append("vt", "mp3")
|
append("vt", "mp3")
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return response?.get("kc")?.jsonPrimitive.toString()
|
return response?.get("kc")?.jsonPrimitive.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getConvertedMp3Link(videoID: String, key: String): JsonObject? {
|
private suspend fun getConvertedMp3Link(videoID: String, key: String): JsonObject? {
|
||||||
return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") {
|
return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") {
|
||||||
body = FormDataContent(Parameters.build {
|
body = FormDataContent(
|
||||||
|
Parameters.build {
|
||||||
append("vid", videoID)
|
append("vid", videoID)
|
||||||
append("k", key)
|
append("k", key)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,7 +25,7 @@ import com.shabinder.common.models.AllPlatforms
|
|||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.head
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@ -91,8 +91,11 @@ actual suspend fun downloadTracks(
|
|||||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||||
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery, it)
|
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery, it)
|
||||||
if (videoId.isNullOrBlank()) {
|
if (videoId.isNullOrBlank()) {
|
||||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
DownloadProgressFlow.emit(
|
||||||
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
|
DownloadProgressFlow.replayCache.getOrElse(
|
||||||
|
0
|
||||||
|
) { hashMapOf() }.apply { set(it.title, DownloadStatus.Failed) }
|
||||||
|
)
|
||||||
} else { // Found Youtube Video ID
|
} else { // Found Youtube Video ID
|
||||||
downloadTrack(videoId, it, dir::saveFileWithMetadata)
|
downloadTrack(videoId, it, dir::saveFileWithMetadata)
|
||||||
}
|
}
|
||||||
@ -116,17 +119,26 @@ suspend fun downloadTrack(
|
|||||||
downloadFile(url).collect {
|
downloadFile(url).collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DownloadResult.Error -> {
|
is DownloadResult.Error -> {
|
||||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
DownloadProgressFlow.emit(
|
||||||
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Failed) })
|
DownloadProgressFlow.replayCache.getOrElse(
|
||||||
|
0
|
||||||
|
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is DownloadResult.Progress -> {
|
is DownloadResult.Progress -> {
|
||||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
DownloadProgressFlow.emit(
|
||||||
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloading(it.progress)) })
|
DownloadProgressFlow.replayCache.getOrElse(
|
||||||
|
0
|
||||||
|
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloading(it.progress)) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is DownloadResult.Success -> { // Todo clear map
|
is DownloadResult.Success -> { // Todo clear map
|
||||||
saveFileWithMetaData(it.byteArray, trackDetails)
|
saveFileWithMetaData(it.byteArray, trackDetails)
|
||||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
DownloadProgressFlow.emit(
|
||||||
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloaded) })
|
DownloadProgressFlow.replayCache.getOrElse(
|
||||||
|
0
|
||||||
|
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import com.shabinder.database.Database
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.jetbrains.skija.Image
|
import org.jetbrains.skija.Image
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@ -35,9 +36,6 @@ import java.net.HttpURLConnection
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
actual class Dir actual constructor(
|
actual class Dir actual constructor(
|
||||||
private val logger: Kermit,
|
private val logger: Kermit,
|
||||||
private val database: Database?,
|
private val database: Database?,
|
||||||
@ -60,16 +58,11 @@ actual class Dir actual constructor(
|
|||||||
actual fun createDirectory(dirPath: String) {
|
actual fun createDirectory(dirPath: String) {
|
||||||
val yourAppDir = File(dirPath)
|
val yourAppDir = File(dirPath)
|
||||||
|
|
||||||
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
|
if (!yourAppDir.exists() && !yourAppDir.isDirectory) { // create empty directory
|
||||||
{ // create empty directory
|
if (yourAppDir.mkdirs()) { logger.i { "$dirPath created" } } else {
|
||||||
if (yourAppDir.mkdirs())
|
|
||||||
{logger.i{"$dirPath created"}}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.e { "Unable to create Dir: $dirPath!" }
|
logger.e { "Unable to create Dir: $dirPath!" }
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
logger.i { "$dirPath already exists" }
|
logger.i { "$dirPath already exists" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,8 +112,10 @@ actual class Dir actual constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
private suspend fun freshImage(url: String): ImageBitmap? {
|
private suspend fun freshImage(url: String): ImageBitmap? {
|
||||||
return try {
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
val source = URL(url)
|
val source = URL(url)
|
||||||
val connection: HttpURLConnection = source.openConnection() as HttpURLConnection
|
val connection: HttpURLConnection = source.openConnection() as HttpURLConnection
|
||||||
connection.connectTimeout = 5000
|
connection.connectTimeout = 5000
|
||||||
@ -140,6 +135,7 @@ actual class Dir actual constructor(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actual val db: Database?
|
actual val db: Database?
|
||||||
get() = database
|
get() = database
|
||||||
|
@ -32,7 +32,6 @@ fun Mp3File.removeAllTags(): Mp3File {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifying Mp3 with MetaData!
|
* Modifying Mp3 with MetaData!
|
||||||
**/
|
**/
|
||||||
|
@ -23,7 +23,7 @@ import com.shabinder.common.models.DownloadStatus
|
|||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.models.spotify.Source
|
import com.shabinder.common.models.spotify.Source
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
|
|
||||||
actual class YoutubeProvider actual constructor(
|
actual class YoutubeProvider actual constructor(
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
|
@ -20,10 +20,12 @@ import com.shabinder.common.models.AllPlatforms
|
|||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.head
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
actual val currentPlatform: AllPlatforms = AllPlatforms.Js
|
actual val currentPlatform: AllPlatforms = AllPlatforms.Js
|
||||||
|
|
||||||
@ -78,7 +80,7 @@ actual suspend fun downloadTracks(
|
|||||||
dir: Dir
|
dir: Dir
|
||||||
) {
|
) {
|
||||||
list.forEach {
|
list.forEach {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(dispatcherIO) {
|
||||||
allTracksStatus[it.title] = DownloadStatus.Queued
|
allTracksStatus[it.title] = DownloadStatus.Queued
|
||||||
if (!it.videoID.isNullOrBlank()) { // Video ID already known!
|
if (!it.videoID.isNullOrBlank()) { // Video ID already known!
|
||||||
downloadTrack(it.videoID!!, it, fetcher, dir)
|
downloadTrack(it.videoID!!, it, fetcher, dir)
|
||||||
|
@ -27,8 +27,8 @@ import kotlinext.js.Object
|
|||||||
import kotlinext.js.js
|
import kotlinext.js.js
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import org.khronos.webgl.ArrayBuffer
|
import org.khronos.webgl.ArrayBuffer
|
||||||
import org.w3c.dom.ImageBitmap
|
|
||||||
import org.khronos.webgl.Int8Array
|
import org.khronos.webgl.Int8Array
|
||||||
|
import org.w3c.dom.ImageBitmap
|
||||||
|
|
||||||
actual class Dir actual constructor(
|
actual class Dir actual constructor(
|
||||||
private val logger: Kermit,
|
private val logger: Kermit,
|
||||||
|
@ -18,8 +18,7 @@ package com.shabinder.common.di
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.models.PlatformQueryResult
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
import com.shabinder.database.Database
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.*
|
|
||||||
|
|
||||||
actual class YoutubeProvider actual constructor(
|
actual class YoutubeProvider actual constructor(
|
||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
|
@ -24,9 +24,9 @@ fun <T : Store<*, *, *>> InstanceKeeper.getStore(key: Any, factory: () -> T): T
|
|||||||
getOrCreate(key) { StoreHolder(factory()) }
|
getOrCreate(key) { StoreHolder(factory()) }
|
||||||
.store
|
.store
|
||||||
|
|
||||||
inline fun <reified T
|
inline fun <reified T :
|
||||||
|
|
||||||
: Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T =
|
Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T =
|
||||||
getStore(T::class, factory)
|
getStore(T::class, factory)
|
||||||
|
|
||||||
private class StoreHolder<T : Store<*, *, *>>(
|
private class StoreHolder<T : Store<*, *, *>>(
|
||||||
|
@ -16,10 +16,16 @@
|
|||||||
|
|
||||||
package com.shabinder.common.list.store
|
package com.shabinder.common.list.store
|
||||||
|
|
||||||
import com.arkivanov.mvikotlin.core.store.*
|
import com.arkivanov.mvikotlin.core.store.Reducer
|
||||||
|
import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
|
||||||
|
import com.arkivanov.mvikotlin.core.store.Store
|
||||||
|
import com.arkivanov.mvikotlin.core.store.StoreFactory
|
||||||
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
||||||
import com.shabinder.common.database.getLogger
|
import com.shabinder.common.database.getLogger
|
||||||
import com.shabinder.common.di.*
|
import com.shabinder.common.di.Dir
|
||||||
|
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||||
|
import com.shabinder.common.di.downloadTracks
|
||||||
|
import com.shabinder.common.di.queryActiveTracks
|
||||||
import com.shabinder.common.list.SpotiFlyerList.State
|
import com.shabinder.common.list.SpotiFlyerList.State
|
||||||
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
@ -38,7 +44,9 @@ internal class SpotiFlyerListStoreProvider(
|
|||||||
) {
|
) {
|
||||||
val logger = getLogger()
|
val logger = getLogger()
|
||||||
fun provide(): SpotiFlyerListStore =
|
fun provide(): SpotiFlyerListStore =
|
||||||
object : SpotiFlyerListStore, Store<Intent, State, Nothing> by storeFactory.create(
|
object :
|
||||||
|
SpotiFlyerListStore,
|
||||||
|
Store<Intent, State, Nothing> by storeFactory.create(
|
||||||
name = "SpotiFlyerListStore",
|
name = "SpotiFlyerListStore",
|
||||||
initialState = State(),
|
initialState = State(),
|
||||||
bootstrapper = SimpleBootstrapper(Unit),
|
bootstrapper = SimpleBootstrapper(Unit),
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.jetbrains.compose.compose
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("multiplatform-setup")
|
id("multiplatform-setup")
|
||||||
id("android-setup")
|
id("android-setup")
|
||||||
|
@ -21,7 +21,10 @@ import com.arkivanov.mvikotlin.extensions.coroutines.states
|
|||||||
import com.shabinder.common.di.Picture
|
import com.shabinder.common.di.Picture
|
||||||
import com.shabinder.common.di.isInternetAvailable
|
import com.shabinder.common.di.isInternetAvailable
|
||||||
import com.shabinder.common.main.SpotiFlyerMain
|
import com.shabinder.common.main.SpotiFlyerMain
|
||||||
import com.shabinder.common.main.SpotiFlyerMain.*
|
import com.shabinder.common.main.SpotiFlyerMain.Dependencies
|
||||||
|
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
|
||||||
|
import com.shabinder.common.main.SpotiFlyerMain.Output
|
||||||
|
import com.shabinder.common.main.SpotiFlyerMain.State
|
||||||
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
|
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
|
||||||
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
|
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
|
||||||
import com.shabinder.common.main.store.getStore
|
import com.shabinder.common.main.store.getStore
|
||||||
|
@ -24,9 +24,9 @@ fun <T : Store<*, *, *>> InstanceKeeper.getStore(key: Any, factory: () -> T): T
|
|||||||
getOrCreate(key) { StoreHolder(factory()) }
|
getOrCreate(key) { StoreHolder(factory()) }
|
||||||
.store
|
.store
|
||||||
|
|
||||||
inline fun <reified T
|
inline fun <reified T :
|
||||||
|
|
||||||
: Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T =
|
Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T =
|
||||||
getStore(T::class, factory)
|
getStore(T::class, factory)
|
||||||
|
|
||||||
private class StoreHolder<T : Store<*, *, *>>(
|
private class StoreHolder<T : Store<*, *, *>>(
|
||||||
|
@ -43,7 +43,9 @@ internal class SpotiFlyerMainStoreProvider(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
fun provide(): SpotiFlyerMainStore =
|
fun provide(): SpotiFlyerMainStore =
|
||||||
object : SpotiFlyerMainStore, Store<Intent, State, Nothing> by storeFactory.create(
|
object :
|
||||||
|
SpotiFlyerMainStore,
|
||||||
|
Store<Intent, State, Nothing> by storeFactory.create(
|
||||||
name = "SpotiFlyerHomeStore",
|
name = "SpotiFlyerHomeStore",
|
||||||
initialState = State(),
|
initialState = State(),
|
||||||
bootstrapper = SimpleBootstrapper(Unit),
|
bootstrapper = SimpleBootstrapper(Unit),
|
||||||
@ -64,7 +66,6 @@ internal class SpotiFlyerMainStoreProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private sealed class Result {
|
private sealed class Result {
|
||||||
data class ItemsLoaded(val items: List<DownloadRecord>) : Result()
|
data class ItemsLoaded(val items: List<DownloadRecord>) : Result()
|
||||||
data class CategoryChanged(val category: SpotiFlyerMain.HomeCategory) : Result()
|
data class CategoryChanged(val category: SpotiFlyerMain.HomeCategory) : Result()
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.jetbrains.compose.compose
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("multiplatform-setup")
|
id("multiplatform-setup")
|
||||||
id("android-setup")
|
id("android-setup")
|
||||||
|
@ -16,7 +16,12 @@
|
|||||||
|
|
||||||
package com.shabinder.common.root.integration
|
package com.shabinder.common.root.integration
|
||||||
|
|
||||||
import com.arkivanov.decompose.*
|
import com.arkivanov.decompose.ComponentContext
|
||||||
|
import com.arkivanov.decompose.RouterState
|
||||||
|
import com.arkivanov.decompose.pop
|
||||||
|
import com.arkivanov.decompose.popWhile
|
||||||
|
import com.arkivanov.decompose.push
|
||||||
|
import com.arkivanov.decompose.router
|
||||||
import com.arkivanov.decompose.statekeeper.Parcelable
|
import com.arkivanov.decompose.statekeeper.Parcelable
|
||||||
import com.arkivanov.decompose.statekeeper.Parcelize
|
import com.arkivanov.decompose.statekeeper.Parcelize
|
||||||
import com.arkivanov.decompose.value.Value
|
import com.arkivanov.decompose.value.Value
|
||||||
|
@ -18,7 +18,6 @@ import androidx.compose.desktop.DesktopMaterialTheme
|
|||||||
import androidx.compose.desktop.Window
|
import androidx.compose.desktop.Window
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
@ -31,13 +30,16 @@ import com.shabinder.common.di.DownloadProgressFlow
|
|||||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||||
import com.shabinder.common.di.initKoin
|
import com.shabinder.common.di.initKoin
|
||||||
import com.shabinder.common.root.SpotiFlyerRoot
|
import com.shabinder.common.root.SpotiFlyerRoot
|
||||||
import com.shabinder.common.uikit.*
|
import com.shabinder.common.uikit.SpotiFlyerColors
|
||||||
|
import com.shabinder.common.uikit.SpotiFlyerRootContent
|
||||||
|
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||||
|
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||||
|
import com.shabinder.common.uikit.colorOffWhite
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
|
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
|
||||||
|
|
||||||
private val koin = initKoin(enableNetworkLogs = true).koin
|
private val koin = initKoin(enableNetworkLogs = true).koin
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
|
||||||
val lifecycle = LifecycleRegistry()
|
val lifecycle = LifecycleRegistry()
|
||||||
@ -54,7 +56,7 @@ fun main(){
|
|||||||
typography = SpotiFlyerTypography,
|
typography = SpotiFlyerTypography,
|
||||||
shapes = SpotiFlyerShapes
|
shapes = SpotiFlyerShapes
|
||||||
) {
|
) {
|
||||||
val callBacks = SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot)).callBacks
|
SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.willowtreeapps.fuzzywuzzy
|
package com.willowtreeapps.fuzzywuzzy
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms an item of type T to a String.
|
* Transforms an item of type T to a String.
|
||||||
*
|
*
|
||||||
|
@ -18,7 +18,10 @@ package com.willowtreeapps.fuzzywuzzy.diffutils
|
|||||||
|
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditOp
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditOp
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.*
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.DELETE
|
||||||
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.INSERT
|
||||||
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.KEEP
|
||||||
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.EditType.REPLACE
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.MatchingBlock
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.MatchingBlock
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.OpCode
|
import com.willowtreeapps.fuzzywuzzy.diffutils.structs.OpCode
|
||||||
|
|
||||||
@ -39,18 +42,12 @@ object DiffUtils {
|
|||||||
var len2Copy = len2
|
var len2Copy = len2
|
||||||
|
|
||||||
var len1o = 0
|
var len1o = 0
|
||||||
val len2o: Int
|
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
val matrix: IntArray
|
|
||||||
|
|
||||||
val c1 = s1
|
|
||||||
val c2 = s2
|
|
||||||
|
|
||||||
var p1 = 0
|
var p1 = 0
|
||||||
var p2 = 0
|
var p2 = 0
|
||||||
|
|
||||||
while (len1Copy > 0 && len2Copy > 0 && c1[p1] == c2[p2]) {
|
while (len1Copy > 0 && len2Copy > 0 && s1[p1] == s2[p2]) {
|
||||||
len1Copy--
|
len1Copy--
|
||||||
len2Copy--
|
len2Copy--
|
||||||
|
|
||||||
@ -60,10 +57,10 @@ object DiffUtils {
|
|||||||
len1o++
|
len1o++
|
||||||
}
|
}
|
||||||
|
|
||||||
len2o = len1o
|
val len2o: Int = len1o
|
||||||
|
|
||||||
/* strip common suffix */
|
/* strip common suffix */
|
||||||
while (len1Copy > 0 && len2Copy > 0 && c1[p1 + len1Copy - 1] == c2[p2 + len2Copy - 1]) {
|
while (len1Copy > 0 && len2Copy > 0 && s1[p1 + len1Copy - 1] == s2[p2 + len2Copy - 1]) {
|
||||||
len1Copy--
|
len1Copy--
|
||||||
len2Copy--
|
len2Copy--
|
||||||
}
|
}
|
||||||
@ -71,7 +68,7 @@ object DiffUtils {
|
|||||||
len1Copy++
|
len1Copy++
|
||||||
len2Copy++
|
len2Copy++
|
||||||
|
|
||||||
matrix = IntArray(len2Copy * len1Copy)
|
val matrix: IntArray = IntArray(len2Copy * len1Copy)
|
||||||
|
|
||||||
while (i < len2Copy) {
|
while (i < len2Copy) {
|
||||||
matrix[i] = i
|
matrix[i] = i
|
||||||
@ -90,7 +87,7 @@ object DiffUtils {
|
|||||||
var ptrC = i * len2Copy
|
var ptrC = i * len2Copy
|
||||||
val ptrEnd = ptrC + len2Copy - 1
|
val ptrEnd = ptrC + len2Copy - 1
|
||||||
|
|
||||||
val char1 = c1[p1 + i - 1]
|
val char1 = s1[p1 + i - 1]
|
||||||
var ptrChar2 = p2
|
var ptrChar2 = p2
|
||||||
|
|
||||||
var x = i
|
var x = i
|
||||||
@ -99,7 +96,7 @@ object DiffUtils {
|
|||||||
|
|
||||||
while (ptrC <= ptrEnd) {
|
while (ptrC <= ptrEnd) {
|
||||||
|
|
||||||
var c3 = matrix[ptrPrev++] + if (char1 != c2[ptrChar2++]) 1 else 0
|
var c3 = matrix[ptrPrev++] + if (char1 != s2[ptrChar2++]) 1 else 0
|
||||||
x++
|
x++
|
||||||
|
|
||||||
if (x > c3) {
|
if (x > c3) {
|
||||||
@ -113,14 +110,12 @@ object DiffUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
matrix[ptrC++] = x
|
matrix[ptrC++] = x
|
||||||
|
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return editOpsFromCostMatrix(len1Copy, c1, p1, len1o, len2Copy, c2, p2, len2o, matrix)
|
return editOpsFromCostMatrix(len1Copy, s1, p1, len1o, len2Copy, s2, p2, len2o, matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -134,11 +129,9 @@ object DiffUtils {
|
|||||||
|
|
||||||
var ptr: Int = len1 * len2 - 1
|
var ptr: Int = len1 * len2 - 1
|
||||||
|
|
||||||
val ops: Array<EditOp?>
|
|
||||||
|
|
||||||
var dir = 0
|
var dir = 0
|
||||||
|
|
||||||
ops = arrayOfNulls(pos)
|
val ops: Array<EditOp?> = arrayOfNulls(pos)
|
||||||
|
|
||||||
while (i > 0 || j > 0) {
|
while (i > 0 || j > 0) {
|
||||||
|
|
||||||
@ -246,10 +239,9 @@ object DiffUtils {
|
|||||||
val n = ops.size
|
val n = ops.size
|
||||||
|
|
||||||
var noOfMB = 0
|
var noOfMB = 0
|
||||||
var i: Int
|
|
||||||
var o = 0
|
var o = 0
|
||||||
|
|
||||||
i = n
|
var i: Int = n
|
||||||
while (i-- != 0) {
|
while (i-- != 0) {
|
||||||
|
|
||||||
if (ops[o].type === KEEP) {
|
if (ops[o].type === KEEP) {
|
||||||
@ -324,7 +316,6 @@ object DiffUtils {
|
|||||||
val n = ops.size
|
val n = ops.size
|
||||||
|
|
||||||
var numberOfMatchingBlocks = 0
|
var numberOfMatchingBlocks = 0
|
||||||
var i: Int
|
|
||||||
var spos: Int
|
var spos: Int
|
||||||
var dpos: Int
|
var dpos: Int
|
||||||
|
|
||||||
@ -335,7 +326,7 @@ object DiffUtils {
|
|||||||
|
|
||||||
var type: EditType
|
var type: EditType
|
||||||
|
|
||||||
i = n
|
var i: Int = n
|
||||||
while (i != 0) {
|
while (i != 0) {
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import com.willowtreeapps.fuzzywuzzy.diffutils.ratio.SimpleRatio
|
|||||||
/**
|
/**
|
||||||
* FuzzySearch facade class
|
* FuzzySearch facade class
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
object FuzzySearch {
|
object FuzzySearch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,6 @@ package com.willowtreeapps.fuzzywuzzy.diffutils.algorithms
|
|||||||
import com.willowtreeapps.fuzzywuzzy.ToStringFunction
|
import com.willowtreeapps.fuzzywuzzy.ToStringFunction
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.Applicable
|
import com.willowtreeapps.fuzzywuzzy.diffutils.Applicable
|
||||||
|
|
||||||
|
|
||||||
abstract class BasicAlgorithm : Applicable {
|
abstract class BasicAlgorithm : Applicable {
|
||||||
|
|
||||||
var stringFunction: ToStringFunction<String>? = null
|
var stringFunction: ToStringFunction<String>? = null
|
||||||
|
@ -27,7 +27,6 @@ import kotlin.math.max
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
|
||||||
class WeightedRatio : BasicAlgorithm() {
|
class WeightedRatio : BasicAlgorithm() {
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,11 @@ import com.shabinder.common.di.DownloadProgressFlow
|
|||||||
import com.shabinder.common.root.SpotiFlyerRoot
|
import com.shabinder.common.root.SpotiFlyerRoot
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import extras.renderableChild
|
import extras.renderableChild
|
||||||
import react.*
|
import react.RBuilder
|
||||||
|
import react.RComponent
|
||||||
|
import react.RProps
|
||||||
|
import react.RState
|
||||||
|
import react.ReactElement
|
||||||
import root.RootR
|
import root.RootR
|
||||||
|
|
||||||
external interface AppProps : RProps {
|
external interface AppProps : RProps {
|
||||||
@ -45,8 +49,6 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
|
|||||||
private val lifecycle = LifecycleRegistry()
|
private val lifecycle = LifecycleRegistry()
|
||||||
private val ctx = DefaultComponentContext(lifecycle = lifecycle)
|
private val ctx = DefaultComponentContext(lifecycle = lifecycle)
|
||||||
private val dependencies = props.dependencies
|
private val dependencies = props.dependencies
|
||||||
private val logger:Kermit
|
|
||||||
get() = dependencies.logger
|
|
||||||
|
|
||||||
private val root = SpotiFlyerRoot(ctx,
|
private val root = SpotiFlyerRoot(ctx,
|
||||||
object : SpotiFlyerRoot.Dependencies{
|
object : SpotiFlyerRoot.Dependencies{
|
||||||
|
@ -14,10 +14,28 @@
|
|||||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
|
import kotlinx.css.BorderStyle
|
||||||
|
import kotlinx.css.Color
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.JustifyContent
|
||||||
|
import kotlinx.css.alignContent
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.backgroundColor
|
||||||
|
import kotlinx.css.borderBottomColor
|
||||||
|
import kotlinx.css.borderBottomStyle
|
||||||
|
import kotlinx.css.borderColor
|
||||||
|
import kotlinx.css.borderRadius
|
||||||
|
import kotlinx.css.borderRightColor
|
||||||
|
import kotlinx.css.borderWidth
|
||||||
|
import kotlinx.css.color
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.justifyContent
|
||||||
|
import kotlinx.css.margin
|
||||||
|
import kotlinx.css.padding
|
||||||
|
import kotlinx.css.px
|
||||||
import styled.StyleSheet
|
import styled.StyleSheet
|
||||||
|
|
||||||
|
|
||||||
val colorPrimary = Color("#FC5C7D")
|
val colorPrimary = Color("#FC5C7D")
|
||||||
val colorPrimaryDark = Color("#CE1CFF")
|
val colorPrimaryDark = Color("#CE1CFF")
|
||||||
val colorAccent = Color("#9AB3FF")
|
val colorAccent = Color("#9AB3FF")
|
||||||
|
@ -16,9 +16,13 @@
|
|||||||
|
|
||||||
package extras
|
package extras
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import react.RComponent
|
import react.RComponent
|
||||||
import react.RProps
|
import react.RProps
|
||||||
import react.RState
|
import react.RState
|
||||||
|
@ -19,7 +19,10 @@ package extras
|
|||||||
import com.arkivanov.decompose.value.Value
|
import com.arkivanov.decompose.value.Value
|
||||||
import com.arkivanov.decompose.value.ValueObserver
|
import com.arkivanov.decompose.value.ValueObserver
|
||||||
import extras.RenderableRootComponent.Props
|
import extras.RenderableRootComponent.Props
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import react.RComponent
|
import react.RComponent
|
||||||
import react.RProps
|
import react.RProps
|
||||||
import react.RState
|
import react.RState
|
||||||
|
@ -16,16 +16,22 @@
|
|||||||
|
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import com.shabinder.common.di.currentPlatform
|
|
||||||
import com.shabinder.common.main.SpotiFlyerMain
|
import com.shabinder.common.main.SpotiFlyerMain
|
||||||
import com.shabinder.common.main.SpotiFlyerMain.State
|
import com.shabinder.common.main.SpotiFlyerMain.State
|
||||||
import com.shabinder.common.models.AllPlatforms
|
|
||||||
import extras.RenderableComponent
|
import extras.RenderableComponent
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.FlexDirection
|
||||||
|
import kotlinx.css.JustifyContent
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.flexDirection
|
||||||
|
import kotlinx.css.flexGrow
|
||||||
|
import kotlinx.css.justifyContent
|
||||||
import kotlinx.dom.appendElement
|
import kotlinx.dom.appendElement
|
||||||
import react.*
|
import react.RBuilder
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
|
|
||||||
|
@ -16,14 +16,23 @@
|
|||||||
|
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import kotlinx.browser.document
|
import Styles
|
||||||
import kotlinx.css.*
|
import kotlinx.css.borderRadius
|
||||||
import kotlinx.dom.appendElement
|
import kotlinx.css.height
|
||||||
import kotlinx.dom.createElement
|
import kotlinx.css.margin
|
||||||
import kotlinx.html.SCRIPT
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.width
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import react.*
|
import react.RBuilder
|
||||||
import styled.*
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
|
import styled.css
|
||||||
|
import styled.styledA
|
||||||
|
import styled.styledDiv
|
||||||
|
import styled.styledForm
|
||||||
|
import styled.styledImg
|
||||||
|
|
||||||
external interface IconListProps : RProps {
|
external interface IconListProps : RProps {
|
||||||
var iconsAndPlatforms: Map<String,String>
|
var iconsAndPlatforms: Map<String,String>
|
||||||
|
@ -16,8 +16,13 @@
|
|||||||
|
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.em
|
||||||
import react.*
|
import kotlinx.css.fontSize
|
||||||
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
import styled.styledH1
|
import styled.styledH1
|
||||||
|
@ -22,9 +22,15 @@ import kotlinx.html.js.onChangeFunction
|
|||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import kotlinx.html.js.onKeyDownFunction
|
import kotlinx.html.js.onKeyDownFunction
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
import org.w3c.dom.Window
|
import react.RBuilder
|
||||||
import react.*
|
import react.RProps
|
||||||
import styled.*
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
|
import styled.css
|
||||||
|
import styled.styledButton
|
||||||
|
import styled.styledDiv
|
||||||
|
import styled.styledImg
|
||||||
|
import styled.styledInput
|
||||||
|
|
||||||
external interface SearchbarProps : RProps {
|
external interface SearchbarProps : RProps {
|
||||||
var link: String
|
var link: String
|
||||||
|
@ -16,8 +16,18 @@
|
|||||||
|
|
||||||
package list
|
package list
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Display
|
||||||
import react.*
|
import kotlinx.css.JustifyContent
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.justifyContent
|
||||||
|
import kotlinx.css.marginBottom
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.width
|
||||||
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
import styled.styledSpan
|
import styled.styledSpan
|
||||||
|
@ -16,15 +16,29 @@
|
|||||||
|
|
||||||
package list
|
package list
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.FlexDirection
|
||||||
|
import kotlinx.css.TextAlign
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.flexDirection
|
||||||
|
import kotlinx.css.height
|
||||||
|
import kotlinx.css.marginTop
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.textAlign
|
||||||
|
import kotlinx.css.width
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import react.*
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
import styled.styledH1
|
import styled.styledH1
|
||||||
import styled.styledImg
|
import styled.styledImg
|
||||||
|
|
||||||
|
|
||||||
external interface CoverImageProps : RProps {
|
external interface CoverImageProps : RProps {
|
||||||
var coverImageURL: String
|
var coverImageURL: String
|
||||||
var coverName: String
|
var coverName: String
|
||||||
|
@ -16,10 +16,26 @@
|
|||||||
|
|
||||||
package list
|
package list
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.JustifyContent
|
||||||
|
import kotlinx.css.WhiteSpace
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.fontSize
|
||||||
|
import kotlinx.css.height
|
||||||
|
import kotlinx.css.justifyContent
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.whiteSpace
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
|
import react.useEffect
|
||||||
|
import react.useState
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
import styled.styledH5
|
import styled.styledH5
|
||||||
|
@ -17,9 +17,17 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import kotlinx.css.*
|
import kotlinx.css.borderRadius
|
||||||
|
import kotlinx.css.em
|
||||||
|
import kotlinx.css.margin
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.width
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
import styled.styledImg
|
import styled.styledImg
|
||||||
|
@ -20,7 +20,15 @@ import com.shabinder.common.list.SpotiFlyerList
|
|||||||
import com.shabinder.common.list.SpotiFlyerList.State
|
import com.shabinder.common.list.SpotiFlyerList.State
|
||||||
import extras.RenderableComponent
|
import extras.RenderableComponent
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Color
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.FlexDirection
|
||||||
|
import kotlinx.css.color
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.flexDirection
|
||||||
|
import kotlinx.css.flexGrow
|
||||||
|
import kotlinx.css.padding
|
||||||
|
import kotlinx.css.px
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import react.RBuilder
|
import react.RBuilder
|
||||||
import styled.css
|
import styled.css
|
||||||
@ -65,7 +73,7 @@ class ListScreen(
|
|||||||
flexDirection = FlexDirection.column
|
flexDirection = FlexDirection.column
|
||||||
color = Color.white
|
color = Color.white
|
||||||
}
|
}
|
||||||
state.data.trackList.forEachIndexed{ index, trackDetails ->
|
state.data.trackList.forEachIndexed{ _, trackDetails ->
|
||||||
TrackItem {
|
TrackItem {
|
||||||
details = trackDetails
|
details = trackDetails
|
||||||
downloadTrack = model::onDownloadClicked
|
downloadTrack = model::onDownloadClicked
|
||||||
|
@ -16,12 +16,22 @@
|
|||||||
|
|
||||||
package list
|
package list
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
import react.*
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.flexGrow
|
||||||
|
import kotlinx.css.height
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.width
|
||||||
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement {
|
fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement {
|
||||||
return child(loadingAnim){
|
return child(loadingAnim){
|
||||||
|
@ -19,7 +19,11 @@ package list
|
|||||||
import kotlinx.css.marginRight
|
import kotlinx.css.marginRight
|
||||||
import kotlinx.css.px
|
import kotlinx.css.px
|
||||||
import kotlinx.css.width
|
import kotlinx.css.width
|
||||||
import react.*
|
import react.RBuilder
|
||||||
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
|
|
||||||
|
@ -18,10 +18,43 @@ package list
|
|||||||
|
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.FlexDirection
|
||||||
|
import kotlinx.css.Overflow
|
||||||
|
import kotlinx.css.TextAlign
|
||||||
|
import kotlinx.css.TextOverflow
|
||||||
|
import kotlinx.css.WhiteSpace
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.em
|
||||||
|
import kotlinx.css.flexDirection
|
||||||
|
import kotlinx.css.flexGrow
|
||||||
|
import kotlinx.css.fontSize
|
||||||
|
import kotlinx.css.height
|
||||||
|
import kotlinx.css.margin
|
||||||
|
import kotlinx.css.minWidth
|
||||||
|
import kotlinx.css.overflow
|
||||||
|
import kotlinx.css.padding
|
||||||
|
import kotlinx.css.paddingRight
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.textAlign
|
||||||
|
import kotlinx.css.textOverflow
|
||||||
|
import kotlinx.css.whiteSpace
|
||||||
|
import kotlinx.css.width
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import react.*
|
import react.RBuilder
|
||||||
import styled.*
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
|
import react.useEffect
|
||||||
|
import react.useState
|
||||||
|
import styled.css
|
||||||
|
import styled.styledDiv
|
||||||
|
import styled.styledH3
|
||||||
|
import styled.styledH4
|
||||||
|
import styled.styledImg
|
||||||
|
|
||||||
external interface TrackItemProps : RProps {
|
external interface TrackItemProps : RProps {
|
||||||
var details:TrackDetails
|
var details:TrackDetails
|
||||||
|
@ -16,12 +16,33 @@
|
|||||||
|
|
||||||
package navbar
|
package navbar
|
||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.Align
|
||||||
|
import kotlinx.css.Display
|
||||||
|
import kotlinx.css.LinearDimension
|
||||||
|
import kotlinx.css.alignItems
|
||||||
|
import kotlinx.css.display
|
||||||
|
import kotlinx.css.filter
|
||||||
|
import kotlinx.css.fontSize
|
||||||
|
import kotlinx.css.height
|
||||||
|
import kotlinx.css.margin
|
||||||
|
import kotlinx.css.marginLeft
|
||||||
|
import kotlinx.css.marginRight
|
||||||
|
import kotlinx.css.px
|
||||||
|
import kotlinx.css.width
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
import kotlinx.html.js.onBlurFunction
|
import kotlinx.html.js.onBlurFunction
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.RBuilder
|
||||||
import styled.*
|
import react.RProps
|
||||||
|
import react.ReactElement
|
||||||
|
import react.child
|
||||||
|
import react.functionalComponent
|
||||||
|
import styled.css
|
||||||
|
import styled.styledA
|
||||||
|
import styled.styledDiv
|
||||||
|
import styled.styledH1
|
||||||
|
import styled.styledImg
|
||||||
|
import styled.styledNav
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{
|
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{
|
||||||
|
@ -18,7 +18,7 @@ package root
|
|||||||
|
|
||||||
import com.arkivanov.decompose.RouterState
|
import com.arkivanov.decompose.RouterState
|
||||||
import com.shabinder.common.root.SpotiFlyerRoot
|
import com.shabinder.common.root.SpotiFlyerRoot
|
||||||
import com.shabinder.common.root.SpotiFlyerRoot.*
|
import com.shabinder.common.root.SpotiFlyerRoot.Child
|
||||||
import extras.RenderableRootComponent
|
import extras.RenderableRootComponent
|
||||||
import extras.renderableChild
|
import extras.renderableChild
|
||||||
import home.HomeScreen
|
import home.HomeScreen
|
||||||
|
Loading…
Reference in New Issue
Block a user