Skip to content

Commit

Permalink
Add web app for the TodoApp example (JetBrains#778)
Browse files Browse the repository at this point in the history
* Prepare the TodoApp example for adding the JavaScript app

* Add the JavaScript app for the TodoApp example

* TodoApp. Update Compose to 0.5.0-build225.
  • Loading branch information
arkivanov authored Jun 17, 2021
1 parent 971a2a4 commit 8f87cda
Show file tree
Hide file tree
Showing 38 changed files with 1,124 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponen
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.arkivanov.mvikotlin.timetravel.store.TimeTravelStoreFactory
import example.todo.common.database.DefaultTodoSharedDatabase
import example.todo.common.database.TodoDatabaseDriver
import example.todo.common.root.TodoRoot
import example.todo.common.root.integration.TodoRootComponent
import example.todo.common.ui.TodoRootContent
import example.todo.database.TodoDatabase

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -33,6 +33,6 @@ class MainActivity : AppCompatActivity() {
TodoRootComponent(
componentContext = componentContext,
storeFactory = LoggingStoreFactory(TimeTravelStoreFactory(DefaultStoreFactory)),
database = TodoDatabase(TodoDatabaseDriver(context = this))
database = DefaultTodoSharedDatabase(TodoDatabaseDriver(context = this))
)
}
6 changes: 4 additions & 2 deletions examples/todoapp/buildSrc/buildSrc/src/main/kotlin/Deps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ object Deps {
const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$VERSION"
const val testCommon = "org.jetbrains.kotlin:kotlin-test-common:$VERSION"
const val testJunit = "org.jetbrains.kotlin:kotlin-test-junit:$VERSION"
const val testJs = "org.jetbrains.kotlin:kotlin-test-js:$VERSION"
const val testAnnotationsCommon = "org.jetbrains.kotlin:kotlin-test-annotations-common:$VERSION"
}

object Compose {
// __LATEST_COMPOSE_RELEASE_VERSION__
private const val VERSION = "0.4.0"
private const val VERSION = "0.5.0-build225"
const val gradlePlugin = "org.jetbrains.compose:compose-gradle-plugin:$VERSION"
}
}
Expand All @@ -37,7 +38,7 @@ object Deps {

object ArkIvanov {
object MVIKotlin {
private const val VERSION = "2.0.2"
private const val VERSION = "2.0.3"
const val rx = "com.arkivanov.mvikotlin:rx:$VERSION"
const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION"
const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION"
Expand Down Expand Up @@ -71,6 +72,7 @@ object Deps {
const val androidDriver = "com.squareup.sqldelight:android-driver:$VERSION"
const val sqliteDriver = "com.squareup.sqldelight:sqlite-driver:$VERSION"
const val nativeDriver = "com.squareup.sqldelight:native-driver:$VERSION"
const val sqljsDriver = "com.squareup.sqldelight:sqljs-driver:$VERSION"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ kotlin {
android()
ios()

js(IR) {
browser()
}

sourceSets {
named("commonTest") {
dependencies {
Expand All @@ -26,6 +30,11 @@ kotlin {
implementation(Deps.JetBrains.Kotlin.testJunit)
}
}
named("jsTest") {
dependencies {
implementation(Deps.JetBrains.Kotlin.testJs)
}
}
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.onKeyEvent
Expand Down Expand Up @@ -130,6 +131,7 @@ private fun Item(
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun TodoInput(
text: String,
Expand Down
6 changes: 6 additions & 0 deletions examples/todoapp/common/database/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,11 @@ kotlin {
implementation(Deps.Squareup.SQLDelight.nativeDriver)
}
}

jsMain {
dependencies {
implementation(Deps.Squareup.SQLDelight.sqljsDriver)
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package example.todo.common.database

import com.badoo.reaktive.base.setCancellable
import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.maybe.Maybe
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.autoConnect
import com.badoo.reaktive.observable.firstOrError
import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.observable
import com.badoo.reaktive.observable.observeOn
import com.badoo.reaktive.observable.replay
import com.badoo.reaktive.scheduler.ioScheduler
import com.badoo.reaktive.single.Single
import com.badoo.reaktive.single.asCompletable
import com.badoo.reaktive.single.asObservable
import com.badoo.reaktive.single.doOnBeforeSuccess
import com.badoo.reaktive.single.flatMapObservable
import com.badoo.reaktive.single.map
import com.badoo.reaktive.single.mapNotNull
import com.badoo.reaktive.single.observeOn
import com.badoo.reaktive.single.singleOf
import com.squareup.sqldelight.Query
import com.squareup.sqldelight.db.SqlDriver
import example.todo.database.TodoDatabase

class DefaultTodoSharedDatabase(driver: Single<SqlDriver>) : TodoSharedDatabase {

constructor(driver: SqlDriver) : this(singleOf(driver))

private val queries: Single<TodoDatabaseQueries> =
driver
.map { TodoDatabase(it).todoDatabaseQueries }
.asObservable()
.replay()
.autoConnect()
.firstOrError()

override fun observeAll(): Observable<List<TodoItemEntity>> =
query(TodoDatabaseQueries::selectAll)
.observe { it.executeAsList() }

override fun select(id: Long): Maybe<TodoItemEntity> =
query { it.select(id = id) }
.mapNotNull { it.executeAsOneOrNull() }

override fun add(text: String): Completable =
execute { it.add(text = text) }

override fun setText(id: Long, text: String): Completable =
execute { it.setText(id = id, text = text) }

override fun setDone(id: Long, isDone: Boolean): Completable =
execute { it.setDone(id = id, isDone = isDone) }

override fun delete(id: Long): Completable =
execute { it.delete(id = id) }

override fun clear(): Completable =
execute { it.clear() }

private fun <T : Any> query(query: (TodoDatabaseQueries) -> Query<T>): Single<Query<T>> =
queries
.observeOn(ioScheduler)
.map(query)

private fun execute(query: (TodoDatabaseQueries) -> Unit): Completable =
queries
.observeOn(ioScheduler)
.doOnBeforeSuccess(query)
.asCompletable()

private fun <T : Any, R> Single<Query<T>>.observe(get: (Query<T>) -> R): Observable<R> =
flatMapObservable { it.observed() }
.observeOn(ioScheduler)
.map(get)

private fun <T : Any> Query<T>.observed(): Observable<Query<T>> =
observable { emitter ->
val listener =
object : Query.Listener {
override fun queryResultsChanged() {
emitter.onNext(this@observed)
}
}

emitter.onNext(this@observed)
addListener(listener)
emitter.setCancellable { removeListener(listener) }
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package example.todo.common.database

import com.badoo.reaktive.base.invoke
import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.completable.completableFromFunction
import com.badoo.reaktive.completable.observeOn
import com.badoo.reaktive.maybe.Maybe
import com.badoo.reaktive.maybe.observeOn
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.observeOn
import com.badoo.reaktive.scheduler.Scheduler
import com.badoo.reaktive.single.notNull
import com.badoo.reaktive.single.singleFromFunction
import com.badoo.reaktive.subject.behavior.BehaviorSubject

// There were problems when using real database in JS tests, hence the in-memory test implementation
class TestTodoSharedDatabase(
private val scheduler: Scheduler
) : TodoSharedDatabase {

private val itemsSubject = BehaviorSubject<Map<Long, TodoItemEntity>>(emptyMap())
private val itemsObservable = itemsSubject.observeOn(scheduler)
val testing: Testing = Testing()

override fun observeAll(): Observable<List<TodoItemEntity>> =
itemsObservable.map { it.values.toList() }

override fun select(id: Long): Maybe<TodoItemEntity> =
singleFromFunction { testing.select(id = id) }
.notNull()
.observeOn(scheduler)

override fun add(text: String): Completable =
execute { testing.add(text = text) }

override fun setText(id: Long, text: String): Completable =
execute { testing.setText(id = id, text = text) }

override fun setDone(id: Long, isDone: Boolean): Completable =
execute { testing.setDone(id = id, isDone = isDone) }

override fun delete(id: Long): Completable =
execute { testing.delete(id = id) }

override fun clear(): Completable =
execute { testing.clear() }

private fun execute(block: () -> Unit): Completable =
completableFromFunction(block)
.observeOn(scheduler)

inner class Testing {
fun select(id: Long): TodoItemEntity? =
itemsSubject.value[id]

fun selectRequired(id: Long): TodoItemEntity =
requireNotNull(select(id = id))

fun add(text: String) {
updateItems { items ->
val nextId = items.keys.maxOrNull()?.plus(1L) ?: 1L

val item =
TodoItemEntity(
id = nextId,
orderNum = items.size.toLong(),
text = text,
isDone = false
)

items + (nextId to item)
}
}

fun setText(id: Long, text: String) {
updateItem(id = id) { it.copy(text = text) }
}

fun setDone(id: Long, isDone: Boolean) {
updateItem(id = id) { it.copy(isDone = isDone) }
}

fun delete(id: Long) {
updateItems { it - id }
}

fun clear() {
updateItems { emptyMap() }
}

fun getLastInsertId(): Long? =
itemsSubject.value.values.lastOrNull()?.id

private fun updateItems(func: (Map<Long, TodoItemEntity>) -> Map<Long, TodoItemEntity>) {
itemsSubject(func(itemsSubject.value))
}

private fun updateItem(id: Long, func: (TodoItemEntity) -> TodoItemEntity) {
updateItems {
it + (id to it.getValue(id).let(func))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package example.todo.common.database

import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.maybe.Maybe
import com.badoo.reaktive.observable.Observable

interface TodoSharedDatabase {

fun observeAll(): Observable<List<TodoItemEntity>>

fun select(id: Long): Maybe<TodoItemEntity>

fun add(text: String): Completable

fun setText(id: Long, text: String): Completable

fun setDone(id: Long, isDone: Boolean): Completable

fun delete(id: Long): Completable

fun clear(): Completable
}

This file was deleted.

Loading

0 comments on commit 8f87cda

Please sign in to comment.