mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
Add Delete button
This commit is contained in:
@@ -28,5 +28,9 @@ UPDATE TodoItemEntity
|
||||
SET isDone = :isDone
|
||||
WHERE id = :id;
|
||||
|
||||
delete:
|
||||
DELETE FROM TodoItemEntity
|
||||
WHERE id = :id;
|
||||
|
||||
getLastInsertId:
|
||||
SELECT last_insert_rowid();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user