Web App Dep Updation and Moving to IR backend

This commit is contained in:
shabinder 2021-06-19 23:27:38 +05:30
parent 034aa246d8
commit 2cdb764430
31 changed files with 180 additions and 427 deletions

View File

@ -129,7 +129,7 @@ dependencies {
} }
//implementation("com.jakewharton.timber:timber:4.7.1") //implementation("com.jakewharton.timber:timber:4.7.1")
implementation("dev.icerock.moko:parcelize:0.6.1") implementation("dev.icerock.moko:parcelize:0.7.0")
implementation("com.github.shabinder:storage-chooser:2.0.4.45") implementation("com.github.shabinder:storage-chooser:2.0.4.45")
implementation("com.google.accompanist:accompanist-insets:0.11.1") implementation("com.google.accompanist:accompanist-insets:0.11.1")

View File

@ -41,7 +41,7 @@ class App: Application(), KoinComponent {
val tracker: Tracker by lazy { val tracker: Tracker by lazy {
TrackerBuilder.createDefault( TrackerBuilder.createDefault(
"https://kind-grasshopper-73.telebit.io/matomo/matomo.php", 1) "https://matomo.spotiflyer.ml/matomo.php", 1)
.build(Matomo.getInstance(this)).apply { .build(Matomo.getInstance(this)).apply {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
/*Timber.plant(DebugTree()) /*Timber.plant(DebugTree())
@ -85,7 +85,7 @@ class App: Application(), KoinComponent {
} }
// Send Crash Report to self hosted Acrarium (FOSS) // Send Crash Report to self hosted Acrarium (FOSS)
httpSender { httpSender {
uri = "https://kind-grasshopper-73.telebit.io/acrarium/report" uri = "https://acrarium.spotiflyer.ml/report"
basicAuthLogin = "sDj2xCKQIxw0dujf" basicAuthLogin = "sDj2xCKQIxw0dujf"
basicAuthPassword = "O83du0TsgsDJ69zN" basicAuthPassword = "O83du0TsgsDJ69zN"
httpMethod = HttpSender.Method.POST httpMethod = HttpSender.Method.POST

View File

@ -42,7 +42,7 @@ kotlin {
implementation(compose.animation) implementation(compose.animation)
implementation(Extras.kermit) implementation(Extras.kermit)
implementation("dev.icerock.moko:parcelize:0.6.1") implementation("dev.icerock.moko:parcelize:0.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
isForce = true isForce = true

View File

@ -34,7 +34,7 @@ kotlin {
jvm("desktop") jvm("desktop")
android() android()
js(/*BOTH*/) { js(BOTH) {
browser() browser()
// nodejs() // nodejs()
} }

View File

@ -37,7 +37,7 @@ kotlin {
jvm("desktop") jvm("desktop")
android() android()
js(/*BOTH*/) { js(BOTH) {
browser() browser()
// nodejs() // nodejs()
} }
@ -65,7 +65,7 @@ kotlin {
implementation(Extras.kermit) implementation(Extras.kermit)
implementation(Serialization.json) implementation(Serialization.json)
implementation("co.touchlab:stately-common:1.1.7") implementation("co.touchlab:stately-common:1.1.7")
implementation("dev.icerock.moko:parcelize:0.6.1") implementation("dev.icerock.moko:parcelize:0.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
isForce = true isForce = true
@ -102,9 +102,9 @@ kotlin {
named("jsMain") { named("jsMain") {
dependencies { dependencies {
implementation(Ktor.clientJs) implementation(Ktor.clientJs)
implementation("org.jetbrains:kotlin-react:17.0.1-pre.148-kotlin-1.4.30") implementation("org.jetbrains.kotlin-wrappers:kotlin-react:17.0.2-pre.213-kotlin-1.5.10")
implementation("org.jetbrains:kotlin-styled:1.0.0-pre.115-kotlin-1.4.10") implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:17.0.2-pre.213-kotlin-1.5.10")
implementation("org.jetbrains:kotlin-react-dom:17.0.1-pre.148-kotlin-1.4.30") implementation("org.jetbrains.kotlin-wrappers:kotlin-styled:5.3.0-pre.213-kotlin-1.5.10")
} }
} }
if(HostOS.isMac){ if(HostOS.isMac){

View File

@ -17,32 +17,12 @@
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.Arrangement 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.fillMaxHeight
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.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.*
import androidx.compose.material.ExperimentalMaterialApi 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.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.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
@ -51,7 +31,7 @@ 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
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.arkivanov.decompose.extensions.compose.jetbrains.asState import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
import com.shabinder.common.di.Picture 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
@ -64,7 +44,7 @@ fun SpotiFlyerListContent(
component: SpotiFlyerList, component: SpotiFlyerList,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val model by component.models.asState() val model by component.model.subscribeAsState()
LaunchedEffect(model.errorOccurred) { LaunchedEffect(model.errorOccurred) {
/*Handle if Any Exception Occurred*/ /*Handle if Any Exception Occurred*/

View File

@ -17,57 +17,21 @@
package com.shabinder.common.uikit package com.shabinder.common.uikit
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.*
import androidx.compose.foundation.Image import androidx.compose.foundation.layout.*
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
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.foundation.verticalScroll import androidx.compose.material.*
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
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.CardGiftcard import androidx.compose.material.icons.rounded.*
import androidx.compose.material.icons.rounded.Edit import androidx.compose.runtime.*
import androidx.compose.material.icons.rounded.Flag
import androidx.compose.material.icons.rounded.Insights
import androidx.compose.material.icons.rounded.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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
@ -80,7 +44,7 @@ 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
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.arkivanov.decompose.extensions.compose.jetbrains.asState import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
@ -89,7 +53,7 @@ import com.shabinder.common.models.methods
@Composable @Composable
fun SpotiFlyerMainContent(component: SpotiFlyerMain) { fun SpotiFlyerMainContent(component: SpotiFlyerMain) {
val model by component.models.asState() val model by component.model.subscribeAsState()
Column { Column {
SearchPanel( SearchPanel(

View File

@ -17,7 +17,7 @@
package com.shabinder.common.models package com.shabinder.common.models
sealed class CorsProxy(open val url: String) { sealed class CorsProxy(open val url: String) {
data class SelfHostedCorsProxy(override val url: String = "https://kind-grasshopper-73.telebit.io/cors/" /*"https://spotiflyer.azurewebsites.net/"*/) : CorsProxy(url) data class SelfHostedCorsProxy(override val url: String = "https://cors.spotiflyer.ml/cors/" /*"https://spotiflyer.azurewebsites.net/"*/) : CorsProxy(url)
data class PublicProxyWithExtension(override val url: String = "https://cors.bridged.cc/") : CorsProxy(url) data class PublicProxyWithExtension(override val url: String = "https://cors.bridged.cc/") : CorsProxy(url)
fun toggle(mode: CorsProxy? = null): CorsProxy { fun toggle(mode: CorsProxy? = null): CorsProxy {

View File

@ -21,7 +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 io.ktor.client.HttpClient import io.ktor.client.*
class YoutubeMp3( class YoutubeMp3(
override val httpClient: HttpClient, override val httpClient: HttpClient,
@ -33,7 +33,7 @@ class YoutubeMp3(
getLinkFromYt1sMp3(videoID)?.let { getLinkFromYt1sMp3(videoID)?.let {
logger.i { "Download Link: $it" } logger.i { "Download Link: $it" }
if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/) if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/)
"https://kind-grasshopper-73.telebit.io/cors/$it" "https://cors.spotiflyer.ml/cors/$it"
// "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue // "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue
else it else it
} }

View File

@ -28,7 +28,7 @@ import io.github.shabinder.YoutubeDownloader
import io.github.shabinder.models.YoutubeVideo import io.github.shabinder.models.YoutubeVideo
import io.github.shabinder.models.formats.Format import io.github.shabinder.models.formats.Format
import io.github.shabinder.models.quality.AudioQuality import io.github.shabinder.models.quality.AudioQuality
import io.ktor.client.HttpClient import io.ktor.client.*
class YoutubeProvider( class YoutubeProvider(
private val httpClient: HttpClient, private val httpClient: HttpClient,
@ -37,7 +37,7 @@ class YoutubeProvider(
) { ) {
val ytDownloader: YoutubeDownloader = YoutubeDownloader( val ytDownloader: YoutubeDownloader = YoutubeDownloader(
enableCORSProxy = true, enableCORSProxy = true,
CORSProxyAddress = "https://kind-grasshopper-73.telebit.io/cors/" CORSProxyAddress = "https://cors.spotiflyer.ml/cors/"
) )
/* /*
@ -157,7 +157,7 @@ class YoutubeProvider(
val video = ytDownloader.getVideo(searchId) val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg" coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video.videoDetails val detail = video.videoDetails
val name = detail.title?.replace(detail.author?.toUpperCase() ?: "", "", true) val name = detail.title?.replace(detail.author?.uppercase() ?: "", "", true)
?: detail.title ?: "" ?: detail.title ?: ""
// logger.i{ detail.toString() } // logger.i{ detail.toString() }
trackList = listOf( trackList = listOf(

View File

@ -16,6 +16,8 @@
package com.shabinder.common.di.utils package com.shabinder.common.di.utils
import io.github.shabinder.TargetPlatforms
import io.github.shabinder.activePlatform
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlin.native.concurrent.ThreadLocal import kotlin.native.concurrent.ThreadLocal
@ -31,6 +33,7 @@ val json by lazy {
* Removing Illegal Chars from File Name * Removing Illegal Chars from File Name
* **/ * **/
fun removeIllegalChars(fileName: String): String { fun removeIllegalChars(fileName: String): String {
if (activePlatform is TargetPlatforms.Js) return fileName
val illegalCharArray = charArrayOf( val illegalCharArray = charArrayOf(
'/', '/',
'\n', '\n',

View File

@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
interface SpotiFlyerList { interface SpotiFlyerList {
val models: Value<State> val model: Value<State>
/* /*
* Download All Tracks(after filtering already Downloaded) * Download All Tracks(after filtering already Downloaded)

View File

@ -56,7 +56,7 @@ internal class SpotiFlyerListImpl(
.maximumCacheSize(75) .maximumCacheSize(75)
.build<String, Picture>() .build<String, Picture>()
override val models: Value<State> = store.asValue() override val model: Value<State> = store.asValue()
override fun onDownloadAllClicked(trackList: List<TrackDetails>) { override fun onDownloadAllClicked(trackList: List<TrackDetails>) {
store.accept(Intent.StartDownloadAll(trackList)) store.accept(Intent.StartDownloadAll(trackList))

View File

@ -28,7 +28,7 @@ import com.shabinder.database.Database
interface SpotiFlyerMain { interface SpotiFlyerMain {
val models: Value<State> val model: Value<State>
val analytics: Analytics val analytics: Analytics

View File

@ -23,10 +23,7 @@ import com.shabinder.common.caching.Cache
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.utils.asValue import com.shabinder.common.di.utils.asValue
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.*
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
@ -55,7 +52,7 @@ internal class SpotiFlyerMainImpl(
.maximumCacheSize(25) .maximumCacheSize(25)
.build<String, Picture>() .build<String, Picture>()
override val models: Value<State> = store.asValue() override val model: Value<State> = store.asValue()
override val analytics = mainAnalytics override val analytics = mainAnalytics

View File

@ -27,24 +27,12 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponen
import com.arkivanov.mvikotlin.core.lifecycle.LifecycleRegistry import com.arkivanov.mvikotlin.core.lifecycle.LifecycleRegistry
import com.arkivanov.mvikotlin.core.lifecycle.resume import com.arkivanov.mvikotlin.core.lifecycle.resume
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.di.Dir import com.shabinder.common.di.*
import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.firstLaunchDone
import com.shabinder.common.di.initKoin
import com.shabinder.common.di.isFirstLaunch
import com.shabinder.common.di.isInternetAccessible
import com.shabinder.common.di.setDownloadDirectory
import com.shabinder.common.di.toggleAnalytics
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.uikit.SpotiFlyerColors import com.shabinder.common.uikit.*
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 kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.piwik.java.tracking.PiwikTracker import org.piwik.java.tracking.PiwikTracker
@ -58,7 +46,7 @@ import javax.swing.JFileChooser.APPROVE_OPTION
private val koin = initKoin(enableNetworkLogs = true).koin private val koin = initKoin(enableNetworkLogs = true).koin
private lateinit var showToast: (String)->Unit private lateinit var showToast: (String)->Unit
private val tracker: PiwikTracker by lazy { private val tracker: PiwikTracker by lazy {
PiwikTracker("https://kind-grasshopper-73.telebit.io/matomo/matomo.php") PiwikTracker("https://matomo.spotiflyer.ml/matomo.php")
} }
fun main() { fun main() {

View File

@ -35,12 +35,6 @@ dependencies {
implementation(MVIKotlin.coroutines) implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlinMain) implementation(MVIKotlin.mvikotlinMain)
implementation(MVIKotlin.mvikotlinLogging) implementation(MVIKotlin.mvikotlinLogging)
implementation(Ktor.auth)
implementation(Ktor.clientJs)
implementation(Ktor.clientJson)
implementation(Ktor.clientCore)
implementation(Ktor.clientLogging)
implementation(Ktor.clientSerialization)
implementation(project(":common:root")) implementation(project(":common:root"))
implementation(project(":common:main")) implementation(project(":common:main"))
implementation(project(":common:list")) implementation(project(":common:list"))
@ -48,20 +42,23 @@ dependencies {
implementation(project(":common:data-models")) implementation(project(":common:data-models"))
implementation(project(":common:dependency-injection")) implementation(project(":common:dependency-injection"))
implementation("co.touchlab:stately-common:1.1.7") implementation("co.touchlab:stately-common:1.1.7")
implementation("dev.icerock.moko:parcelize:0.6.1") implementation("dev.icerock.moko:parcelize:0.7.0")
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1") // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1") {
// https://youtrack.jetbrains.com/issue/KTOR-2670 // https://youtrack.jetbrains.com/issue/KTOR-2670
isForce = true isForce = true
} }
implementation("org.jetbrains:kotlin-react:17.0.1-pre.148-kotlin-1.4.30") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") {
implementation("org.jetbrains:kotlin-react-dom:17.0.1-pre.148-kotlin-1.4.30") @Suppress("DEPRECATION")
implementation("org.jetbrains:kotlin-styled:1.0.0-pre.115-kotlin-1.4.10") isForce = true
implementation("org.jetbrains:kotlin-react-router-dom:5.2.0-pre.148-kotlin-1.4.30") }
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:17.0.2-pre.213-kotlin-1.5.10")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:17.0.2-pre.213-kotlin-1.5.10")
implementation("org.jetbrains.kotlin-wrappers:kotlin-styled:5.3.0-pre.213-kotlin-1.5.10")
} }
kotlin { kotlin {
js { js(IR) {
//useCommonJs() //useCommonJs()
browser { browser {
webpackTask { webpackTask {
@ -77,5 +74,6 @@ kotlin {
} }
} }
} }
binaries.executable()
} }
} }

View File

@ -28,11 +28,7 @@ import com.shabinder.common.models.TrackDetails
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.RBuilder import react.*
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 {
@ -46,6 +42,10 @@ fun RBuilder.App(attrs: AppProps.() -> Unit): ReactElement {
} }
} }
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED", "NON_EXPORTABLE_TYPE")
@OptIn(ExperimentalJsExport::class)
@JsExport
class App(props: AppProps): RComponent<AppProps, RState>(props) { class App(props: AppProps): RComponent<AppProps, RState>(props) {
private val lifecycle = LifecycleRegistry() private val lifecycle = LifecycleRegistry()

View File

@ -17,53 +17,63 @@
package extras package extras
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import kotlinx.coroutines.CoroutineScope import com.arkivanov.decompose.value.ValueObserver
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.RComponent
import react.RProps import react.RProps
import react.RState import react.RState
import react.setState import react.setState
abstract class RenderableComponent<
T : Any, @Suppress("EXPERIMENTAL_IS_NOT_ENABLED", "NON_EXPORTABLE_TYPE")
S : Any @OptIn(ExperimentalJsExport::class)
>( @JsExport
abstract class RenderableComponent<T: Any, S: RState>(
props: Props<T>, props: Props<T>,
initialState: S initialState: S
) : RComponent<RenderableComponent.Props<T>, RenderableComponent.State<S>>(props) { ) : RComponent<Props<T>, S>(props) {
protected abstract val stateFlow: Value<S> private val subscriptions = ArrayList<Subscription<*>>()
protected val model: T get() = props.model protected val component: T get() = props.component
protected var scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
init { init {
state = State(data = initialState) state = initialState
} }
override fun componentDidMount() { override fun componentDidMount() {
if(!scope.isActive) subscriptions.forEach { subscribe(it) }
scope = CoroutineScope(Dispatchers.Default)
scope.launch {
stateFlow.subscribe {
setState { data = it }
}
} }
private fun <T : Any> subscribe(subscription: Subscription<T>) {
subscription.value.subscribe(subscription.observer)
} }
override fun componentWillUnmount() { override fun componentWillUnmount() {
scope.cancel("Component Unmounted") subscriptions.forEach { unsubscribe(it) }
} }
interface Props<T : Any> : RProps { private fun <T : Any> unsubscribe(subscription: Subscription<T>) {
subscription.value.unsubscribe(subscription.observer)
}
protected fun <T : Any> Value<T>.bindToState(buildState: S.(T) -> Unit) {
subscriptions += Subscription(this) { data -> setState { buildState(data) } }
}
protected class Subscription<T : Any>(
val value: Value<T>,
val observer: ValueObserver<T>
)
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED", "NON_EXPORTABLE_TYPE")
@OptIn(ExperimentalJsExport::class)
@JsExport
class RStateWrapper<T>(
var model: T var model: T
}
class State<S>(
var data: S
) : RState ) : RState
external interface Props<T : Any> : RProps {
var component: T
} }

View File

@ -1,78 +0,0 @@
/*
* * Copyright (c) 2021 Shabinder Singh
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package extras
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.ValueObserver
import extras.RenderableRootComponent.Props
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
import react.setState
abstract class RenderableRootComponent<
T : Any,
S : RState
>(
props: Props<T>,
initialState: S
) : RComponent<Props<T>, S>(props) {
protected val model: T get() = props.model
private val subscriptions = ArrayList<Subscription<*>>()
protected var scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
init {
this.state = initialState
}
override fun componentDidMount() {
subscriptions.forEach { subscribe(it) }
if(!scope.isActive)
scope = CoroutineScope(Dispatchers.Default)
}
private fun <T : Any> subscribe(subscription: Subscription<T>) {
subscription.value.subscribe(subscription.observer)
}
override fun componentWillUnmount() {
subscriptions.forEach { unsubscribe(it) }
scope.cancel("Component Unmounted")
}
private fun <T : Any> unsubscribe(subscription: Subscription<T>) {
subscription.value.unsubscribe(subscription.observer)
}
protected fun <T : Any> Value<T>.bindToState(buildState: S.(T) -> Unit) {
subscriptions += Subscription(this) { data -> setState { buildState(data) } }
}
interface Props<T : Any> : RProps {
var model: T
}
protected class Subscription<T : Any>(
val value: Value<T>,
val observer: ValueObserver<T>
)
}

View File

@ -19,15 +19,9 @@ package extras
import react.RBuilder import react.RBuilder
import kotlin.reflect.KClass import kotlin.reflect.KClass
fun <M : Any, T : RenderableRootComponent<M, *>> RBuilder.renderableChild(clazz: KClass<out T>, model: M) {
child(clazz) {
key = model.uniqueId().toString()
attrs.model = model
}
}
fun <M : Any, T : RenderableComponent<M, *>> RBuilder.renderableChild(clazz: KClass<out T>, model: M) { fun <M : Any, T : RenderableComponent<M, *>> RBuilder.renderableChild(clazz: KClass<out T>, model: M) {
child(clazz) { child(clazz) {
key = model.uniqueId().toString() key = model.uniqueId().toString()
attrs.model = model attrs.component = model
} }
} }

View File

@ -16,21 +16,12 @@
package home package home
import com.arkivanov.decompose.value.Value
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State import extras.Props
import extras.RStateWrapper
import extras.RenderableComponent import extras.RenderableComponent
import kotlinx.browser.document 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 kotlinx.dom.appendElement
import react.RBuilder import react.RBuilder
import styled.css import styled.css
@ -38,12 +29,20 @@ import styled.styledDiv
class HomeScreen( class HomeScreen(
props: Props<SpotiFlyerMain>, props: Props<SpotiFlyerMain>,
) : RenderableComponent<SpotiFlyerMain, State>( ) : RenderableComponent<SpotiFlyerMain, RStateWrapper<SpotiFlyerMain.State>>(
props, props,
initialState = State() initialState = RStateWrapper(props.component.model.value)
) { ) {
init {
component.model.bindToState {
model = it
}
}
override fun componentDidMount() { override fun componentDidMount() {
super.componentDidMount() super.componentDidMount()
// RazorPay Button
val form = document.getElementById("razorpay-form")!! val form = document.getElementById("razorpay-form")!!
repeat(form.childNodes.length){ repeat(form.childNodes.length){
form.childNodes.item(0)?.let { it1 -> form.removeChild(it1) } form.childNodes.item(0)?.let { it1 -> form.removeChild(it1) }
@ -56,8 +55,6 @@ class HomeScreen(
} }
} }
override val stateFlow: Value<SpotiFlyerMain.State> = model.models
override fun RBuilder.render() { override fun RBuilder.render() {
styledDiv{ styledDiv{
css { css {
@ -73,9 +70,9 @@ class HomeScreen(
} }
SearchBar { SearchBar {
link = state.data.link link = state.model.link
search = model::onLinkSearch search = component::onLinkSearch
onLinkChange = model::onInputLinkChanged onLinkChange = component::onInputLinkChanged
} }
IconList { IconList {

View File

@ -17,22 +17,11 @@
package home package home
import Styles import Styles
import kotlinx.css.borderRadius import kotlinx.css.*
import kotlinx.css.height
import kotlinx.css.margin
import kotlinx.css.px
import kotlinx.css.width
import kotlinx.html.id import kotlinx.html.id
import react.RBuilder import react.*
import react.RProps import react.dom.attrs
import react.ReactElement import styled.*
import react.child
import react.functionalComponent
import styled.css
import styled.styledA
import styled.styledDiv
import styled.styledForm
import styled.styledImg
external interface IconListProps : RProps { external interface IconListProps : RProps {
var iconsAndPlatforms: Map<String,String> var iconsAndPlatforms: Map<String,String>

View File

@ -25,12 +25,9 @@ import org.w3c.dom.HTMLInputElement
import react.RBuilder import react.RBuilder
import react.RProps import react.RProps
import react.child import react.child
import react.dom.attrs
import react.functionalComponent import react.functionalComponent
import styled.css import styled.*
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
@ -45,8 +42,6 @@ fun RBuilder.SearchBar(handler:SearchbarProps.() -> Unit) = child(searchbar){
} }
} }
val searchbar = functionalComponent<SearchbarProps>("SearchBar"){ props -> val searchbar = functionalComponent<SearchbarProps>("SearchBar"){ props ->
styledDiv{ styledDiv{
css { css {

View File

@ -16,24 +16,10 @@
package list package list
import kotlinx.css.Align import kotlinx.css.*
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.RBuilder import react.*
import react.RProps import react.dom.attrs
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledH1 import styled.styledH1

View File

@ -16,26 +16,11 @@
package list package list
import kotlinx.css.Align import kotlinx.css.*
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.RBuilder import react.*
import react.RProps import react.dom.attrs
import react.ReactElement
import react.child
import react.functionalComponent
import react.useEffect
import react.useState
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledH5 import styled.styledH5

View File

@ -17,17 +17,10 @@
package list package list
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import kotlinx.css.borderRadius import kotlinx.css.*
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.RBuilder import react.*
import react.RProps import react.dom.attrs
import react.ReactElement
import react.child
import react.functionalComponent
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledImg import styled.styledImg

View File

@ -16,55 +16,55 @@
package list package list
import com.arkivanov.decompose.value.Value
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.State import com.shabinder.common.list.SpotiFlyerList.State
import extras.Props
import extras.RStateWrapper
import extras.RenderableComponent 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 kotlinx.html.id
import react.RBuilder import react.RBuilder
import react.dom.attrs
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledSection import styled.styledSection
class ListScreen( class ListScreen(
props: Props<SpotiFlyerList>, props: Props<SpotiFlyerList>,
) : RenderableComponent<SpotiFlyerList, State>(props,initialState = State()) { ) : RenderableComponent<SpotiFlyerList, RStateWrapper<State>>(
props,
override val stateFlow: Value<SpotiFlyerList.State> = model.models initialState = RStateWrapper(props.component.model.value)
) {
init {
component.model.bindToState {
model = it
}
}
override fun RBuilder.render() { override fun RBuilder.render() {
val result = state.data.queryResult val queryResult = state.model.queryResult
styledSection { styledSection {
attrs { attrs {
id = "list-screen" id = "list-screen"
} }
if(result == null) { if(queryResult == null) {
LoadingAnim { } LoadingAnim { }
}else { }else {
CoverImage { CoverImage {
coverImageURL = result.coverUrl coverImageURL = queryResult.coverUrl
coverName = result.title coverName = queryResult.title
} }
DownloadAllButton { DownloadAllButton {
isActive = state.data.trackList.size > 1 isActive = state.model.trackList.size > 1
downloadAll = { downloadAll = {
model.onDownloadAllClicked(state.data.trackList) component.onDownloadAllClicked(state.model.trackList)
} }
link = state.data.link link = state.model.link
} }
styledDiv { styledDiv {
@ -74,10 +74,10 @@ class ListScreen(
flexDirection = FlexDirection.column flexDirection = FlexDirection.column
color = Color.white color = Color.white
} }
state.data.trackList.forEachIndexed{ _, trackDetails -> state.model.trackList.forEachIndexed{ _, trackDetails ->
TrackItem { TrackItem {
details = trackDetails details = trackDetails
downloadTrack = model::onDownloadClicked downloadTrack = component::onDownloadClicked
} }
} }
} }

View File

@ -18,43 +18,11 @@ 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.Align import kotlinx.css.*
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.RBuilder import react.*
import react.RProps import react.dom.attrs
import react.ReactElement import styled.*
import react.child
import react.functionalComponent
import react.useEffect
import react.useState
import styled.css
import styled.styledDiv
import styled.styledH3
import styled.styledH4
import styled.styledImg
external interface TrackItemProps : RProps { external interface TrackItemProps : RProps {
var details:TrackDetails var details:TrackDetails

View File

@ -16,33 +16,13 @@
package navbar package navbar
import kotlinx.css.Align import kotlinx.css.*
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.RBuilder import react.*
import react.RProps import react.dom.attrs
import react.ReactElement import styled.*
import react.child
import react.functionalComponent
import styled.css
import styled.styledA
import styled.styledDiv
import styled.styledH1
import styled.styledImg
import styled.styledNav
@Suppress("FunctionName") @Suppress("FunctionName")
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{ fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{

View File

@ -19,7 +19,8 @@ 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.Child import com.shabinder.common.root.SpotiFlyerRoot.Child
import extras.RenderableRootComponent import extras.Props
import extras.RenderableComponent
import extras.renderableChild import extras.renderableChild
import home.HomeScreen import home.HomeScreen
import list.ListScreen import list.ListScreen
@ -27,31 +28,34 @@ import navbar.NavBar
import react.RBuilder import react.RBuilder
import react.RState import react.RState
class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRoot, RootR.State>( class RootR(props: Props<SpotiFlyerRoot>) : RenderableComponent<SpotiFlyerRoot, State>(
props = props, props = props,
initialState = State(routerState = props.model.routerState.value) initialState = State(routerState = props.component.routerState.value)
) { ) {
private val component: Child private val child: Child
get() = model.routerState.value.activeChild.instance get() = component.routerState.value.activeChild.instance
private val callBacks get() = model.callBacks private val callBacks get() = component.callBacks
init {
component.routerState.bindToState { routerState = it }
}
override fun RBuilder.render() { override fun RBuilder.render() {
NavBar { NavBar {
isBackVisible = (component is Child.List) isBackVisible = (child is Child.List)
popBackToHomeScreen = callBacks::popBackToHomeScreen popBackToHomeScreen = callBacks::popBackToHomeScreen
} }
when(component){ when(child){
is Child.Main -> renderableChild(HomeScreen::class, (component as Child.Main).component) is Child.Main -> renderableChild(HomeScreen::class, (child as Child.Main).component)
is Child.List -> renderableChild(ListScreen::class, (component as Child.List).component) is Child.List -> renderableChild(ListScreen::class, (child as Child.List).component)
}
} }
} }
init { @Suppress("NON_EXPORTABLE_TYPE", "EXPERIMENTAL_IS_NOT_ENABLED")
model.routerState.bindToState { routerState = it } @OptIn(ExperimentalJsExport::class)
} @JsExport
class State( class State(
var routerState: RouterState<*, Child> var routerState: RouterState<*, Child>
) : RState ) : RState
}