mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 12:47:54 +01:00
web-app and business logic integration (WIP) & gradle scripts refactor.
This commit is contained in:
parent
9920885b3b
commit
42a24ce412
@ -72,7 +72,7 @@ dependencies {
|
||||
implementation(project(":common:data-models"))
|
||||
|
||||
implementation(Koin.android)
|
||||
implementation(Koin.androidViewModel)
|
||||
implementation(Koin.compose)
|
||||
|
||||
//DECOMPOSE
|
||||
implementation(Decompose.decompose)
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -13,6 +13,7 @@ kotlin {
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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") {
|
||||
|
@ -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
|
||||
@ -102,4 +103,16 @@ actual fun YoutubeMusicLogo() = vectorResource(R.drawable.ic_youtube_music_logo)
|
||||
actual fun GithubLogo() = vectorResource(R.drawable.ic_github)
|
||||
|
||||
@Composable
|
||||
fun vectorResource(@DrawableRes id: Int) = ImageVector.Companion.vectorResource(id)
|
||||
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()
|
||||
}
|
@ -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)
|
||||
|
@ -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,
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -9,8 +9,6 @@ expect fun shareApp()
|
||||
|
||||
expect fun giveDonation()
|
||||
|
||||
expect fun showPopUpMessage(text: String)
|
||||
|
||||
expect val dispatcherIO: CoroutineDispatcher
|
||||
|
||||
expect val isInternetAvailable:Boolean
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ kotlin {
|
||||
implementation(MVIKotlin.coroutines)
|
||||
implementation(MVIKotlin.mvikotlin)
|
||||
implementation(Decompose.decompose)
|
||||
implementation(Decompose.extensionsCompose)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>>
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
)
|
@ -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")
|
||||
|
@ -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")
|
||||
|
58
web-app/src/main/kotlin/App.kt
Normal file
58
web-app/src/main/kotlin/App.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
27
web-app/src/main/kotlin/extras/RenderableComponent.kt
Normal file
27
web-app/src/main/kotlin/extras/RenderableComponent.kt
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
web-app/src/main/kotlin/extras/RenderableRootComponent.kt
Normal file
63
web-app/src/main/kotlin/extras/RenderableRootComponent.kt
Normal 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>
|
||||
)
|
||||
}
|
16
web-app/src/main/kotlin/extras/UniqueId.kt
Normal file
16
web-app/src/main/kotlin/extras/UniqueId.kt
Normal 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
|
||||
}
|
11
web-app/src/main/kotlin/extras/Utils.kt
Normal file
11
web-app/src/main/kotlin/extras/Utils.kt
Normal 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
|
||||
}
|
||||
}
|
@ -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/",
|
||||
|
@ -12,36 +12,37 @@ 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() {
|
||||
styledDiv {
|
||||
css {
|
||||
+Styles.makeRow
|
||||
margin(18.px)
|
||||
if(props.isBadge) {
|
||||
alignItems = Align.end
|
||||
}
|
||||
private val iconList = functionalComponent<IconListProps>("IconList") { props ->
|
||||
styledDiv {
|
||||
css {
|
||||
+Styles.makeRow
|
||||
margin(18.px)
|
||||
if(props.isBadge) {
|
||||
alignItems = Align.end
|
||||
}
|
||||
for((icon,platformLink) in props.iconsAndPlatforms){
|
||||
styledA(href = platformLink){
|
||||
styledImg {
|
||||
attrs {
|
||||
src = icon
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("glow-button")
|
||||
margin(8.px)
|
||||
if (!props.isBadge) {
|
||||
height = 42.px
|
||||
width = 42.px
|
||||
borderRadius = 50.px
|
||||
}
|
||||
}
|
||||
for((icon,platformLink) in props.iconsAndPlatforms){
|
||||
styledA(href = platformLink){
|
||||
styledImg {
|
||||
attrs {
|
||||
src = icon
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("glow-button")
|
||||
margin(8.px)
|
||||
if (!props.isBadge) {
|
||||
height = 42.px
|
||||
width = 42.px
|
||||
borderRadius = 50.px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,28 +6,26 @@ 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() {
|
||||
styledDiv {
|
||||
styledH1 {
|
||||
+"Your Gateway to Nirvana, for FREE!"
|
||||
css {
|
||||
classes = mutableListOf("headingTitle")
|
||||
fontSize = 3.2.rem
|
||||
}
|
||||
private val message = functionalComponent<MessageProps>("Message") { props->
|
||||
styledDiv {
|
||||
styledH1 {
|
||||
+ props.text
|
||||
css {
|
||||
classes = mutableListOf("headingTitle")
|
||||
fontSize = 3.2.rem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,59 +7,54 @@ 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() {
|
||||
styledDiv{
|
||||
|
||||
val searchbar = functionalComponent<SearchbarProps>("SearchBar"){ props ->
|
||||
val (link,setLink) = useState(props.link)
|
||||
|
||||
styledDiv{
|
||||
css {
|
||||
classes = mutableListOf("searchBox")
|
||||
}
|
||||
styledInput(type = InputType.url){
|
||||
attrs {
|
||||
placeholder = "Search"
|
||||
onChangeFunction = {
|
||||
val target = it.target as HTMLInputElement
|
||||
setLink(target.value)
|
||||
}
|
||||
value = link
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("searchBox")
|
||||
classes = mutableListOf("searchInput")
|
||||
}
|
||||
styledInput(type = InputType.url){
|
||||
attrs {
|
||||
placeholder = "Search"
|
||||
onChangeFunction = {
|
||||
val target = it.target as HTMLInputElement
|
||||
setState{
|
||||
link = target.value
|
||||
}
|
||||
}
|
||||
value = state.link
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("searchInput")
|
||||
}
|
||||
styledButton {
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
props.search(link)
|
||||
}
|
||||
}
|
||||
styledButton {
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
|
||||
}
|
||||
}
|
||||
css {
|
||||
classes = mutableListOf("searchButton")
|
||||
}
|
||||
styledImg(src = "search.svg") {
|
||||
css {
|
||||
classes = mutableListOf("searchButton")
|
||||
}
|
||||
styledImg(src = "search.svg") {
|
||||
css {
|
||||
classes = mutableListOf("search-icon")
|
||||
}
|
||||
classes = mutableListOf("search-icon")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
45
web-app/src/main/kotlin/list/CoverImage.kt
Normal file
45
web-app/src/main/kotlin/list/CoverImage.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
39
web-app/src/main/kotlin/list/ListScreen.kt
Normal file
39
web-app/src/main/kotlin/list/ListScreen.kt
Normal 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
|
||||
}
|
13
web-app/src/main/kotlin/list/TrackItem.kt
Normal file
13
web-app/src/main/kotlin/list/TrackItem.kt
Normal 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"){
|
||||
|
||||
}
|
34
web-app/src/main/kotlin/root/RootR.kt
Normal file
34
web-app/src/main/kotlin/root/RootR.kt
Normal 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
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user