Browse Source

Add Delete button

pull/10/head
Arkadii Ivanov 4 years ago
parent
commit
7426dc702a
  1. 4
      examples/todoapp/common/database/src/commonMain/sqldelight/example/todo/common/database/TodoDatabase.sq
  2. 4
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainStoreDatabase.kt
  3. 1
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStore.kt
  4. 10
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStoreProvider.kt
  5. 31
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/ui/TodoMainUi.kt
  6. 29
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/integration/TodoMainTest.kt
  7. 7
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TestTodoMainStoreDatabase.kt
  8. 37
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TodoMainStoreTest.kt

4
examples/todoapp/common/database/src/commonMain/sqldelight/example/todo/common/database/TodoDatabase.sq

@ -28,5 +28,9 @@ UPDATE TodoItemEntity
SET isDone = :isDone
WHERE id = :id;
delete:
DELETE FROM TodoItemEntity
WHERE id = :id;
getLastInsertId:
SELECT last_insert_rowid();

4
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainStoreDatabase.kt

@ -35,6 +35,10 @@ internal class TodoMainStoreDatabase(
completableFromFunction { queries.setDone(id = id, isDone = isDone) }
.subscribeOn(ioScheduler)
override fun delete(id: Long): Completable =
completableFromFunction { queries.delete(id = id) }
.subscribeOn(ioScheduler)
override fun add(text: String): Completable =
completableFromFunction {
queries.transactionWithResult {

1
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStore.kt

@ -8,6 +8,7 @@ internal interface TodoMainStore : Store<Intent, State, Nothing> {
sealed class Intent {
data class SetItemDone(val id: Long, val isDone: Boolean) : Intent()
data class DeleteItem(val id: Long) : Intent()
data class SetText(val text: String) : Intent()
object AddItem : Intent()
}

10
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStoreProvider.kt

@ -30,6 +30,7 @@ internal class TodoMainStoreProvider(
private sealed class Result {
data class ItemsLoaded(val items: List<TodoItem>) : Result()
data class ItemDoneChanged(val id: Long, val isDone: Boolean) : Result()
data class ItemDeleted(val id: Long) : Result()
data class TextChanged(val text: String) : Result()
}
@ -45,6 +46,7 @@ internal class TodoMainStoreProvider(
override fun executeIntent(intent: Intent, getState: () -> State): Unit =
when (intent) {
is Intent.SetItemDone -> setItemDone(id = intent.id, isDone = intent.isDone)
is Intent.DeleteItem -> deleteItem(id = intent.id)
is Intent.SetText -> dispatch(Result.TextChanged(text = intent.text))
is Intent.AddItem -> addItem(state = getState())
}
@ -54,6 +56,11 @@ internal class TodoMainStoreProvider(
database.setDone(id = id, isDone = isDone).subscribeScoped()
}
private fun deleteItem(id: Long) {
dispatch(Result.ItemDeleted(id = id))
database.delete(id = id).subscribeScoped()
}
private fun addItem(state: State) {
dispatch(Result.TextChanged(text = ""))
database.add(text = state.text).subscribeScoped()
@ -65,6 +72,7 @@ internal class TodoMainStoreProvider(
when (result) {
is Result.ItemsLoaded -> copy(items = result.items.sorted())
is Result.ItemDoneChanged -> update(id = result.id) { copy(isDone = result.isDone) }
is Result.ItemDeleted -> copy(items = items.filterNot { it.id == result.id })
is Result.TextChanged -> copy(text = result.text)
}
@ -89,6 +97,8 @@ internal class TodoMainStoreProvider(
fun setDone(id: Long, isDone: Boolean): Completable
fun delete(id: Long): Completable
fun add(text: String): Completable
}
}

31
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/ui/TodoMainUi.kt

@ -1,5 +1,6 @@
package example.todo.common.main.ui
import androidx.compose.foundation.Icon
import androidx.compose.foundation.Text
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@ -12,8 +13,11 @@ import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.Divider
import androidx.compose.material.IconButton
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -43,7 +47,8 @@ internal fun TodoMainUi(
TodoList(
items = state.items,
onItemClicked = { output.onNext(Output.Selected(id = it)) },
onDoneChanged = { id, isDone -> intents(Intent.SetItemDone(id = id, isDone = isDone)) }
onDoneChanged = { id, isDone -> intents(Intent.SetItemDone(id = id, isDone = isDone)) },
onDeleteItemClicked = { intents(Intent.DeleteItem(id = it)) }
)
}
@ -59,23 +64,33 @@ internal fun TodoMainUi(
private fun TodoList(
items: List<TodoItem>,
onItemClicked: (id: Long) -> Unit,
onDoneChanged: (id: Long, isDone: Boolean) -> Unit
onDoneChanged: (id: Long, isDone: Boolean) -> Unit,
onDeleteItemClicked: (id: Long) -> Unit
) {
LazyColumnFor(items = items) { item ->
Row(modifier = Modifier.clickable(onClick = { onItemClicked(item.id) }).padding(8.dp)) {
Row(modifier = Modifier.clickable(onClick = { onItemClicked(item.id) })) {
Spacer(modifier = Modifier.width(8.dp))
Checkbox(
checked = item.isDone,
modifier = Modifier.align(Alignment.CenterVertically),
onCheckedChange = { onDoneChanged(item.id, it) }
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = AnnotatedString(item.text),
modifier = Modifier.weight(1F),
modifier = Modifier.weight(1F).align(Alignment.CenterVertically),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(8.dp))
Checkbox(
checked = item.isDone,
onCheckedChange = { onDoneChanged(item.id, it) }
)
IconButton(onClick = { onDeleteItemClicked(item.id) }) {
Icon(Icons.Default.Delete)
}
}
Divider()

29
examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/integration/TodoMainTest.kt

@ -21,6 +21,7 @@ import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
@ -59,6 +60,16 @@ class TodoMainTest {
assertEquals("Item1", firstItem().text)
}
@Test
fun WHEN_item_deleted_from_database_THEN_item_not_displayed() {
queries.add("Item1")
val id = lastInsertItem().id
queries.delete(id = id)
assertFalse(impl.state.items.any { it.id == id })
}
@Test
fun WHEN_item_selected_THEN_Output_Selected_emitted() {
queries.add("Item1")
@ -70,25 +81,35 @@ class TodoMainTest {
}
@Test
fun GIVEN_item_isDone_false_WHEN_done_changed_to_true_THEN_item_isDone_true() {
fun GIVEN_item_isDone_false_WHEN_done_changed_to_true_THEN_item_isDone_true_in_database() {
queries.add("Item1")
val id = firstItem().id
queries.setDone(id = id, isDone = false)
impl.onIntent(Intent.SetItemDone(id = id, isDone = true))
assertTrue(firstItem().isDone)
assertTrue(queries.select(id = id).executeAsOne().isDone)
}
@Test
fun GIVEN_item_isDone_true_WHEN_done_changed_to_false_THEN_item_isDone_false() {
fun GIVEN_item_isDone_true_WHEN_done_changed_to_false_THEN_item_isDone_false_in_database() {
queries.add("Item1")
val id = firstItem().id
queries.setDone(id = id, isDone = true)
impl.onIntent(Intent.SetItemDone(id = id, isDone = false))
assertFalse(firstItem().isDone)
assertFalse(queries.select(id = id).executeAsOne().isDone)
}
@Test
fun WHEN_delete_clicked_THEN_item_deleted_in_database() {
queries.add("Item1")
val id = firstItem().id
impl.onIntent(Intent.DeleteItem(id = id))
assertNull(queries.select(id = id).executeAsOneOrNull())
}
@Test

7
examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TestTodoMainStoreDatabase.kt

@ -4,8 +4,6 @@ import com.badoo.reaktive.completable.Completable
import com.badoo.reaktive.completable.completableFromFunction
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.subject.behavior.BehaviorSubject
import example.todo.common.main.store.TodoItem
import example.todo.common.main.store.TodoMainStoreProvider
internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database {
@ -24,6 +22,11 @@ internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database {
update(id = id) { copy(isDone = isDone) }
}
override fun delete(id: Long): Completable =
completableFromFunction {
this.items = items.filterNot { it.id == id }
}
override fun add(text: String): Completable =
completableFromFunction {
val id = items.maxBy(TodoItem::id)?.id?.inc() ?: 1L

37
examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TodoMainStoreTest.kt

@ -7,6 +7,7 @@ import example.todo.common.main.store.TodoMainStore.Intent
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
@ -57,6 +58,42 @@ class TodoMainStoreTest {
assertTrue(store.state.items.first { it.id == 2L }.isDone)
}
@Test
fun WHEN_Intent_SetItemDone_THEN_done_changed_in_database() {
val item1 = TodoItem(id = 1L, text = "item1")
val item2 = TodoItem(id = 2L, text = "item2", isDone = false)
database.items = listOf(item1, item2)
val store = provider.provide()
store.accept(Intent.SetItemDone(id = 2L, isDone = true))
assertTrue(database.items.first { it.id == 2L }.isDone)
}
@Test
fun WHEN_Intent_DeleteItem_THEN_item_deleted_in_state() {
val item1 = TodoItem(id = 1L, text = "item1")
val item2 = TodoItem(id = 2L, text = "item2")
database.items = listOf(item1, item2)
val store = provider.provide()
store.accept(Intent.DeleteItem(id = 2L))
assertFalse(store.state.items.any { it.id == 2L })
}
@Test
fun WHEN_Intent_DeleteItem_THEN_item_deleted_in_database() {
val item1 = TodoItem(id = 1L, text = "item1")
val item2 = TodoItem(id = 2L, text = "item2")
database.items = listOf(item1, item2)
val store = provider.provide()
store.accept(Intent.DeleteItem(id = 2L))
assertFalse(database.items.any { it.id == 2L })
}
@Test
fun WHEN_Intent_SetText_WHEN_text_changed_in_state() {
val store = provider.provide()

Loading…
Cancel
Save