diff --git a/README.md b/README.md index eb9835d..4008e9d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Minimal **Kotlin Multiplatform** project with SwiftUI, Jetpack Compose, Compose * watchOS (SwiftUI) ✅ DONE * macOS (SwiftUI) ✅ DONE * Web (ReactJS) ✅ DONE +* Web (Compose-Web) ⚠️ EXPERIMENTAL ### TODOs diff --git a/compose-web/build.gradle.kts b/compose-web/build.gradle.kts new file mode 100644 index 0000000..b712e63 --- /dev/null +++ b/compose-web/build.gradle.kts @@ -0,0 +1,39 @@ +import org.jetbrains.compose.compose +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension + +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version ("1.1.0") +} + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() +} + +kotlin { + js(IR) { + browser() + binaries.executable() + } + sourceSets { + val jsMain by getting { + dependencies { + implementation(compose.web.core) + implementation(compose.runtime) + implementation(project(":shared")) + } + } + } +} + +// a temporary workaround for a bug in jsRun invocation - see https://youtrack.jetbrains.com/issue/KT-48273 +afterEvaluate { + rootProject.extensions.configure { + nodeVersion = "16.0.0" + versions.webpackDevServer.version = "4.0.0" + versions.webpackCli.version = "4.9.0" + } +} + diff --git a/compose-web/src/jsMain/kotlin/TrendingReposListScreen.kt b/compose-web/src/jsMain/kotlin/TrendingReposListScreen.kt new file mode 100644 index 0000000..714f390 --- /dev/null +++ b/compose-web/src/jsMain/kotlin/TrendingReposListScreen.kt @@ -0,0 +1,107 @@ +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.baseio.kmm.features.trending.GithubTrendingDataModel +import org.jetbrains.compose.web.attributes.InputType +import org.jetbrains.compose.web.attributes.href +import org.jetbrains.compose.web.css.marginLeft +import org.jetbrains.compose.web.css.paddingBottom +import org.jetbrains.compose.web.css.paddingLeft +import org.jetbrains.compose.web.css.paddingTop +import org.jetbrains.compose.web.css.px +import org.jetbrains.compose.web.css.width +import org.jetbrains.compose.web.dom.A +import org.jetbrains.compose.web.dom.Button +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.H1 +import org.jetbrains.compose.web.dom.Img +import org.jetbrains.compose.web.dom.Input +import org.jetbrains.compose.web.dom.Progress +import org.jetbrains.compose.web.dom.Text + +@Composable +fun TrendingReposListScreen(trendingReposVM: TrendingReposVM) { + val uiState = trendingReposVM.uiState + var currentSearchText by remember { mutableStateOf("") } + + H1 { + Text("Trending Kotlin Repositories") + } + + Div { + Input(InputType.Text) { + onChange { newTextState -> + currentSearchText = newTextState.value + } + } + Button( + attrs = { + style { + marginLeft(8.px) + } + onClick { + trendingReposVM.trendingDataModel.filterRecords(search = currentSearchText) + } + } + ) { + Text("Search") + } + } + + when (uiState) { + is GithubTrendingDataModel.LoadingState -> Progress() + is GithubTrendingDataModel.ErrorState -> { + Text(uiState.throwable.message ?: "Unexpected error occurred!") + } + is GithubTrendingDataModel.SuccessState -> { + uiState.trendingList.forEach { repoItem -> + Div( + attrs = { + style { + paddingTop(16.px) + paddingLeft(16.px) + } + } + ) { + Img( + src = repoItem.avatar ?: "http://via.placeholder.com/640x360", + attrs = { + style { + width(100.px) + } + } + ) + } + Div( + attrs = { + style { + paddingLeft(16.px) + paddingTop(16.px) + } + } + ) { + Text("${repoItem.author}: ${repoItem.name}") + } + Div( + attrs = { + style { + paddingLeft(16.px) + paddingBottom(16.px) + } + } + ) { + A( + attrs = { + href(repoItem.url.orEmpty()) + } + ) { + Text(repoItem.url.orEmpty()) + } + } + } + } + else -> Unit + } +} \ No newline at end of file diff --git a/compose-web/src/jsMain/kotlin/TrendingReposVM.kt b/compose-web/src/jsMain/kotlin/TrendingReposVM.kt new file mode 100644 index 0000000..9be6487 --- /dev/null +++ b/compose-web/src/jsMain/kotlin/TrendingReposVM.kt @@ -0,0 +1,46 @@ +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import com.baseio.kmm.db.DriverFactory +import com.baseio.kmm.di.SharedComponent +import com.baseio.kmm.features.trending.GithubTrendingDataModel +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch + +val sharedComponent = SharedComponent() + +class TrendingReposVM { + + var uiState by mutableStateOf(GithubTrendingDataModel.EmptyState) + private set + + val trendingDataModel = GithubTrendingDataModel(onDataState = { stateNew -> + uiState = stateNew + }) + + init { + MainScope().launch { + setupDriver() + trendingDataModel.activate() + } + } +} + +suspend fun setupDriver() { + sharedComponent.provideGithubTrendingLocal().driver?.let {} ?: run { + setupDriverInternal() + } + +} + +private suspend fun setupDriverInternal() { + try { + val driver = DriverFactory().createDriverBlocking() + val trendingLocal = sharedComponent.provideGithubTrendingLocal() + trendingLocal.driver = driver + } catch (ex: Exception) { + console.log(ex.message) + console.log("Exception happened here.") + console.log(ex) + } +} diff --git a/compose-web/src/jsMain/kotlin/main.kt b/compose-web/src/jsMain/kotlin/main.kt new file mode 100644 index 0000000..c7b76b5 --- /dev/null +++ b/compose-web/src/jsMain/kotlin/main.kt @@ -0,0 +1,10 @@ +import com.baseio.kmm.di.initSqlDelightExperimentalDependencies +import org.jetbrains.compose.web.renderComposable + +// invoke using `./gradlew :compose-web:jsBrowserDevelopmentRun` +fun main() { + initSqlDelightExperimentalDependencies() + renderComposable(rootElementId = "root") { + TrendingReposListScreen(TrendingReposVM()) + } +} diff --git a/compose-web/src/jsMain/resources/index.html b/compose-web/src/jsMain/resources/index.html new file mode 100644 index 0000000..9a790b2 --- /dev/null +++ b/compose-web/src/jsMain/resources/index.html @@ -0,0 +1,11 @@ + + + + + PraxisKMM + + +
+ + + \ No newline at end of file diff --git a/compose-web/webpack.config.d/sqljs.js b/compose-web/webpack.config.d/sqljs.js new file mode 100644 index 0000000..3a4c95f --- /dev/null +++ b/compose-web/webpack.config.d/sqljs.js @@ -0,0 +1,18 @@ + +config.resolve.fallback = { + fs: false, + path: false, + crypto: false, +}; + +const CopyWebpackPlugin = require('copy-webpack-plugin'); +config.plugins.push( + new CopyWebpackPlugin({ + patterns: [ + { + from: '../../node_modules/sql.js/dist/sql-wasm.wasm', + to: '../../../compose-web/build/distributions' + } + ] + }) +); \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 97bddd2..7a9036f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,6 +2,7 @@ pluginManagement { repositories { google() gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") mavenCentral() } } @@ -12,4 +13,5 @@ include(":shared") include(":watchApp") include(":wearOS") include(":webApp") -include(":compose-desktop") \ No newline at end of file +include(":compose-desktop") +include(":compose-web") \ No newline at end of file