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(project(":common:data-models"))
implementation(Koin.android) implementation(Koin.android)
implementation(Koin.androidViewModel) implementation(Koin.compose)
//DECOMPOSE //DECOMPOSE
implementation(Decompose.decompose) implementation(Decompose.decompose)

View File

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

View File

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

View File

@ -13,6 +13,7 @@ kotlin {
implementation(compose.runtime) implementation(compose.runtime)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material) 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.kotlin
import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.sourceSets import gradle.kotlin.dsl.accessors._2e23d8fadf0ed92ae13e19db3d83f86d.sourceSets
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import org.jetbrains.compose.compose
plugins { plugins {
id("com.android.library") // id("com.android.library")
id("android-setup")
id("kotlin-multiplatform") id("kotlin-multiplatform")
id("org.jetbrains.compose") id("org.jetbrains.compose")
} }
@ -19,9 +21,7 @@ kotlin {
sourceSets { sourceSets {
named("commonMain") { named("commonMain") {
dependencies { dependencies {
implementation(compose.runtime) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
implementation(compose.foundation)
implementation(compose.material)
} }
} }
@ -29,12 +29,22 @@ kotlin {
dependencies { dependencies {
implementation("androidx.appcompat:appcompat:1.2.0") implementation("androidx.appcompat:appcompat:1.2.0")
implementation(Androidx.core) implementation(Androidx.core)
implementation(compose.runtime)
implementation(compose.material)
implementation(compose.foundation)
implementation(compose.materialIconsExtended)
implementation(Decompose.extensionsCompose)
} }
} }
named("desktopMain") { named("desktopMain") {
dependencies { dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.desktop.common) implementation(compose.desktop.common)
implementation(compose.materialIconsExtended)
implementation(Decompose.extensionsCompose)
} }
} }
named("jsMain") { 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.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import com.shabinder.common.database.R import com.shabinder.common.database.R
import com.shabinder.common.database.appContext
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO import com.shabinder.common.di.dispatcherIO
import kotlinx.coroutines.withContext 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) actual fun GithubLogo() = vectorResource(R.drawable.ic_github)
@Composable @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()
}

View File

@ -85,7 +85,6 @@ fun MainScreen(modifier: Modifier = Modifier, topPadding: Dp = 0.dp,statusBarHei
Spacer(Modifier.padding(top = topPadding)) Spacer(Modifier.padding(top = topPadding))
Children( Children(
routerState = component.routerState, routerState = component.routerState,
//TODO animation = crossfade()
) { child, _ -> ) { child, _ ->
when (child) { when (child) {
is Child.Main -> SpotiFlyerMainContent(component = child.component) 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.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@ -8,6 +8,8 @@ enum class ToastDuration(val value: Int) {
Short(1000), Long(3000) Short(1000), Long(3000)
} }
expect fun showPopUpMessage(text: String)
@Composable @Composable
expect fun Toast( expect fun Toast(
text: String, 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.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -15,13 +15,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
actual val dispatcherIO = Dispatchers.IO
private val message: MutableState<String> = mutableStateOf("") private val message: MutableState<String> = mutableStateOf("")
private val state: MutableState<Boolean> = mutableStateOf(false) private val state: MutableState<Boolean> = mutableStateOf(false)

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("multiplatform-compose-setup") id("multiplatform-setup")
id("android-setup") id("android-setup")
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") kotlin("plugin.serialization")
@ -10,7 +10,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api("dev.icerock.moko:parcelize:0.6.0") 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 { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
implementation(compose.materialIconsExtended) implementation(project(":common:data-models"))
api(project(":common:data-models")) implementation(project(":common:database"))
api(project(":common:database"))
implementation(project(":fuzzywuzzy:app")) implementation(project(":fuzzywuzzy:app"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") 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-serialization-json:1.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.1.1")
implementation(Ktor.clientCore) implementation(Ktor.clientCore)
implementation(Ktor.clientCio) //implementation(Ktor.clientCio)
implementation(Ktor.clientSerialization) implementation(Ktor.clientSerialization)
implementation(Ktor.clientLogging) implementation(Ktor.clientLogging)
implementation(Ktor.clientJson) implementation(Ktor.clientJson)
@ -34,6 +33,7 @@ kotlin {
} }
androidMain { androidMain {
dependencies{ dependencies{
implementation(compose.materialIconsExtended)
implementation(Ktor.clientAndroid) implementation(Ktor.clientAndroid)
implementation(Extras.Android.fetch) implementation(Extras.Android.fetch)
implementation(Koin.android) implementation(Koin.android)
@ -43,12 +43,14 @@ kotlin {
} }
desktopMain { desktopMain {
dependencies{ dependencies{
implementation(compose.materialIconsExtended)
implementation(Ktor.clientApache) implementation(Ktor.clientApache)
implementation(Ktor.slf4j) implementation(Ktor.slf4j)
} }
} }
jsMain { jsMain {
dependencies { dependencies {
implementation(Ktor.clientJs)
implementation(project(":common:data-models")) implementation(project(":common:data-models"))
} }
} }

View File

@ -34,21 +34,9 @@ actual fun openPlatform(packageID:String, platformLink:String){
} }
actual val dispatcherIO = Dispatchers.IO 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 actual val isInternetAvailable:Boolean
get() = internetAvailability.value ?: true get() = internetAvailability.value ?: true
actual fun showPopUpMessage(text: String){
android.widget.Toast.makeText(appContext,text, android.widget.Toast.LENGTH_SHORT).show()
}
actual fun shareApp(){ actual fun shareApp(){
val sendIntent: Intent = Intent().apply { val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND 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.*
import io.ktor.client.features.json.serializer.* import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.* 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 kotlinx.serialization.json.Json
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.KoinAppDeclaration

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import io.ktor.client.request.* import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@ -22,6 +23,8 @@ actual fun openPlatform(packageID:String, platformLink:String){
//TODO //TODO
} }
actual val dispatcherIO = Dispatchers.IO
actual fun shareApp(){ actual fun shareApp(){
//TODO //TODO
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,16 @@ repositories {
dependencies { dependencies {
implementation(kotlin("stdlib-js")) 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: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-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") 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 react.dom.render
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
import navbar.navBar import navbar.navBar
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
fun main() { fun main() {
window.onload = { window.onload = {
render(document.getElementById("root")) { render(document.getElementById("root")) {
navBar {} navBar {}
homeScreen { app {
link = "" 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 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 kotlinx.css.*
import react.* import react.*
import styled.css import styled.css
import styled.styledDiv 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() { override fun RBuilder.render() {
styledDiv{ styledDiv{
css { css {
@ -28,24 +26,34 @@ class HomeScreen(props:HomeScreenProps):RComponent<HomeScreenProps,HomeScreenSta
justifyContent = JustifyContent.center justifyContent = JustifyContent.center
alignItems = Align.center alignItems = Align.center
} }
message {}
searchBar { Message {
link = props.link text = "Your Gateway to Nirvana, for FREE!"
} }
iconList {
iconsAndPlatforms = iconList SearchBar {
link = state.data.link
search = model::onLinkSearch
}
IconList {
iconsAndPlatforms = platformIconList
isBadge = false isBadge = false
} }
} }
iconList{ IconList {
iconsAndPlatforms = badges iconsAndPlatforms = badges
isBadge = true isBadge = true
} }
} }
class State(
var data: SpotiFlyerMain.State
):RState
} }
private val iconList = mapOf( private val platformIconList = mapOf(
"spotify.svg" to "https://open.spotify.com/", "spotify.svg" to "https://open.spotify.com/",
"gaana.svg" to "https://www.gaana.com/", "gaana.svg" to "https://www.gaana.com/",
"youtube.svg" to "https://www.youtube.com/", "youtube.svg" to "https://www.youtube.com/",

View File

@ -12,36 +12,37 @@ external interface IconListProps : RProps {
var isBadge:Boolean var isBadge:Boolean
} }
fun RBuilder.iconList(attrs:IconListProps.() -> Unit): ReactElement { @Suppress("FunctionName")
return child(IconList::class){ fun RBuilder.IconList(handler:IconListProps.() -> Unit): ReactElement {
this.attrs(attrs) return child(iconList){
attrs {
handler()
}
} }
} }
class IconList(props: IconListProps):RComponent<IconListProps,RState>(props) { private val iconList = functionalComponent<IconListProps>("IconList") { props ->
override fun RBuilder.render() { styledDiv {
styledDiv { css {
css { +Styles.makeRow
+Styles.makeRow margin(18.px)
margin(18.px) if(props.isBadge) {
if(props.isBadge) { alignItems = Align.end
alignItems = Align.end
}
} }
for((icon,platformLink) in props.iconsAndPlatforms){ }
styledA(href = platformLink){ for((icon,platformLink) in props.iconsAndPlatforms){
styledImg { styledA(href = platformLink){
attrs { styledImg {
src = icon attrs {
} src = icon
css { }
classes = mutableListOf("glow-button") css {
margin(8.px) classes = mutableListOf("glow-button")
if (!props.isBadge) { margin(8.px)
height = 42.px if (!props.isBadge) {
width = 42.px height = 42.px
borderRadius = 50.px width = 42.px
} borderRadius = 50.px
} }
} }
} }

View File

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

View File

@ -7,59 +7,54 @@ import org.w3c.dom.HTMLInputElement
import react.* import react.*
import styled.* import styled.*
data class SearchbarState(var link:String):RState
external interface SearchbarProps : RProps { external interface SearchbarProps : RProps {
var link: String var link: String
var search:(String)->Unit
} }
fun RBuilder.searchBar(attrs:SearchbarProps.() -> Unit): ReactElement { @Suppress("FunctionName")
return child(Searchbar::class){ fun RBuilder.SearchBar(handler:SearchbarProps.() -> Unit) = child(searchbar){
this.attrs(attrs) 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 { css {
classes = mutableListOf("searchBox") classes = mutableListOf("searchInput")
} }
styledInput(type = InputType.url){ }
attrs { styledButton {
placeholder = "Search" attrs {
onChangeFunction = { onClickFunction = {
val target = it.target as HTMLInputElement props.search(link)
setState{
link = target.value
}
}
value = state.link
}
css {
classes = mutableListOf("searchInput")
} }
} }
styledButton { css {
attrs { classes = mutableListOf("searchButton")
onClickFunction = { }
styledImg(src = "search.svg") {
}
}
css { css {
classes = mutableListOf("searchButton") classes = mutableListOf("search-icon")
}
styledImg(src = "search.svg") {
css {
classes = mutableListOf("search-icon")
}
} }
} }
} }
} }
}
}

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
}