web-app and business logic integration (WIP) & gradle scripts refactor.

This commit is contained in:
shabinder 2021-03-08 01:28:47 +05:30
parent 9920885b3b
commit 42a24ce412
45 changed files with 543 additions and 181 deletions

View File

@ -72,7 +72,7 @@ dependencies {
implementation(project(":common:data-models"))
implementation(Koin.android)
implementation(Koin.androidViewModel)
implementation(Koin.compose)
//DECOMPOSE
implementation(Decompose.decompose)

View File

@ -48,6 +48,7 @@ import com.tonyodev.fetch2.Status
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.koin.android.ext.android.inject
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
const val disableDozeCode = 1223
@ -117,6 +118,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override val database = this@MainActivity.database
override val fetchPlatformQueryResult = this@MainActivity.fetcher
override val directories: Dir = this@MainActivity.dir
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
}
)
@ -234,7 +236,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override fun onPaymentError(errorCode: Int, response: String?) {
try{
showPopUpMessage("Payment Failed, Response:$response")
uikitShowPopUpMessage("Payment Failed, Response:$response")
}catch (e: Exception){
Log.d("Razorpay Payment","Exception in onPaymentSuccess $response")
}
@ -242,9 +244,9 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override fun onPaymentSuccess(razorpayPaymentId: String?) {
try{
showPopUpMessage("Payment Successful, ThankYou!")
uikitShowPopUpMessage("Payment Successful, ThankYou!")
}catch (e: Exception){
showPopUpMessage("Razorpay Payment, Error Occurred.")
uikitShowPopUpMessage("Razorpay Payment, Error Occurred.")
Log.d("Razorpay Payment","Exception in onPaymentSuccess, ${e.message}")
}
}

View File

@ -9,13 +9,13 @@ object Versions {
const val coilVersion = "0.4.1"
//DI
const val koin = "3.0.0-alpha-4"
const val koin = "3.0.1-beta-1"
//Logger
const val kermit = "0.1.8"
//Internet
const val ktor = "1.5.1"
const val ktor = "1.5.2"
const val kotlinxSerialization = "1.1.0-RC"
//Database
@ -32,10 +32,10 @@ object Versions {
const val androidLifecycle = "2.3.0"
}
object Koin {
val core = "org.koin:koin-core:${Versions.koin}"
val test = "org.koin:koin-test:${Versions.koin}"
val android = "org.koin:koin-android:${Versions.koin}"
val androidViewModel = "org.koin:koin-androidx-viewmodel:${Versions.koin}"
val core = "io.insert-koin:koin-core:${Versions.koin}"
val test = "io.insert-koin:koin-test:${Versions.koin}"
val android = "io.insert-koin:koin-android:${Versions.koin}"
val compose = "io.insert-koin:koin-androidx-compose:${Versions.koin}"
}
object Androidx{
const val androidxActivity = "androidx.activity:activity-compose:1.3.0-alpha02"

View File

@ -13,6 +13,7 @@ kotlin {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
}
}

View File

@ -2,9 +2,11 @@ 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("com.android.library")
id("android-setup")
id("kotlin-multiplatform")
id("org.jetbrains.compose")
}
@ -19,9 +21,7 @@ kotlin {
sourceSets {
named("commonMain") {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
}
}
@ -29,12 +29,22 @@ kotlin {
dependencies {
implementation("androidx.appcompat:appcompat:1.2.0")
implementation(Androidx.core)
implementation(compose.runtime)
implementation(compose.material)
implementation(compose.foundation)
implementation(compose.materialIconsExtended)
implementation(Decompose.extensionsCompose)
}
}
named("desktopMain") {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.desktop.common)
implementation(compose.materialIconsExtended)
implementation(Decompose.extensionsCompose)
}
}
named("jsMain") {

View File

@ -16,6 +16,7 @@ import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import com.shabinder.common.database.R
import com.shabinder.common.database.appContext
import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO
import kotlinx.coroutines.withContext
@ -103,3 +104,15 @@ actual fun GithubLogo() = vectorResource(R.drawable.ic_github)
@Composable
fun vectorResource(@DrawableRes id: Int) = ImageVector.Companion.vectorResource(id)
@Composable
actual fun Toast(
text: String,
visibility: MutableState<Boolean>,
duration: ToastDuration
){
//We Have Android's Implementation of Toast so its just Empty
}
actual fun showPopUpMessage(text: String){
android.widget.Toast.makeText(appContext,text, android.widget.Toast.LENGTH_SHORT).show()
}

View File

@ -85,7 +85,6 @@ fun MainScreen(modifier: Modifier = Modifier, topPadding: Dp = 0.dp,statusBarHei
Spacer(Modifier.padding(top = topPadding))
Children(
routerState = component.routerState,
//TODO animation = crossfade()
) { child, _ ->
when (child) {
is Child.Main -> SpotiFlyerMainContent(component = child.component)

View File

@ -1,4 +1,4 @@
package com.shabinder.common.di
package com.shabinder.common.uikit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
@ -8,6 +8,8 @@ enum class ToastDuration(val value: Int) {
Short(1000), Long(3000)
}
expect fun showPopUpMessage(text: String)
@Composable
expect fun Toast(
text: String,

View File

@ -1,4 +1,4 @@
package com.shabinder.common.di
package com.shabinder.common.uikit
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@ -15,13 +15,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
actual val dispatcherIO = Dispatchers.IO
private val message: MutableState<String> = mutableStateOf("")
private val state: MutableState<Boolean> = mutableStateOf(false)

View File

@ -1,5 +1,5 @@
plugins {
id("multiplatform-compose-setup")
id("multiplatform-setup")
id("android-setup")
id("kotlin-parcelize")
kotlin("plugin.serialization")
@ -10,7 +10,7 @@ kotlin {
commonMain {
dependencies {
api("dev.icerock.moko:parcelize:0.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
}
}
}

View File

@ -10,15 +10,14 @@ kotlin {
sourceSets {
commonMain {
dependencies {
implementation(compose.materialIconsExtended)
api(project(":common:data-models"))
api(project(":common:database"))
implementation(project(":common:data-models"))
implementation(project(":common:database"))
implementation(project(":fuzzywuzzy:app"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1")
implementation(Ktor.clientCore)
implementation(Ktor.clientCio)
//implementation(Ktor.clientCio)
implementation(Ktor.clientSerialization)
implementation(Ktor.clientLogging)
implementation(Ktor.clientJson)
@ -34,6 +33,7 @@ kotlin {
}
androidMain {
dependencies{
implementation(compose.materialIconsExtended)
implementation(Ktor.clientAndroid)
implementation(Extras.Android.fetch)
implementation(Koin.android)
@ -43,12 +43,14 @@ kotlin {
}
desktopMain {
dependencies{
implementation(compose.materialIconsExtended)
implementation(Ktor.clientApache)
implementation(Ktor.slf4j)
}
}
jsMain {
dependencies {
implementation(Ktor.clientJs)
implementation(project(":common:data-models"))
}
}

View File

@ -34,21 +34,9 @@ actual fun openPlatform(packageID:String, platformLink:String){
}
actual val dispatcherIO = Dispatchers.IO
@Composable
actual fun Toast(
text: String,
visibility: MutableState<Boolean>,
duration: ToastDuration
){
//We Have Android's Implementation of Toast so its just Empty
}
actual val isInternetAvailable:Boolean
get() = internetAvailability.value ?: true
actual fun showPopUpMessage(text: String){
android.widget.Toast.makeText(appContext,text, android.widget.Toast.LENGTH_SHORT).show()
}
actual fun shareApp(){
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND

View File

@ -11,10 +11,6 @@ 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.request.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration

View File

@ -9,8 +9,6 @@ expect fun shareApp()
expect fun giveDonation()
expect fun showPopUpMessage(text: String)
expect val dispatcherIO: CoroutineDispatcher
expect val isInternetAvailable:Boolean

View File

@ -51,7 +51,7 @@ class SpotifyProvider(
override suspend fun authenticateSpotify(): HttpClient?{
val token = tokenStore.getToken()
return if(token == null) {
showPopUpMessage("Please Check your Network Connection")
logger.d{ "Please Check your Network Connection" }
null
}
else{

View File

@ -12,6 +12,7 @@ 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.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
@ -22,6 +23,8 @@ actual fun openPlatform(packageID:String, platformLink:String){
//TODO
}
actual val dispatcherIO = Dispatchers.IO
actual fun shareApp(){
//TODO
}

View File

@ -19,7 +19,6 @@ actual fun giveDonation(){
}
actual fun queryActiveTracks(){}
actual fun showPopUpMessage(text:String){}
actual val dispatcherIO: CoroutineDispatcher = Dispatchers.Default
@ -40,6 +39,7 @@ private suspend fun isInternetAvailable(): Boolean {
actual val isInternetAvailable:Boolean
get(){
return true
var result = false
val job = GlobalScope.launch { result = isInternetAvailable() }
while(job.isActive){}
@ -52,11 +52,11 @@ actual suspend fun downloadTracks(
list: List<TrackDetails>,
getYTIDBestMatch:suspend (String, TrackDetails)->String?,
saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit
){
){/*
list.forEach {
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
} else {
}
}
}*/
}

View File

@ -0,0 +1,17 @@
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.*
actual class YoutubeProvider actual constructor(
httpClient: HttpClient,
database: Database,
logger: Kermit,
dir: Dir
) {
actual suspend fun query(fullLink: String): PlatformQueryResult? {
return null // TODO
}
}

View File

@ -15,7 +15,6 @@ kotlin {
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
}
}
}

View File

@ -48,6 +48,7 @@ interface SpotiFlyerList {
val dir: Dir
val link: String
val listOutput: Consumer<Output>
val showPopUpMessage:(String)->Unit
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
}
sealed class Output {

View File

@ -1,6 +1,5 @@
package com.shabinder.common.list.integration
import androidx.compose.ui.graphics.ImageBitmap
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.di.Picture
@ -24,8 +23,9 @@ internal class SpotiFlyerListImpl(
dir = this.dir,
storeFactory = storeFactory,
fetchQuery = fetchQuery,
downloadProgressFlow = downloadProgressFlow,
link = link,
downloadProgressFlow = downloadProgressFlow
showPopUpMessage = showPopUpMessage
).provide()
}

View File

@ -17,6 +17,7 @@ internal class SpotiFlyerListStoreProvider(
private val storeFactory: StoreFactory,
private val fetchQuery: FetchPlatformQueryResult,
private val link: String,
private val showPopUpMessage: (String) -> Unit,
private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
) {
val logger = getLogger()

View File

@ -10,16 +10,13 @@ kotlin {
sourceSets {
commonMain {
dependencies {
implementation(compose.materialIconsExtended)
implementation(project(":common:dependency-injection"))
//implementation("com.alialbaali.kamel:kamel-image:0.1.0")
implementation(project(":common:data-models"))
implementation(project(":common:database"))
implementation(SqlDelight.coroutineExtensions)
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
}
}
}

View File

@ -1,6 +1,5 @@
package com.shabinder.common.main
import androidx.compose.ui.graphics.ImageBitmap
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir
@ -39,8 +38,9 @@ interface SpotiFlyerMain {
interface Dependencies {
val mainOutput: Consumer<Output>
val storeFactory: StoreFactory
val database: Database
val database: Database?
val dir: Dir
val showPopUpMessage:(String)->Unit
}
sealed class Output {
data class Search(val link: String) : Output()

View File

@ -1,11 +1,9 @@
package com.shabinder.common.main.integration
import androidx.compose.ui.graphics.ImageBitmap
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.di.Picture
import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.di.showPopUpMessage
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.*
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
@ -22,7 +20,8 @@ internal class SpotiFlyerMainImpl(
instanceKeeper.getStore {
SpotiFlyerMainStoreProvider(
storeFactory = storeFactory,
database = database
database = database,
showPopUpMessage = showPopUpMessage
).provide()
}

View File

@ -22,7 +22,8 @@ import kotlinx.coroutines.flow.map
internal class SpotiFlyerMainStoreProvider(
private val storeFactory: StoreFactory,
database: Database
private val showPopUpMessage: (String)->Unit,
private val database: Database?
) {
fun provide(): SpotiFlyerMainStore =
@ -34,12 +35,12 @@ internal class SpotiFlyerMainStoreProvider(
reducer = ReducerImpl
) {}
val updates: Flow<List<DownloadRecord>> =
database.downloadRecordDatabaseQueries
.selectAll()
.asFlow()
.mapToList(Dispatchers.Default)
.map {
val updates: Flow<List<DownloadRecord>>? =
database?.downloadRecordDatabaseQueries
?.selectAll()
?.asFlow()
?.mapToList(Dispatchers.Default)
?.map {
it.map { record ->
record.run{
DownloadRecord(id, type, name, link, coverUrl, totalFiles)
@ -56,7 +57,7 @@ internal class SpotiFlyerMainStoreProvider(
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override suspend fun executeAction(action: Unit, getState: () -> State) {
updates.collect {
updates?.collect {
dispatch(Result.ItemsLoaded(it))
}
}

View File

@ -10,7 +10,6 @@ kotlin {
sourceSets {
commonMain {
dependencies {
implementation(compose.materialIconsExtended)
implementation(project(":common:dependency-injection"))
implementation(project(":common:data-models"))
implementation(project(":common:database"))
@ -20,7 +19,6 @@ kotlin {
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
}
}
}

View File

@ -28,9 +28,10 @@ interface SpotiFlyerRoot {
interface Dependencies {
val storeFactory: StoreFactory
val database: Database
val database: Database?
val fetchPlatformQueryResult: FetchPlatformQueryResult
val directories: Dir
val showPopUpMessage:(String)->Unit
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
}
}

View File

@ -17,8 +17,7 @@ import com.shabinder.common.di.initKoin
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.uikit.*
import com.shabinder.database.Database
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
private val koin = initKoin(enableNetworkLogs = true).koin
@ -49,9 +48,10 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
componentContext = componentContext,
dependencies = object : SpotiFlyerRoot.Dependencies {
override val storeFactory = DefaultStoreFactory
override val database: Database = koin.get()
override val database: Database? = koin.get()
override val fetchPlatformQueryResult: FetchPlatformQueryResult = koin.get()
override val directories: Dir = koin.get()
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
override val downloadProgressReport = DownloadProgressFlow
}
)

View File

@ -14,7 +14,8 @@ final pathSeparator = System.properties["path.separator"]
kotlin {
jvm()
/*js() {
js() {
browser()
[compileKotlinJs, compileTestKotlinJs].each { configuration ->
configuration.kotlinOptions {
moduleKind = 'umd'
@ -22,7 +23,7 @@ kotlin {
metaInfo = true
}
}
}*/
}
/*
wasm32("wasm")
@ -63,7 +64,7 @@ kotlin {
implementation 'junit:junit:4.12'
}
}
/*jsMain {
jsMain {
kotlin.srcDir('src/jsMain/kotlin')
dependencies {
implementation kotlin("stdlib-js")
@ -77,7 +78,7 @@ kotlin {
kotlinOptions.moduleKind = "umd"
}
}
jsTest {
/*jsTest {
dependencies {
implementation kotlin("test-js")
implementation kotlin("stdlib-js")

View File

@ -14,6 +14,16 @@ repositories {
dependencies {
implementation(kotlin("stdlib-js"))
implementation(Decompose.decompose)
implementation(Koin.core)
implementation(MVIKotlin.mvikotlin)
implementation(MVIKotlin.mvikotlinMain)
implementation(project(":common:root"))
implementation(project(":common:main"))
implementation(project(":common:list"))
implementation(project(":common:database"))
implementation(project(":common:data-models"))
implementation(project(":common:dependency-injection"))
implementation("org.jetbrains:kotlin-react:17.0.1-pre.148-kotlin-1.4.30")
implementation("org.jetbrains:kotlin-react-dom:17.0.1-pre.148-kotlin-1.4.30")
implementation("org.jetbrains:kotlin-styled:1.0.0-pre.115-kotlin-1.4.10")

View File

@ -0,0 +1,58 @@
import co.touchlab.kermit.Kermit
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.decompose.lifecycle.LifecycleRegistry
import com.arkivanov.decompose.lifecycle.destroy
import com.arkivanov.decompose.lifecycle.resume
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.database.Database
import extras.renderableChild
import kotlinx.coroutines.flow.MutableSharedFlow
import react.*
import root.RootR
external interface AppProps : RProps {
var dependencies: AppDependencies
}
fun RBuilder.app(attrs: AppProps.() -> Unit): ReactElement {
return child(App::class){
this.attrs(attrs)
}
}
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{
override val storeFactory: StoreFactory = DefaultStoreFactory
override val database: Database? = null
override val fetchPlatformQueryResult = dependencies.fetchPlatformQueryResult
override val directories = dependencies.directories
override val showPopUpMessage: (String) -> Unit = {}//TODO
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
= MutableSharedFlow(1)
}
)
override fun componentDidMount() {
lifecycle.resume()
}
override fun componentWillUnmount() {
lifecycle.destroy()
}
override fun RBuilder.render() {
renderableChild(RootR::class, root)
}
}

View File

@ -1,16 +1,34 @@
import home.homeScreen
import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.initKoin
import react.dom.render
import kotlinx.browser.document
import kotlinx.browser.window
import navbar.navBar
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
fun main() {
window.onload = {
render(document.getElementById("root")) {
navBar {}
homeScreen {
link = ""
app {
dependencies = AppDependencies
}
}
}
}
object AppDependencies : KoinComponent {
val logger: Kermit
val directories: Dir
val fetchPlatformQueryResult: FetchPlatformQueryResult
init {
initKoin()
directories = get()
logger = get()
fetchPlatformQueryResult = get()
}
}

View File

@ -0,0 +1,27 @@
package extras
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import react.RState
import react.setState
abstract class RenderableComponent<
T : Any,
S : RState
>(
props: Props<T>,
initialState: S
) : RenderableRootComponent<T, S>(props,initialState) {
protected abstract val stateFlow: Flow<S>
override fun componentDidMount() {
super.componentDidMount()
scope.launch {
stateFlow.collectLatest {
setState { state = it }
}
}
}
}

View File

@ -0,0 +1,63 @@
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.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
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 lateinit var scope: CoroutineScope
init {
state = initialState
}
override fun componentDidMount() {
subscriptions.forEach { subscribe(it) }
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

@ -0,0 +1,16 @@
package extras
import kotlinext.js.Object
import kotlinext.js.jsObject
var uniqueId: Long = 0L
internal fun Any.uniqueId(): Long {
var id: dynamic = asDynamic().__unique_id
if (id == undefined) {
id = ++uniqueId
Object.defineProperty<Any, Long>(this, "__unique_id", jsObject { value = id })
}
return id
}

View File

@ -0,0 +1,11 @@
package extras
import react.RBuilder
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
}
}

View File

@ -1,24 +1,22 @@
package home
import com.shabinder.common.main.SpotiFlyerMain
import extras.RenderableComponent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.css.*
import react.*
import styled.css
import styled.styledDiv
class HomeScreen(
props: Props<SpotiFlyerMain>,
override val stateFlow: Flow<State> = props.model.models.map { State(it) }
) : RenderableComponent<SpotiFlyerMain, HomeScreen.State>(
props,
initialState = State(data = SpotiFlyerMain.State())
) {
data class HomeScreenState(var link:String): RState
external interface HomeScreenProps : RProps {
var link: String
}
fun RBuilder.homeScreen(attrs:HomeScreenProps.() -> Unit): ReactElement {
return child(HomeScreen::class){
this.attrs(attrs)
}
}
class HomeScreen(props:HomeScreenProps):RComponent<HomeScreenProps,HomeScreenState>(props) {
override fun RBuilder.render() {
styledDiv{
css {
@ -28,24 +26,34 @@ class HomeScreen(props:HomeScreenProps):RComponent<HomeScreenProps,HomeScreenSta
justifyContent = JustifyContent.center
alignItems = Align.center
}
message {}
searchBar {
link = props.link
Message {
text = "Your Gateway to Nirvana, for FREE!"
}
iconList {
iconsAndPlatforms = iconList
SearchBar {
link = state.data.link
search = model::onLinkSearch
}
IconList {
iconsAndPlatforms = platformIconList
isBadge = false
}
}
iconList{
IconList {
iconsAndPlatforms = badges
isBadge = true
}
}
class State(
var data: SpotiFlyerMain.State
):RState
}
private val iconList = mapOf(
private val platformIconList = mapOf(
"spotify.svg" to "https://open.spotify.com/",
"gaana.svg" to "https://www.gaana.com/",
"youtube.svg" to "https://www.youtube.com/",

View File

@ -12,14 +12,16 @@ external interface IconListProps : RProps {
var isBadge:Boolean
}
fun RBuilder.iconList(attrs:IconListProps.() -> Unit): ReactElement {
return child(IconList::class){
this.attrs(attrs)
@Suppress("FunctionName")
fun RBuilder.IconList(handler:IconListProps.() -> Unit): ReactElement {
return child(iconList){
attrs {
handler()
}
}
}
class IconList(props: IconListProps):RComponent<IconListProps,RState>(props) {
override fun RBuilder.render() {
private val iconList = functionalComponent<IconListProps>("IconList") { props ->
styledDiv {
css {
+Styles.makeRow
@ -48,4 +50,3 @@ class IconList(props: IconListProps):RComponent<IconListProps,RState>(props) {
}
}
}
}

View File

@ -6,24 +6,23 @@ import styled.css
import styled.styledDiv
import styled.styledH1
data class MessageState(var link:String): RState
external interface MessageProps : RProps {
var text: String
}
fun RBuilder.message(attrs:MessageProps.() -> Unit): ReactElement {
return child(Message::class){
this.attrs(attrs)
@Suppress("FunctionName")
fun RBuilder.Message(handler:MessageProps.() -> Unit): ReactElement {
return child(message){
attrs {
handler()
}
}
}
class Message(props:MessageProps): RComponent<MessageProps,MessageState>(props) {
override fun RBuilder.render() {
private val message = functionalComponent<MessageProps>("Message") { props->
styledDiv {
styledH1 {
+"Your Gateway to Nirvana, for FREE!"
+ props.text
css {
classes = mutableListOf("headingTitle")
fontSize = 3.2.rem
@ -31,4 +30,3 @@ class Message(props:MessageProps): RComponent<MessageProps,MessageState>(props)
}
}
}
}

View File

@ -7,24 +7,23 @@ import org.w3c.dom.HTMLInputElement
import react.*
import styled.*
data class SearchbarState(var link:String):RState
external interface SearchbarProps : RProps {
var link: String
var search:(String)->Unit
}
fun RBuilder.searchBar(attrs:SearchbarProps.() -> Unit): ReactElement {
return child(Searchbar::class){
this.attrs(attrs)
@Suppress("FunctionName")
fun RBuilder.SearchBar(handler:SearchbarProps.() -> Unit) = child(searchbar){
attrs {
handler()
}
}
class Searchbar(props: SearchbarProps): RComponent<SearchbarProps,SearchbarState>(props) {
init {
state = SearchbarState(props.link)
}
override fun RBuilder.render() {
val searchbar = functionalComponent<SearchbarProps>("SearchBar"){ props ->
val (link,setLink) = useState(props.link)
styledDiv{
css {
classes = mutableListOf("searchBox")
@ -34,11 +33,9 @@ class Searchbar(props: SearchbarProps): RComponent<SearchbarProps,SearchbarState
placeholder = "Search"
onChangeFunction = {
val target = it.target as HTMLInputElement
setState{
link = target.value
setLink(target.value)
}
}
value = state.link
value = link
}
css {
classes = mutableListOf("searchInput")
@ -47,7 +44,7 @@ class Searchbar(props: SearchbarProps): RComponent<SearchbarProps,SearchbarState
styledButton {
attrs {
onClickFunction = {
props.search(link)
}
}
css {
@ -61,5 +58,3 @@ class Searchbar(props: SearchbarProps): RComponent<SearchbarProps,SearchbarState
}
}
}
}

View File

@ -0,0 +1,45 @@
package list
import kotlinx.css.*
import kotlinx.html.id
import react.RProps
import react.rFunction
import react.useState
import styled.css
import styled.styledDiv
import styled.styledH1
import styled.styledImg
external interface CoverImageProps : RProps {
var coverImageURL: String
var coverName: String
}
val CoverImage = rFunction<CoverImageProps>("CoverImage"){ props ->
val (coverURL,setCoverURL) = useState(props.coverImageURL)
val (coverName,setCoverName) = useState(props.coverName)
styledDiv {
styledImg(src=coverURL){
css {
height = 300.px
width = 300.px
}
}
styledH1 {
+coverName
css {
textAlign = TextAlign.center
}
}
attrs {
id = "cover-image"
}
css {
display = Display.flex
alignItems = Align.center
flexDirection = FlexDirection.column
}
}
}

View File

@ -0,0 +1,39 @@
package list
import com.shabinder.common.list.SpotiFlyerList
import extras.RenderableComponent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.css.*
import kotlinx.html.id
import react.RBuilder
import react.RState
import styled.css
import styled.styledDiv
class ListScreen(
props: Props<SpotiFlyerList>,
override val stateFlow: Flow<State> = props.model.models.map { State(it) }
) : RenderableComponent<SpotiFlyerList, ListScreen.State>(props,initialState = State(SpotiFlyerList.State())) {
override fun RBuilder.render() {
styledDiv {
attrs {
id = "list-screen-div"
}
css {
classes = mutableListOf("list-screen")
display = Display.flex
flexDirection = FlexDirection.column
flexGrow = 1.0
justifyContent = JustifyContent.center
alignItems = Align.center
backgroundColor = Color.white
}
}
}
class State(
var data: SpotiFlyerList.State
):RState
}

View File

@ -0,0 +1,13 @@
package list
import react.RProps
import react.rFunction
external interface TrackItemProps : RProps {
var coverImageURL: String
var coverName: String
}
val trackItem = rFunction<TrackItemProps>("Track-Item"){
}

View File

@ -0,0 +1,34 @@
package root
import com.arkivanov.decompose.RouterState
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.*
import extras.RenderableRootComponent
import extras.renderableChild
import home.HomeScreen
import list.ListScreen
import react.RBuilder
import react.RState
class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRoot, RootR.State>(
props = props,
initialState = State(routerState = props.model.routerState.value)
) {
private val component: Child
get() = model.routerState.value.activeChild.component
override fun RBuilder.render() {
when(component){
is Child.Main -> renderableChild(HomeScreen::class, (component as Child.Main).component)
is Child.List -> renderableChild(ListScreen::class, (component as Child.List).component)
}
}
init {
model.routerState.bindToState { routerState = it }
}
class State(
var routerState: RouterState<*, Child>,
) : RState
}