Code Cleaned & Ktlint Added

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

View File

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

View File

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

View File

@ -16,7 +16,6 @@
plugins {
`kotlin-dsl`
//`kotlin-dsl-precompiled-script-plugins`
}
group = "com.shabinder"
@ -28,6 +27,7 @@ repositories {
mavenCentral()
google()
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://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.firebase:perf-plugin:1.3.5")
implementation("com.google.firebase:firebase-crashlytics-gradle:2.5.1")
implementation("org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}")
implementation(JetBrains.Compose.gradlePlugin)
implementation(JetBrains.Kotlin.gradlePlugin)
implementation(JetBrains.Kotlin.serialization)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,17 +14,11 @@
* * 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 {
// id("com.android.library")
id("android-setup")
id("kotlin-multiplatform")
id("org.jetbrains.compose")
id("ktlint-setup")
}
kotlin {
@ -37,9 +31,7 @@ kotlin {
}
sourceSets {
named("commonMain") {
dependencies {
}
dependencies {}
}
named("androidMain") {

View File

@ -21,7 +21,13 @@ package com.shabinder.common.uikit
import androidx.annotation.DrawableRes
import androidx.compose.animation.Crossfade
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.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
@ -68,7 +74,6 @@ actual fun pristineFont() = FontFamily(
Font(R.font.pristine_script, FontWeight.Bold)
)
@Composable
actual fun DownloadImageTick() {
Image(

View File

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

View File

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

View File

@ -17,26 +17,54 @@
package com.shabinder.common.uikit
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.BorderStroke
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.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
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.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults.textFieldColors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.rounded.*
import androidx.compose.runtime.*
import androidx.compose.material.icons.rounded.CardGiftcard
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
@ -146,10 +174,15 @@ fun SearchPanel(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
modifier = modifier.padding(12.dp).fillMaxWidth()
.border(
BorderStroke(2.dp, Brush.horizontalGradient(listOf(
BorderStroke(
2.dp,
Brush.horizontalGradient(
listOf(
colorPrimary,
colorAccent
))),
)
)
),
RoundedCornerShape(30.dp)
),
shape = RoundedCornerShape(size = 30.dp),
@ -168,10 +201,15 @@ fun SearchPanel(
onSearch(link)
}
},
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(
border = BorderStroke(
1.dp,
Brush.horizontalGradient(
listOf(
colorPrimary,
colorAccent
)))
)
)
)
) {
Text(text = "Search", style = SpotiFlyerTypography.h6, modifier = Modifier.padding(4.dp))
}
@ -199,28 +237,35 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Spotify",
tint = Color.Unspecified,
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))
Icon(imageVector = GaanaLogo(),
Icon(
imageVector = GaanaLogo(),
"Open Gaana",
tint = Color.Unspecified,
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))
Icon(imageVector = YoutubeLogo(),
Icon(
imageVector = YoutubeLogo(),
"Open Youtube",
tint = Color.Unspecified,
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))
Icon(imageVector = YoutubeMusicLogo(),
Icon(
imageVector = YoutubeMusicLogo(),
"Open Youtube Music",
tint = Color.Unspecified,
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
)
Spacer(modifier = Modifier.padding(top = 6.dp))
Row(verticalAlignment = Alignment.CenterVertically,
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable(
onClick = { openPlatform("","http://github.com/Shabinder/SpotiFlyer") })
onClick = { openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }
)
.padding(vertical = 6.dp)
) {
Icon(imageVector = GithubLogo(), "Open Project Repo", tint = Color(0xFFCCCCCC))
@ -293,9 +340,11 @@ fun AboutColumn(modifier: Modifier = Modifier) {
}
Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = {
.clickable(
onClick = {
shareApp()
}),
}
),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Share, "Share SpotiFlyer App")
@ -325,7 +374,8 @@ fun HistoryColumn(
Crossfade(list) {
if (it.isEmpty()) {
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
)
Text("No History Available", style = SpotiFlyerTypography.h4.copy(fontWeight = FontWeight.Light), textAlign = TextAlign.Center)
@ -375,15 +425,16 @@ fun DownloadRecordItem(
Image(
imageVector = ShareImage(),
"Research",
modifier = Modifier.clickable(onClick = {
modifier = Modifier.clickable(
onClick = {
// if(!isOnline(ctx)) showDialog("Check Your Internet Connection") else
onItemClicked(item.link)
})
}
)
)
}
}
@Composable
fun HomeCategoryTabIndicator(
modifier: Modifier = Modifier,

View File

@ -16,11 +16,24 @@
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.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.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.Text
import androidx.compose.material.TopAppBar

View File

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

View File

@ -23,7 +23,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
expect fun montserratFont(): FontFamily
expect fun pristineFont(): FontFamily

View File

@ -17,7 +17,13 @@
package com.shabinder.common.uikit.splash
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.Text
import androidx.compose.runtime.Composable
@ -29,7 +35,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
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
private const val SplashWaitTime: Long = 2000

View File

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

View File

@ -19,7 +19,12 @@ package com.shabinder.common.uikit
import androidx.compose.animation.Crossfade
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.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
@ -97,7 +102,6 @@ actual fun ShareImage():ImageVector = vectorXmlResource("drawable/ic_share_open.
@Composable
actual fun PlaceHolderImage(): ImageVector = vectorXmlResource("drawable/music.xml")
@Composable
actual fun SpotiFlyerLogo(): ImageVector =
vectorXmlResource("drawable/ic_spotiflyer_logo.xml")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,6 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkRequest
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.lifecycle.LiveData
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)
*/
override fun onAvailable(network: Network) {
Log.d(TAG, "onAvailable: ${network}")
Log.d(TAG, "onAvailable: $network")
val networkCapabilities = cm.getNetworkCapabilities(network)
val hasInternetCapability = networkCapabilities?.hasCapability(NET_CAPABILITY_INTERNET)
Log.d(TAG, "onAvailable: ${network}, $hasInternetCapability")
Log.d(TAG, "onAvailable: $network, $hasInternetCapability")
if (hasInternetCapability == true) {
// check if this network actually has internet
CoroutineScope(Dispatchers.IO).launch {
val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory)
if (hasInternet) {
withContext(Dispatchers.Main) {
Log.d(TAG, "onAvailable: adding network. ${network}")
Log.d(TAG, "onAvailable: adding network. $network")
validNetworks.add(network)
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)
*/
override fun onLost(network: Network) {
Log.d(TAG, "onLost: ${network}")
Log.d(TAG, "onLost: $network")
validNetworks.remove(network)
checkValidNetworks()
}
}
/**

View File

@ -16,7 +16,11 @@
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.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner

View File

@ -23,7 +23,7 @@ import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import io.ktor.client.*
import io.ktor.client.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@ -17,15 +17,22 @@
package com.shabinder.common.di.worker
import android.annotation.SuppressLint
import android.app.*
import android.app.DownloadManager
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.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.*
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
@ -33,18 +40,28 @@ import androidx.core.net.toUri
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
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.FetchPlatformQueryResult
import com.shabinder.common.di.R
import com.shabinder.common.di.getData
import com.shabinder.common.models.DownloadStatus
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 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 java.io.File
import java.util.*
import kotlin.coroutines.CoroutineContext
class ForegroundService : Service(), CoroutineScope {
@ -78,7 +95,6 @@ class ForegroundService : Service(),CoroutineScope{
private val ytDownloader: YoutubeDownloader
get() = fetcher.youtubeProvider.ytDownloader
override fun onBind(intent: Intent): IBinder? = null
@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"
))
)
)
downloadObjects?.let { list ->
downloadObjects.size.let { size ->
@ -175,7 +193,6 @@ class ForegroundService : Service(),CoroutineScope{
}
}
private fun downloadTrack(videoID: String, track: TrackDetails) {
launch {
try {
@ -193,19 +210,19 @@ class ForegroundService : Service(),CoroutineScope{
}
}
private fun enqueueDownload(url: String, track: TrackDetails) {
val request = Request(url, track.outputFilePath).apply {
priority = Priority.NORMAL
networkType = NetworkType.ALL
}
fetch.enqueue(request,
fetch.enqueue(
request,
{ request1 ->
requestMap[request1] = track
logger.d(tag) { "Enqueuing Download" }
},
{ 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 {
requestMap[download.request]?.run {
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 {
action = "Progress"
@ -375,8 +391,6 @@ class ForegroundService : Service(),CoroutineScope{
}
}
/**
* 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 {
setSmallIcon(drawable.ic_download_arrow)
setSmallIcon(R.drawable.ic_download_arrow)
setContentTitle("Total: $total Completed:$converted Failed:$failed")
setSilent(true)
setStyle(
@ -479,7 +493,7 @@ class ForegroundService : Service(),CoroutineScope{
addLine(messageList[messageList.size - 5])
}
)
addAction(drawable.ic_round_cancel_24,"Exit",cancelIntent)
addAction(R.drawable.ic_round_cancel_24, "Exit", cancelIntent)
build()
}

View File

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

View File

@ -22,9 +22,10 @@ import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.client.request.get
import io.ktor.client.statement.HttpStatement
import io.ktor.http.contentLength
import io.ktor.http.isSuccess
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt

View File

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

View File

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

View File

@ -19,9 +19,13 @@ package com.shabinder.common.di.gaana
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.corsProxy
import com.shabinder.common.models.gaana.*
import io.ktor.client.*
import io.ktor.client.request.*
import com.shabinder.common.models.gaana.GaanaAlbum
import com.shabinder.common.models.gaana.GaanaArtistDetails
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) {
corsProxy.url

View File

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

View File

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

View File

@ -21,10 +21,7 @@ import com.shabinder.common.di.Dir
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.youtubeMp3.Yt1sMp3
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.CorsProxy
import com.shabinder.common.models.corsProxy
import com.shabinder.database.Database
import io.ktor.client.*
import io.ktor.client.HttpClient
class YoutubeMp3(
override val httpClient: HttpClient,

View File

@ -21,10 +21,21 @@ import com.shabinder.common.di.gaana.corsApi
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.YoutubeTrack
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.json.*
import io.ktor.client.HttpClient
import io.ktor.client.request.headers
import io.ktor.client.request.post
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import kotlin.math.absoluteValue
private const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
@ -65,8 +76,10 @@ class YoutubeMusic constructor(
continue
}
for(contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray
?: listOf()){
for (
contents in cBlock.jsonObject["musicShelfRenderer"]?.jsonObject?.get("contents")?.jsonArray
?: listOf()
) {
/**
* apparently content Blocks without an 'overlay' field don't have linkBlocks
* I have no clue what they are and why there even exist
@ -219,7 +232,6 @@ class YoutubeMusic constructor(
continue
}
// Find artist match
// 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
@ -259,7 +271,9 @@ class YoutubeMusic constructor(
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
}
// 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import io.ktor.client.request.*
import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow
@ -91,8 +91,11 @@ actual suspend fun downloadTracks(
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery, it)
if (videoId.isNullOrBlank()) {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(it.title, DownloadStatus.Failed) }
)
} else { // Found Youtube Video ID
downloadTrack(videoId, it, dir::saveFileWithMetadata)
}
@ -116,17 +119,26 @@ suspend fun downloadTrack(
downloadFile(url).collect {
when (it) {
is DownloadResult.Error -> {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Failed) })
DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
)
}
is DownloadResult.Progress -> {
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloading(it.progress)) })
DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloading(it.progress)) }
)
}
is DownloadResult.Success -> { // Todo clear map
saveFileWithMetaData(it.byteArray, trackDetails)
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloaded) })
DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Downloaded) }
)
}
}
}

View File

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

View File

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

View File

@ -23,7 +23,7 @@ import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import io.ktor.client.*
import io.ktor.client.HttpClient
actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient,

View File

@ -20,10 +20,12 @@ import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import io.ktor.client.request.*
import kotlinx.coroutines.*
import io.ktor.client.request.head
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
actual val currentPlatform: AllPlatforms = AllPlatforms.Js
@ -78,7 +80,7 @@ actual suspend fun downloadTracks(
dir: Dir
) {
list.forEach {
withContext(Dispatchers.Default) {
withContext(dispatcherIO) {
allTracksStatus[it.title] = DownloadStatus.Queued
if (!it.videoID.isNullOrBlank()) { // Video ID already known!
downloadTrack(it.videoID!!, it, fetcher, dir)

View File

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

View File

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

View File

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

View File

@ -16,10 +16,16 @@
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.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.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus
@ -38,7 +44,9 @@ internal class SpotiFlyerListStoreProvider(
) {
val logger = getLogger()
fun provide(): SpotiFlyerListStore =
object : SpotiFlyerListStore, Store<Intent, State, Nothing> by storeFactory.create(
object :
SpotiFlyerListStore,
Store<Intent, State, Nothing> by storeFactory.create(
name = "SpotiFlyerListStore",
initialState = State(),
bootstrapper = SimpleBootstrapper(Unit),

View File

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

View File

@ -21,7 +21,10 @@ import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.di.Picture
import com.shabinder.common.di.isInternetAvailable
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.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore

View File

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

View File

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

View File

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

View File

@ -16,7 +16,12 @@
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.Parcelize
import com.arkivanov.decompose.value.Value

View File

@ -18,7 +18,6 @@ import androidx.compose.desktop.DesktopMaterialTheme
import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.initKoin
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.common.uikit.showPopUpMessage as uikitShowPopUpMessage
private val koin = initKoin(enableNetworkLogs = true).koin
fun main() {
val lifecycle = LifecycleRegistry()
@ -54,7 +56,7 @@ fun main(){
typography = SpotiFlyerTypography,
shapes = SpotiFlyerShapes
) {
val callBacks = SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot)).callBacks
SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot))
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,10 +14,28 @@
* * 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
val colorPrimary = Color("#FC5C7D")
val colorPrimaryDark = Color("#CE1CFF")
val colorAccent = Color("#9AB3FF")

View File

@ -16,9 +16,13 @@
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.collect
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import react.RComponent
import react.RProps
import react.RState

View File

@ -19,7 +19,10 @@ package extras
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.ValueObserver
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.RProps
import react.RState

View File

@ -16,16 +16,22 @@
package home
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.models.AllPlatforms
import extras.RenderableComponent
import kotlinx.browser.document
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 react.*
import react.RBuilder
import styled.css
import styled.styledDiv

View File

@ -16,14 +16,23 @@
package home
import kotlinx.browser.document
import kotlinx.css.*
import kotlinx.dom.appendElement
import kotlinx.dom.createElement
import kotlinx.html.SCRIPT
import Styles
import kotlinx.css.borderRadius
import kotlinx.css.height
import kotlinx.css.margin
import kotlinx.css.px
import kotlinx.css.width
import kotlinx.html.id
import react.*
import styled.*
import react.RBuilder
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 {
var iconsAndPlatforms: Map<String,String>

View File

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

View File

@ -22,9 +22,15 @@ import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onKeyDownFunction
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.Window
import react.*
import styled.*
import react.RBuilder
import react.RProps
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 {
var link: String

View File

@ -16,8 +16,18 @@
package list
import kotlinx.css.*
import react.*
import kotlinx.css.Display
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.styledDiv
import styled.styledSpan

View File

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

View File

@ -16,10 +16,26 @@
package list
import kotlinx.css.*
import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.JustifyContent
import kotlinx.css.WhiteSpace
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.fontSize
import kotlinx.css.height
import kotlinx.css.justifyContent
import kotlinx.css.px
import kotlinx.css.whiteSpace
import kotlinx.html.id
import kotlinx.html.js.onClickFunction
import react.*
import react.RBuilder
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.styledH5

View File

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

View File

@ -20,7 +20,15 @@ import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.State
import extras.RenderableComponent
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 react.RBuilder
import styled.css
@ -65,7 +73,7 @@ class ListScreen(
flexDirection = FlexDirection.column
color = Color.white
}
state.data.trackList.forEachIndexed{ index, trackDetails ->
state.data.trackList.forEachIndexed{ _, trackDetails ->
TrackItem {
details = trackDetails
downloadTrack = model::onDownloadClicked

View File

@ -16,12 +16,22 @@
package list
import kotlinx.css.*
import react.*
import kotlinx.css.Align
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.styledDiv
@Suppress("FunctionName")
fun RBuilder.LoadingAnim(handler: RProps.() -> Unit): ReactElement {
return child(loadingAnim){

View File

@ -19,7 +19,11 @@ package list
import kotlinx.css.marginRight
import kotlinx.css.px
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.styledDiv

View File

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

View File

@ -16,12 +16,33 @@
package navbar
import kotlinx.css.*
import kotlinx.css.Align
import kotlinx.css.Display
import kotlinx.css.LinearDimension
import kotlinx.css.alignItems
import kotlinx.css.display
import kotlinx.css.filter
import kotlinx.css.fontSize
import kotlinx.css.height
import kotlinx.css.margin
import kotlinx.css.marginLeft
import kotlinx.css.marginRight
import kotlinx.css.px
import kotlinx.css.width
import kotlinx.html.id
import kotlinx.html.js.onBlurFunction
import kotlinx.html.js.onClickFunction
import react.*
import styled.*
import react.RBuilder
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")
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{

View File

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