Arkadii Ivanov
4 years ago
25 changed files with 241 additions and 426 deletions
@ -1,19 +0,0 @@
|
||||
plugins { |
||||
id("multiplatform-setup") |
||||
id("multiplatform-compose-setup") |
||||
} |
||||
|
||||
kotlin { |
||||
sourceSets { |
||||
named("commonMain") { |
||||
dependencies { |
||||
implementation(project(":common:utils")) |
||||
implementation(project(":common:database")) |
||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) |
||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive) |
||||
implementation(Deps.ArkIvanov.Decompose.decompose) |
||||
implementation(Deps.Badoo.Reaktive.reaktive) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest package="example.todo.common.add"/> |
@ -1,20 +0,0 @@
|
||||
package example.todo.common.add |
||||
|
||||
import com.arkivanov.decompose.ComponentContext |
||||
import com.arkivanov.mvikotlin.core.store.StoreFactory |
||||
import example.todo.common.add.TodoAdd.Dependencies |
||||
import example.todo.common.add.integration.TodoAddImpl |
||||
import example.todo.common.utils.Component |
||||
import example.todo.database.TodoDatabase |
||||
|
||||
interface TodoAdd : Component { |
||||
|
||||
interface Dependencies { |
||||
val storeFactory: StoreFactory |
||||
val database: TodoDatabase |
||||
} |
||||
} |
||||
|
||||
@Suppress("FunctionName") // Factory function |
||||
fun TodoAdd(componentContext: ComponentContext, dependencies: Dependencies): TodoAdd = |
||||
TodoAddImpl(componentContext, dependencies) |
@ -1,64 +0,0 @@
|
||||
package example.todo.common.add.integration |
||||
|
||||
import androidx.compose.foundation.Text |
||||
import androidx.compose.foundation.layout.Row |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.material.Button |
||||
import androidx.compose.material.OutlinedTextField |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.input.key.ExperimentalKeyInput |
||||
import androidx.compose.ui.input.key.Key |
||||
import androidx.compose.ui.input.key.keyInputFilter |
||||
import androidx.compose.ui.unit.dp |
||||
import com.arkivanov.decompose.ComponentContext |
||||
import example.todo.common.add.TodoAdd |
||||
import example.todo.common.add.TodoAdd.Dependencies |
||||
import example.todo.common.add.store.TodoAddStore.Intent |
||||
import example.todo.common.add.store.TodoAddStoreProvider |
||||
import example.todo.common.utils.composeState |
||||
import example.todo.common.utils.getStore |
||||
import example.todo.common.utils.onKeyUp |
||||
|
||||
internal class TodoAddImpl( |
||||
componentContext: ComponentContext, |
||||
dependencies: Dependencies |
||||
) : TodoAdd, ComponentContext by componentContext, Dependencies by dependencies { |
||||
|
||||
private val store = |
||||
instanceKeeper.getStore { |
||||
TodoAddStoreProvider( |
||||
storeFactory = storeFactory, |
||||
database = TodoAddStoreDatabase(queries = database.todoDatabaseQueries) |
||||
).provide() |
||||
} |
||||
|
||||
@OptIn(ExperimentalKeyInput::class) |
||||
@Composable |
||||
override fun invoke() { |
||||
val state by store.composeState |
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp)) { |
||||
OutlinedTextField( |
||||
value = state.text, |
||||
modifier = Modifier.weight(weight = 1F).keyInputFilter(onKeyUp(Key.Enter, ::onAddClicked)), |
||||
onValueChange = ::onTextChanged, |
||||
label = { Text(text = "Add a todo") } |
||||
) |
||||
|
||||
Button(modifier = Modifier.padding(start = 8.dp), onClick = ::onAddClicked) { |
||||
Text(text = "+") |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun onTextChanged(text: String) { |
||||
store.accept(Intent.SetText(text = text)) |
||||
} |
||||
|
||||
private fun onAddClicked() { |
||||
store.accept(Intent.Add) |
||||
} |
||||
} |
@ -1,23 +0,0 @@
|
||||
package example.todo.common.add.integration |
||||
|
||||
import com.badoo.reaktive.completable.Completable |
||||
import com.badoo.reaktive.completable.completableFromFunction |
||||
import com.badoo.reaktive.completable.subscribeOn |
||||
import com.badoo.reaktive.scheduler.ioScheduler |
||||
import example.todo.common.add.store.TodoAddStoreProvider.Database |
||||
import example.todo.common.database.TodoDatabaseQueries |
||||
|
||||
internal class TodoAddStoreDatabase( |
||||
private val queries: TodoDatabaseQueries |
||||
) : Database { |
||||
|
||||
override fun add(text: String): Completable = |
||||
completableFromFunction { |
||||
queries.transactionWithResult { |
||||
queries.add(text = text) |
||||
val lastId = queries.getLastInsertId().executeAsOne() |
||||
queries.select(id = lastId).executeAsOne() |
||||
} |
||||
} |
||||
.subscribeOn(ioScheduler) |
||||
} |
@ -1,17 +0,0 @@
|
||||
package example.todo.common.add.store |
||||
|
||||
import com.arkivanov.mvikotlin.core.store.Store |
||||
import example.todo.common.add.store.TodoAddStore.Intent |
||||
import example.todo.common.add.store.TodoAddStore.State |
||||
|
||||
internal interface TodoAddStore : Store<Intent, State, Nothing> { |
||||
|
||||
sealed class Intent { |
||||
data class SetText(val text: String) : Intent() |
||||
object Add : Intent() |
||||
} |
||||
|
||||
data class State( |
||||
val text: String = "" |
||||
) |
||||
} |
@ -1,51 +0,0 @@
|
||||
package example.todo.common.add.store |
||||
|
||||
import com.arkivanov.mvikotlin.core.store.Reducer |
||||
import com.arkivanov.mvikotlin.core.store.Store |
||||
import com.arkivanov.mvikotlin.core.store.StoreFactory |
||||
import com.arkivanov.mvikotlin.extensions.reaktive.ReaktiveExecutor |
||||
import com.badoo.reaktive.completable.Completable |
||||
import example.todo.common.add.store.TodoAddStore.Intent |
||||
import example.todo.common.add.store.TodoAddStore.State |
||||
|
||||
internal class TodoAddStoreProvider( |
||||
private val storeFactory: StoreFactory, |
||||
private val database: Database |
||||
) { |
||||
|
||||
fun provide(): TodoAddStore = |
||||
object : TodoAddStore, Store<Intent, State, Nothing> by storeFactory.create( |
||||
name = "TodoAddStore", |
||||
initialState = State(), |
||||
executorFactory = ::ExecutorImpl, |
||||
reducer = ReducerImpl |
||||
) {} |
||||
|
||||
private sealed class Result { |
||||
data class TextChanged(val text: String) : Result() |
||||
} |
||||
|
||||
private inner class ExecutorImpl : ReaktiveExecutor<Intent, Nothing, State, Result, Nothing>() { |
||||
override fun executeIntent(intent: Intent, getState: () -> State): Unit = |
||||
when (intent) { |
||||
is Intent.SetText -> dispatch(Result.TextChanged(text = intent.text)) |
||||
is Intent.Add -> add(state = getState()) |
||||
} |
||||
|
||||
private fun add(state: State) { |
||||
dispatch(Result.TextChanged(text = "")) |
||||
database.add(text = state.text).subscribeScoped() |
||||
} |
||||
} |
||||
|
||||
private object ReducerImpl : Reducer<State, Result> { |
||||
override fun State.reduce(result: Result): State = |
||||
when (result) { |
||||
is Result.TextChanged -> copy(text = result.text) |
||||
} |
||||
} |
||||
|
||||
interface Database { |
||||
fun add(text: String): Completable |
||||
} |
||||
} |
@ -1,27 +0,0 @@
|
||||
plugins { |
||||
id("multiplatform-setup") |
||||
id("multiplatform-compose-setup") |
||||
} |
||||
|
||||
kotlin { |
||||
sourceSets { |
||||
named("commonMain") { |
||||
dependencies { |
||||
implementation(project(":common:utils")) |
||||
implementation(project(":common:database")) |
||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) |
||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive) |
||||
implementation(Deps.ArkIvanov.Decompose.decompose) |
||||
implementation(Deps.Badoo.Reaktive.reaktive) |
||||
} |
||||
} |
||||
|
||||
named("commonTest") { |
||||
dependencies { |
||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) |
||||
implementation(Deps.Badoo.Reaktive.reaktiveTesting) |
||||
implementation(Deps.Badoo.Reaktive.utils) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest package="example.todo.common.list"/> |
@ -1,26 +0,0 @@
|
||||
package example.todo.common.list |
||||
|
||||
import com.arkivanov.decompose.ComponentContext |
||||
import com.arkivanov.mvikotlin.core.store.StoreFactory |
||||
import com.badoo.reaktive.base.Consumer |
||||
import example.todo.common.list.TodoList.Dependencies |
||||
import example.todo.common.list.integration.TodoListImpl |
||||
import example.todo.common.utils.Component |
||||
import example.todo.database.TodoDatabase |
||||
|
||||
interface TodoList : Component { |
||||
|
||||
interface Dependencies { |
||||
val storeFactory: StoreFactory |
||||
val database: TodoDatabase |
||||
val listOutput: Consumer<Output> |
||||
} |
||||
|
||||
sealed class Output { |
||||
data class ItemSelected(val id: Long) : Output() |
||||
} |
||||
} |
||||
|
||||
@Suppress("FunctionName") // Factory function |
||||
fun TodoList(componentContext: ComponentContext, dependencies: Dependencies): TodoList = |
||||
TodoListImpl(componentContext, dependencies) |
@ -1,75 +0,0 @@
|
||||
package example.todo.common.list.integration |
||||
|
||||
import androidx.compose.foundation.Text |
||||
import androidx.compose.foundation.clickable |
||||
import androidx.compose.foundation.layout.Row |
||||
import androidx.compose.foundation.layout.Spacer |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.foundation.layout.width |
||||
import androidx.compose.foundation.lazy.LazyColumnFor |
||||
import androidx.compose.material.Checkbox |
||||
import androidx.compose.material.Divider |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.text.AnnotatedString |
||||
import androidx.compose.ui.text.style.TextOverflow |
||||
import androidx.compose.ui.unit.dp |
||||
import com.arkivanov.decompose.ComponentContext |
||||
import example.todo.common.list.TodoList |
||||
import example.todo.common.list.TodoList.Dependencies |
||||
import example.todo.common.list.TodoList.Output |
||||
import example.todo.common.list.store.TodoListStore |
||||
import example.todo.common.list.store.TodoListStore.Intent |
||||
import example.todo.common.list.store.TodoListStoreProvider |
||||
import example.todo.common.utils.composeState |
||||
import example.todo.common.utils.getStore |
||||
|
||||
internal class TodoListImpl( |
||||
componentContext: ComponentContext, |
||||
dependencies: Dependencies |
||||
) : TodoList, ComponentContext by componentContext, Dependencies by dependencies { |
||||
|
||||
private val store = |
||||
instanceKeeper.getStore { |
||||
TodoListStoreProvider( |
||||
storeFactory = storeFactory, |
||||
database = TodoListStoreDatabase(queries = database.todoDatabaseQueries) |
||||
).provide() |
||||
} |
||||
|
||||
internal val state: TodoListStore.State get() = store.state |
||||
|
||||
@Composable |
||||
override fun invoke() { |
||||
val state by store.composeState |
||||
|
||||
LazyColumnFor(items = state.items) { item -> |
||||
Row(modifier = Modifier.clickable(onClick = { onItemClicked(id = item.id) }).padding(8.dp)) { |
||||
Text( |
||||
text = AnnotatedString(item.text), |
||||
modifier = Modifier.weight(1F), |
||||
maxLines = 1, |
||||
overflow = TextOverflow.Ellipsis |
||||
) |
||||
|
||||
Spacer(modifier = Modifier.width(8.dp)) |
||||
|
||||
Checkbox( |
||||
checked = item.isDone, |
||||
onCheckedChange = { onDoneChanged(id = item.id, isDone = it) } |
||||
) |
||||
} |
||||
|
||||
Divider() |
||||
} |
||||
} |
||||
|
||||
internal fun onItemClicked(id: Long) { |
||||
listOutput.onNext(Output.ItemSelected(id = id)) |
||||
} |
||||
|
||||
internal fun onDoneChanged(id: Long, isDone: Boolean) { |
||||
store.accept(Intent.SetDone(id = id, isDone = isDone)) |
||||
} |
||||
} |
@ -1,16 +0,0 @@
|
||||
package example.todo.common.list.store |
||||
|
||||
import com.arkivanov.mvikotlin.core.store.Store |
||||
import example.todo.common.list.store.TodoListStore.Intent |
||||
import example.todo.common.list.store.TodoListStore.State |
||||
|
||||
internal interface TodoListStore : Store<Intent, State, Nothing> { |
||||
|
||||
sealed class Intent { |
||||
data class SetDone(val id: Long, val isDone: Boolean) : Intent() |
||||
} |
||||
|
||||
data class State( |
||||
val items: List<TodoItem> = emptyList() |
||||
) |
||||
} |
@ -1,11 +0,0 @@
|
||||
package example.todo.common.main.integration |
||||
|
||||
import example.todo.common.list.TodoList |
||||
import example.todo.common.main.TodoMain.Output |
||||
|
||||
internal val listOutputToOutput: TodoList.Output.() -> Output? = |
||||
{ |
||||
when (this) { |
||||
is TodoList.Output.ItemSelected -> Output.Selected(id = id) |
||||
} |
||||
} |
@ -1,4 +1,4 @@
|
||||
package example.todo.common.list.store |
||||
package example.todo.common.main.store |
||||
|
||||
internal data class TodoItem( |
||||
val id: Long = 0L, |
@ -0,0 +1,19 @@
|
||||
package example.todo.common.main.store |
||||
|
||||
import com.arkivanov.mvikotlin.core.store.Store |
||||
import example.todo.common.main.store.TodoMainStore.Intent |
||||
import example.todo.common.main.store.TodoMainStore.State |
||||
|
||||
internal interface TodoMainStore : Store<Intent, State, Nothing> { |
||||
|
||||
sealed class Intent { |
||||
data class SetItemDone(val id: Long, val isDone: Boolean) : Intent() |
||||
data class SetText(val text: String) : Intent() |
||||
object AddItem : Intent() |
||||
} |
||||
|
||||
data class State( |
||||
val items: List<TodoItem> = emptyList(), |
||||
val text: String = "" |
||||
) |
||||
} |
Loading…
Reference in new issue