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 SET isDone = :isDone
WHERE id = :id; WHERE id = :id;
delete:
DELETE FROM TodoItemEntity
WHERE id = :id;
getLastInsertId: getLastInsertId:
SELECT last_insert_rowid(); 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) } completableFromFunction { queries.setDone(id = id, isDone = isDone) }
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
override fun delete(id: Long): Completable =
completableFromFunction { queries.delete(id = id) }
.subscribeOn(ioScheduler)
override fun add(text: String): Completable = override fun add(text: String): Completable =
completableFromFunction { completableFromFunction {
queries.transactionWithResult { 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 { sealed class Intent {
data class SetItemDone(val id: Long, val isDone: Boolean) : 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() data class SetText(val text: String) : Intent()
object AddItem : 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 { private sealed class Result {
data class ItemsLoaded(val items: List<TodoItem>) : Result() data class ItemsLoaded(val items: List<TodoItem>) : Result()
data class ItemDoneChanged(val id: Long, val isDone: Boolean) : 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() data class TextChanged(val text: String) : Result()
} }
@ -45,6 +46,7 @@ internal class TodoMainStoreProvider(
override fun executeIntent(intent: Intent, getState: () -> State): Unit = override fun executeIntent(intent: Intent, getState: () -> State): Unit =
when (intent) { when (intent) {
is Intent.SetItemDone -> setItemDone(id = intent.id, isDone = intent.isDone) 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.SetText -> dispatch(Result.TextChanged(text = intent.text))
is Intent.AddItem -> addItem(state = getState()) is Intent.AddItem -> addItem(state = getState())
} }
@ -54,6 +56,11 @@ internal class TodoMainStoreProvider(
database.setDone(id = id, isDone = isDone).subscribeScoped() 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) { private fun addItem(state: State) {
dispatch(Result.TextChanged(text = "")) dispatch(Result.TextChanged(text = ""))
database.add(text = state.text).subscribeScoped() database.add(text = state.text).subscribeScoped()
@ -65,6 +72,7 @@ internal class TodoMainStoreProvider(
when (result) { when (result) {
is Result.ItemsLoaded -> copy(items = result.items.sorted()) is Result.ItemsLoaded -> copy(items = result.items.sorted())
is Result.ItemDoneChanged -> update(id = result.id) { copy(isDone = result.isDone) } 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) is Result.TextChanged -> copy(text = result.text)
} }
@ -89,6 +97,8 @@ internal class TodoMainStoreProvider(
fun setDone(id: Long, isDone: Boolean): Completable fun setDone(id: Long, isDone: Boolean): Completable
fun delete(id: Long): Completable
fun add(text: String): 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 package example.todo.common.main.ui
import androidx.compose.foundation.Icon
import androidx.compose.foundation.Text import androidx.compose.foundation.Text
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box 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.Button
import androidx.compose.material.Checkbox import androidx.compose.material.Checkbox
import androidx.compose.material.Divider import androidx.compose.material.Divider
import androidx.compose.material.IconButton
import androidx.compose.material.OutlinedTextField import androidx.compose.material.OutlinedTextField
import androidx.compose.material.TopAppBar 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.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -43,7 +47,8 @@ internal fun TodoMainUi(
TodoList( TodoList(
items = state.items, items = state.items,
onItemClicked = { output.onNext(Output.Selected(id = it)) }, 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( private fun TodoList(
items: List<TodoItem>, items: List<TodoItem>,
onItemClicked: (id: Long) -> Unit, onItemClicked: (id: Long) -> Unit,
onDoneChanged: (id: Long, isDone: Boolean) -> Unit onDoneChanged: (id: Long, isDone: Boolean) -> Unit,
onDeleteItemClicked: (id: Long) -> Unit
) { ) {
LazyColumnFor(items = items) { item -> 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(
text = AnnotatedString(item.text), text = AnnotatedString(item.text),
modifier = Modifier.weight(1F), modifier = Modifier.weight(1F).align(Alignment.CenterVertically),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Checkbox( IconButton(onClick = { onDeleteItemClicked(item.id) }) {
checked = item.isDone, Icon(Icons.Default.Delete)
onCheckedChange = { onDoneChanged(item.id, it) } }
)
} }
Divider() 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.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
@Suppress("TestFunctionName") @Suppress("TestFunctionName")
@ -59,6 +60,16 @@ class TodoMainTest {
assertEquals("Item1", firstItem().text) 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 @Test
fun WHEN_item_selected_THEN_Output_Selected_emitted() { fun WHEN_item_selected_THEN_Output_Selected_emitted() {
queries.add("Item1") queries.add("Item1")
@ -70,25 +81,35 @@ class TodoMainTest {
} }
@Test @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") queries.add("Item1")
val id = firstItem().id val id = firstItem().id
queries.setDone(id = id, isDone = false) queries.setDone(id = id, isDone = false)
impl.onIntent(Intent.SetItemDone(id = id, isDone = true)) impl.onIntent(Intent.SetItemDone(id = id, isDone = true))
assertTrue(firstItem().isDone) assertTrue(queries.select(id = id).executeAsOne().isDone)
} }
@Test @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") queries.add("Item1")
val id = firstItem().id val id = firstItem().id
queries.setDone(id = id, isDone = true) queries.setDone(id = id, isDone = true)
impl.onIntent(Intent.SetItemDone(id = id, isDone = false)) 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 @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.completable.completableFromFunction
import com.badoo.reaktive.observable.Observable import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.subject.behavior.BehaviorSubject 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 { internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database {
@ -24,6 +22,11 @@ internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database {
update(id = id) { copy(isDone = isDone) } 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 = override fun add(text: String): Completable =
completableFromFunction { completableFromFunction {
val id = items.maxBy(TodoItem::id)?.id?.inc() ?: 1L 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.BeforeTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@Suppress("TestFunctionName") @Suppress("TestFunctionName")
@ -57,6 +58,42 @@ class TodoMainStoreTest {
assertTrue(store.state.items.first { it.id == 2L }.isDone) 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 @Test
fun WHEN_Intent_SetText_WHEN_text_changed_in_state() { fun WHEN_Intent_SetText_WHEN_text_changed_in_state() {
val store = provider.provide() val store = provider.provide()

Loading…
Cancel
Save