Browse Source

Add iOS target and app for todoapp example (#166)

pull/210/head
Arkadii Ivanov 4 years ago committed by GitHub
parent
commit
497fd017fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      examples/todoapp/android/build.gradle.kts
  2. 3
      examples/todoapp/android/src/main/java/example/todo/android/MainActivity.kt
  3. 14
      examples/todoapp/buildSrc/buildSrc/src/main/kotlin/Deps.kt
  4. 26
      examples/todoapp/buildSrc/src/main/kotlin/android-setup.gradle.kts
  5. 22
      examples/todoapp/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts
  6. 31
      examples/todoapp/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts
  7. 18
      examples/todoapp/common/compose-ui/build.gradle.kts
  8. 2
      examples/todoapp/common/compose-ui/src/androidMain/AndroidManifest.xml
  9. 2
      examples/todoapp/common/compose-ui/src/androidMain/kotlin/example/todo/common/ui/Scrollbars.kt
  10. 20
      examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/Crossfade.kt
  11. 2
      examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/Scrollbars.kt
  12. 2
      examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/ShortcutHandler.kt
  13. 27
      examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/TodoEditUi.kt
  14. 37
      examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/TodoMainUi.kt
  15. 16
      examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/TodoRootUi.kt
  16. 2
      examples/todoapp/common/compose-ui/src/desktopMain/kotlin/example/todo/common/ui/Scrollbars.kt
  17. 7
      examples/todoapp/common/database/build.gradle.kts
  18. 3
      examples/todoapp/common/database/src/commonMain/sqldelight/example/todo/common/database/TodoDatabase.sq
  19. 26
      examples/todoapp/common/database/src/iosMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt
  20. 9
      examples/todoapp/common/database/src/iosMain/kotlin/example/todo/common/database/TodoDatabaseDriverFactory.kt
  21. 2
      examples/todoapp/common/edit/build.gradle.kts
  22. 17
      examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/TodoEdit.kt
  23. 2
      examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/TodoItem.kt
  24. 12
      examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/integration/Mappers.kt
  25. 29
      examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/integration/TodoEditImpl.kt
  26. 8
      examples/todoapp/common/main/build.gradle.kts
  27. 4
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/TodoItem.kt
  28. 21
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/TodoMain.kt
  29. 12
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/Mappers.kt
  30. 36
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainImpl.kt
  31. 11
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainStoreDatabase.kt
  32. 1
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStore.kt
  33. 1
      examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoMainStoreProvider.kt
  34. 38
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/integration/TodoMainTest.kt
  35. 1
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TestTodoMainStoreDatabase.kt
  36. 1
      examples/todoapp/common/main/src/commonTest/kotlin/example/todo/common/main/store/TodoMainStoreTest.kt
  37. 78
      examples/todoapp/common/root/build.gradle.kts
  38. 14
      examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/TodoRoot.kt
  39. 26
      examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/integration/TodoRootImpl.kt
  40. 6
      examples/todoapp/common/utils/build.gradle.kts
  41. 24
      examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/Binder.kt
  42. 9
      examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/Component.kt
  43. 16
      examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/CrossfadeExt.kt
  44. 31
      examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/StoreExt.kt
  45. 1
      examples/todoapp/desktop/build.gradle.kts
  46. 3
      examples/todoapp/desktop/src/jvmMain/kotlin/example/todo/desktop/Main.kt
  47. 1
      examples/todoapp/gradle.properties
  48. 18
      examples/todoapp/ios/.gitignore
  49. 418
      examples/todoapp/ios/TodoApp.xcodeproj/project.pbxproj
  50. 7
      examples/todoapp/ios/TodoApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  51. 8
      examples/todoapp/ios/TodoApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  52. 11
      examples/todoapp/ios/ios/Assets.xcassets/AccentColor.colorset/Contents.json
  53. 98
      examples/todoapp/ios/ios/Assets.xcassets/AppIcon.appiconset/Contents.json
  54. 6
      examples/todoapp/ios/ios/Assets.xcassets/Contents.json
  55. 19
      examples/todoapp/ios/ios/ComponentHolder.swift
  56. 31
      examples/todoapp/ios/ios/ContentView.swift
  57. 67
      examples/todoapp/ios/ios/EditView.swift
  58. 50
      examples/todoapp/ios/ios/Info.plist
  59. 28
      examples/todoapp/ios/ios/InputView.swift
  60. 39
      examples/todoapp/ios/ios/ListView.swift
  61. 68
      examples/todoapp/ios/ios/MainView.swift
  62. 5
      examples/todoapp/ios/ios/MutableStateBuilder.swift
  63. 22
      examples/todoapp/ios/ios/ObservableValue.swift
  64. 6
      examples/todoapp/ios/ios/Preview Content/Preview Assets.xcassets/Contents.json
  65. 43
      examples/todoapp/ios/ios/RootView.swift
  66. 13
      examples/todoapp/ios/ios/SimpleRouterState.swift
  67. 10
      examples/todoapp/ios/ios/TodoApp.swift
  68. 1
      examples/todoapp/settings.gradle.kts

2
examples/todoapp/android/build.gradle.kts

@ -30,6 +30,7 @@ dependencies {
implementation(project(":common:database")) implementation(project(":common:database"))
implementation(project(":common:utils")) implementation(project(":common:utils"))
implementation(project(":common:root")) implementation(project(":common:root"))
implementation(project(":common:compose-ui"))
implementation(compose.material) implementation(compose.material)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain)
@ -37,4 +38,5 @@ dependencies {
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinTimeTravel) implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinTimeTravel)
implementation(Deps.ArkIvanov.Decompose.decompose) implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.ArkIvanov.Decompose.extensionsCompose) implementation(Deps.ArkIvanov.Decompose.extensionsCompose)
implementation(Deps.AndroidX.AppCompat.appCompat)
} }

3
examples/todoapp/android/src/main/java/example/todo/android/MainActivity.kt

@ -13,6 +13,7 @@ import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.arkivanov.mvikotlin.timetravel.store.TimeTravelStoreFactory import com.arkivanov.mvikotlin.timetravel.store.TimeTravelStoreFactory
import example.todo.common.database.TodoDatabaseDriver import example.todo.common.database.TodoDatabaseDriver
import example.todo.common.root.TodoRoot import example.todo.common.root.TodoRoot
import example.todo.common.ui.TodoRootContent
import example.todo.database.TodoDatabase import example.todo.database.TodoDatabase
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@ -22,7 +23,7 @@ class MainActivity : AppCompatActivity() {
setContent { setContent {
ComposeAppTheme { ComposeAppTheme {
Surface(color = MaterialTheme.colors.background) { Surface(color = MaterialTheme.colors.background) {
rootComponent(::todoRoot).invoke() TodoRootContent(rootComponent(::todoRoot))
} }
} }
} }

14
examples/todoapp/buildSrc/buildSrc/src/main/kotlin/Deps.kt

@ -25,19 +25,30 @@ object Deps {
} }
} }
object AndroidX {
object AppCompat {
const val appCompat = "androidx.appcompat:appcompat:1.1.0"
}
}
object ArkIvanov { object ArkIvanov {
object MVIKotlin { object MVIKotlin {
private const val VERSION = "2.0.0" private const val VERSION = "2.0.0"
const val rx = "com.arkivanov.mvikotlin:rx:$VERSION"
const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION" const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION"
const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION" const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION"
const val mvikotlinMainIosX64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosx64:$VERSION"
const val mvikotlinMainIosArm64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosarm64:$VERSION"
const val mvikotlinLogging = "com.arkivanov.mvikotlin:mvikotlin-logging:$VERSION" const val mvikotlinLogging = "com.arkivanov.mvikotlin:mvikotlin-logging:$VERSION"
const val mvikotlinTimeTravel = "com.arkivanov.mvikotlin:mvikotlin-timetravel:$VERSION" const val mvikotlinTimeTravel = "com.arkivanov.mvikotlin:mvikotlin-timetravel:$VERSION"
const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION" const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION"
} }
object Decompose { object Decompose {
private const val VERSION = "0.1.1" private const val VERSION = "0.1.3"
const val decompose = "com.arkivanov.decompose:decompose:$VERSION" const val decompose = "com.arkivanov.decompose:decompose:$VERSION"
const val decomposeIosX64 = "com.arkivanov.decompose:decompose-iosx64:$VERSION"
const val decomposeIosArm64 = "com.arkivanov.decompose:decompose-iosarm64:$VERSION"
const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION" const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION"
} }
} }
@ -59,6 +70,7 @@ object Deps {
const val gradlePlugin = "com.squareup.sqldelight:gradle-plugin:$VERSION" const val gradlePlugin = "com.squareup.sqldelight:gradle-plugin:$VERSION"
const val androidDriver = "com.squareup.sqldelight:android-driver:$VERSION" const val androidDriver = "com.squareup.sqldelight:android-driver:$VERSION"
const val sqliteDriver = "com.squareup.sqldelight:sqlite-driver:$VERSION" const val sqliteDriver = "com.squareup.sqldelight:sqlite-driver:$VERSION"
const val nativeDriver = "com.squareup.sqldelight:native-driver:$VERSION"
} }
} }
} }

26
examples/todoapp/buildSrc/src/main/kotlin/android-setup.gradle.kts

@ -0,0 +1,26 @@
import gradle.kotlin.dsl.accessors._2e8a70bdda5e56ec477a6ff432ddf9d7.android
plugins {
id("com.android.library")
}
android {
compileSdkVersion(30)
defaultConfig {
minSdkVersion(23)
targetSdkVersion(30)
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
sourceSets {
named("main") {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
res.srcDirs("src/androidMain/res")
}
}
}

22
examples/todoapp/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts

@ -1,29 +1,39 @@
import org.jetbrains.compose.compose import org.jetbrains.compose.compose
plugins { plugins {
id("com.android.library")
id("kotlin-multiplatform") id("kotlin-multiplatform")
id("org.jetbrains.compose") id("org.jetbrains.compose")
} }
kotlin { kotlin {
jvm("desktop")
android()
sourceSets { sourceSets {
named("commonMain") { named("commonMain") {
dependencies { dependencies {
api(compose.runtime) implementation(compose.runtime)
api(compose.foundation) implementation(compose.foundation)
api(compose.material) implementation(compose.material)
} }
} }
named("androidMain") { named("androidMain") {
dependencies { dependencies {
api("androidx.appcompat:appcompat:1.1.0") implementation("androidx.appcompat:appcompat:1.1.0")
api("androidx.core:core-ktx:1.3.1") implementation("androidx.core:core-ktx:1.3.1")
} }
} }
named("desktopMain") { named("desktopMain") {
dependencies { dependencies {
api(compose.desktop.common) implementation(compose.desktop.common)
} }
} }
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
} }

31
examples/todoapp/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts

@ -6,17 +6,9 @@ plugins {
kotlin { kotlin {
jvm("desktop") jvm("desktop")
android() android()
ios()
sourceSets { sourceSets {
named("commonMain") {
dependencies {
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive)
implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.Badoo.Reaktive.reaktive)
}
}
named("commonTest") { named("commonTest") {
dependencies { dependencies {
implementation(Deps.JetBrains.Kotlin.testCommon) implementation(Deps.JetBrains.Kotlin.testCommon)
@ -40,24 +32,3 @@ kotlin {
kotlinOptions.jvmTarget = "1.8" kotlinOptions.jvmTarget = "1.8"
} }
} }
android {
compileSdkVersion(30)
defaultConfig {
minSdkVersion(23)
targetSdkVersion(30)
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
sourceSets {
named("main") {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
res.srcDirs("src/androidMain/res")
}
}
}

18
examples/todoapp/common/compose-ui/build.gradle.kts

@ -0,0 +1,18 @@
plugins {
id("multiplatform-compose-setup")
id("android-setup")
}
kotlin {
sourceSets {
named("commonMain") {
dependencies {
implementation(project(":common:main"))
implementation(project(":common:edit"))
implementation(project(":common:root"))
implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.ArkIvanov.Decompose.extensionsCompose)
}
}
}
}

2
examples/todoapp/common/compose-ui/src/androidMain/AndroidManifest.xml

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="example.todo.common.ui"/>

2
examples/todoapp/common/utils/src/androidMain/kotlin/example/todo/common/utils/compose/Scrollbars.kt → examples/todoapp/common/compose-ui/src/androidMain/kotlin/example/todo/common/ui/Scrollbars.kt

@ -1,4 +1,4 @@
package example.todo.common.utils.compose package example.todo.common.ui
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable

20
examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/Crossfade.kt

@ -0,0 +1,20 @@
package example.todo.common.ui
import androidx.compose.runtime.Composable
fun <T, K> crossfade(): @Composable (currentChild: T, currentKey: K, children: @Composable (T, K) -> Unit) -> Unit =
{ currentChild: T, currentKey: K, children: @Composable (T, K) -> Unit ->
KeyedCrossfade(currentChild, currentKey, children)
}
@Composable
private fun <T, K> KeyedCrossfade(currentChild: T, currentKey: K, children: @Composable (T, K) -> Unit) {
androidx.compose.animation.Crossfade(current = ChildWrapper(currentChild, currentKey)) {
children(it.child, it.key)
}
}
private class ChildWrapper<out T, out C>(val child: T, val key: C) {
override fun equals(other: Any?): Boolean = key == (other as? ChildWrapper<*, *>)?.key
override fun hashCode(): Int = key.hashCode()
}

2
examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/compose/Scrollbars.kt → examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/Scrollbars.kt

@ -1,4 +1,4 @@
package example.todo.common.utils.compose package example.todo.common.ui
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable

2
examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/ShortcutHandler.kt → examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/ShortcutHandler.kt

@ -1,4 +1,4 @@
package example.todo.common.utils package example.todo.common.ui
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEvent

27
examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/ui/TodoEditUi.kt → examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/TodoEditUi.kt

@ -1,4 +1,4 @@
package example.todo.common.edit.ui package example.todo.common.ui
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -15,35 +15,32 @@ import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.badoo.reaktive.base.Consumer import com.arkivanov.decompose.extensions.compose.jetbrains.asState
import example.todo.common.edit.TodoEdit.Output import example.todo.common.edit.TodoEdit
import example.todo.common.edit.store.TodoEditStore.Intent
import example.todo.common.edit.store.TodoEditStore.State
@Composable @Composable
internal fun TodoEditUi( fun TodoEditContent(component: TodoEdit) {
state: State, val model by component.models.asState()
output: Consumer<Output>,
intents: (Intent) -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
TopAppBar( TopAppBar(
title = { Text("Edit todo") }, title = { Text("Edit todo") },
navigationIcon = { navigationIcon = {
IconButton(onClick = { output.onNext(Output.Finished) }) { IconButton(onClick = component::onCloseClicked) {
Icon(Icons.Default.ArrowBack) Icon(Icons.Default.ArrowBack)
} }
} }
) )
TextField( TextField(
value = state.text, value = model.text,
modifier = Modifier.weight(1F).fillMaxWidth().padding(8.dp), modifier = Modifier.weight(1F).fillMaxWidth().padding(8.dp),
label = { Text("Todo text") }, label = { Text("Todo text") },
onValueChange = { intents(Intent.SetText(text = it)) } onValueChange = component::onTextChanged
) )
Row(modifier = Modifier.padding(8.dp)) { Row(modifier = Modifier.padding(8.dp)) {
@ -52,8 +49,8 @@ internal fun TodoEditUi(
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Checkbox( Checkbox(
checked = state.isDone, checked = model.isDone,
onCheckedChange = { intents(Intent.SetDone(isDone = it)) } onCheckedChange = component::onDoneChanged
) )
} }
} }

37
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/ui/TodoMainUi.kt → examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/TodoMainUi.kt

@ -1,4 +1,4 @@
package example.todo.common.main.ui package example.todo.common.ui
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
@ -28,38 +29,30 @@ import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.badoo.reaktive.base.Consumer import com.arkivanov.decompose.extensions.compose.jetbrains.asState
import example.todo.common.main.TodoMain.Output import example.todo.common.main.TodoItem
import example.todo.common.main.store.TodoItem import example.todo.common.main.TodoMain
import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State
import example.todo.common.utils.compose.MARGIN_SCROLLBAR
import example.todo.common.utils.compose.VerticalScrollbar
import example.todo.common.utils.compose.rememberScrollbarAdapter
import example.todo.common.utils.onKeyUp
@Composable @Composable
internal fun TodoMainUi( fun TodoMainContent(component: TodoMain) {
state: State, val model by component.models.asState()
output: Consumer<Output>,
intents: (Intent) -> Unit
) {
Column { Column {
TopAppBar(title = { Text(text = "Todo List") }) TopAppBar(title = { Text(text = "Todo List") })
Box(Modifier.weight(1F)) { Box(Modifier.weight(1F)) {
TodoList( TodoList(
items = state.items, items = model.items,
onItemClicked = { output.onNext(Output.Selected(id = it)) }, onItemClicked = component::onItemClicked,
onDoneChanged = { id, isDone -> intents(Intent.SetItemDone(id = id, isDone = isDone)) }, onDoneChanged = component::onItemDoneChanged,
onDeleteItemClicked = { intents(Intent.DeleteItem(id = it)) } onDeleteItemClicked = component::onItemDeleteClicked
) )
} }
TodoInput( TodoInput(
text = state.text, text = model.text,
onAddClicked = { intents(Intent.AddItem) }, onAddClicked = component::onAddItemClicked,
onTextChanged = { intents(Intent.SetText(text = it)) } onTextChanged = component::onInputTextChanged
) )
} }
} }

16
examples/todoapp/common/compose-ui/src/commonMain/kotlin/example/todo/common/ui/TodoRootUi.kt

@ -0,0 +1,16 @@
package example.todo.common.ui
import androidx.compose.runtime.Composable
import com.arkivanov.decompose.extensions.compose.jetbrains.Children
import example.todo.common.root.TodoRoot
import example.todo.common.root.TodoRoot.Child
@Composable
fun TodoRootContent(component: TodoRoot) {
Children(routerState = component.routerState, animation = crossfade()) { child, _ ->
when (child) {
is Child.Main -> TodoMainContent(child.component)
is Child.Edit -> TodoEditContent(child.component)
}
}
}

2
examples/todoapp/common/utils/src/desktopMain/kotlin/example/todo/common/utils/compose/Scrollbars.kt → examples/todoapp/common/compose-ui/src/desktopMain/kotlin/example/todo/common/ui/Scrollbars.kt

@ -1,4 +1,4 @@
package example.todo.common.utils.compose package example.todo.common.ui
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState

7
examples/todoapp/common/database/build.gradle.kts

@ -1,5 +1,6 @@
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("android-setup")
id("com.squareup.sqldelight") id("com.squareup.sqldelight")
} }
@ -29,5 +30,11 @@ kotlin {
implementation(Deps.Squareup.SQLDelight.sqliteDriver) implementation(Deps.Squareup.SQLDelight.sqliteDriver)
} }
} }
iosMain {
dependencies {
implementation(Deps.Squareup.SQLDelight.nativeDriver)
}
}
} }
} }

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

@ -34,3 +34,6 @@ WHERE id = :id;
getLastInsertId: getLastInsertId:
SELECT last_insert_rowid(); SELECT last_insert_rowid();
clear:
DELETE FROM TodoItemEntity;

26
examples/todoapp/common/database/src/iosMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt

@ -0,0 +1,26 @@
package example.todo.common.database
import co.touchlab.sqliter.DatabaseConfiguration
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
import com.squareup.sqldelight.drivers.native.wrapConnection
import example.todo.database.TodoDatabase
@Suppress("FunctionName") // Factory function
actual fun TestDatabaseDriver(): SqlDriver {
val schema = TodoDatabase.Schema
return NativeSqliteDriver(
DatabaseConfiguration(
name = ":memory:",
version = schema.version,
create = { wrapConnection(it, schema::create) },
upgrade = { connection, oldVersion, newVersion ->
wrapConnection(connection) {
schema.migrate(it, oldVersion, newVersion)
}
},
inMemory = true
)
)
}

9
examples/todoapp/common/database/src/iosMain/kotlin/example/todo/common/database/TodoDatabaseDriverFactory.kt

@ -0,0 +1,9 @@
package example.todo.common.database
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
import example.todo.database.TodoDatabase
@Suppress("FunctionName") // Factory function
fun TodoDatabaseDriver(): SqlDriver =
NativeSqliteDriver(TodoDatabase.Schema, "TodoDatabase.db")

2
examples/todoapp/common/edit/build.gradle.kts

@ -1,6 +1,6 @@
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("multiplatform-compose-setup") id("android-setup")
} }
kotlin { kotlin {

17
examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/TodoEdit.kt

@ -1,14 +1,27 @@
package example.todo.common.edit package example.todo.common.edit
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.badoo.reaktive.base.Consumer import com.badoo.reaktive.base.Consumer
import example.todo.common.edit.TodoEdit.Dependencies import example.todo.common.edit.TodoEdit.Dependencies
import example.todo.common.edit.integration.TodoEditImpl import example.todo.common.edit.integration.TodoEditImpl
import example.todo.common.utils.Component
import example.todo.database.TodoDatabase import example.todo.database.TodoDatabase
interface TodoEdit : Component { interface TodoEdit {
val models: Value<Model>
fun onTextChanged(text: String)
fun onDoneChanged(isDone: Boolean)
fun onCloseClicked()
data class Model(
val text: String,
val isDone: Boolean
)
interface Dependencies { interface Dependencies {
val storeFactory: StoreFactory val storeFactory: StoreFactory

2
examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/TodoItem.kt

@ -1,6 +1,6 @@
package example.todo.common.edit package example.todo.common.edit
data class TodoItem( internal data class TodoItem(
val text: String, val text: String,
val isDone: Boolean val isDone: Boolean
) )

12
examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/integration/Mappers.kt

@ -0,0 +1,12 @@
package example.todo.common.edit.integration
import example.todo.common.edit.TodoEdit.Model
import example.todo.common.edit.store.TodoEditStore.State
internal val stateToModel: (State) -> Model =
{
Model(
text = it.text,
isDone = it.isDone
)
}

29
examples/todoapp/common/edit/src/commonMain/kotlin/example/todo/common/edit/integration/TodoEditImpl.kt

@ -1,13 +1,15 @@
package example.todo.common.edit.integration package example.todo.common.edit.integration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.operator.map
import example.todo.common.edit.TodoEdit import example.todo.common.edit.TodoEdit
import example.todo.common.edit.TodoEdit.Dependencies import example.todo.common.edit.TodoEdit.Dependencies
import example.todo.common.edit.TodoEdit.Model
import example.todo.common.edit.TodoEdit.Output
import example.todo.common.edit.store.TodoEditStore.Intent
import example.todo.common.edit.store.TodoEditStoreProvider import example.todo.common.edit.store.TodoEditStoreProvider
import example.todo.common.edit.ui.TodoEditUi import example.todo.common.utils.asValue
import example.todo.common.utils.composeState
import example.todo.common.utils.getStore import example.todo.common.utils.getStore
internal class TodoEditImpl( internal class TodoEditImpl(
@ -24,14 +26,17 @@ internal class TodoEditImpl(
).provide() ).provide()
} }
@Composable override val models: Value<Model> = store.asValue().map(stateToModel)
override fun invoke() {
val state by store.composeState
TodoEditUi( override fun onTextChanged(text: String) {
state = state, store.accept(Intent.SetText(text = text))
output = editOutput, }
intents = store::accept
) override fun onDoneChanged(isDone: Boolean) {
store.accept(Intent.SetDone(isDone = isDone))
}
override fun onCloseClicked() {
editOutput.onNext(Output.Finished)
} }
} }

8
examples/todoapp/common/main/build.gradle.kts

@ -1,6 +1,8 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("multiplatform-compose-setup") id("android-setup")
} }
kotlin { kotlin {
@ -24,4 +26,8 @@ kotlin {
} }
} }
} }
targets.getByName<KotlinNativeTarget>("iosX64").compilations.forEach {
it.kotlinOptions.freeCompilerArgs += arrayOf("-linker-options", "-lsqlite3")
}
} }

4
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/store/TodoItem.kt → examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/TodoItem.kt

@ -1,6 +1,6 @@
package example.todo.common.main.store package example.todo.common.main
internal data class TodoItem( data class TodoItem(
val id: Long = 0L, val id: Long = 0L,
val order: Long = 0L, val order: Long = 0L,
val text: String = "", val text: String = "",

21
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/TodoMain.kt

@ -1,14 +1,31 @@
package example.todo.common.main package example.todo.common.main
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.badoo.reaktive.base.Consumer import com.badoo.reaktive.base.Consumer
import example.todo.common.main.TodoMain.Dependencies import example.todo.common.main.TodoMain.Dependencies
import example.todo.common.main.integration.TodoMainImpl import example.todo.common.main.integration.TodoMainImpl
import example.todo.common.utils.Component
import example.todo.database.TodoDatabase import example.todo.database.TodoDatabase
interface TodoMain : Component { interface TodoMain {
val models: Value<Model>
fun onItemClicked(id: Long)
fun onItemDoneChanged(id: Long, isDone: Boolean)
fun onItemDeleteClicked(id: Long)
fun onInputTextChanged(text: String)
fun onAddItemClicked()
data class Model(
val items: List<TodoItem>,
val text: String
)
interface Dependencies { interface Dependencies {
val storeFactory: StoreFactory val storeFactory: StoreFactory

12
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/Mappers.kt

@ -0,0 +1,12 @@
package example.todo.common.main.integration
import example.todo.common.main.TodoMain.Model
import example.todo.common.main.store.TodoMainStore.State
internal val stateToModel: (State) -> Model =
{
Model(
items = it.items,
text = it.text
)
}

36
examples/todoapp/common/main/src/commonMain/kotlin/example/todo/common/main/integration/TodoMainImpl.kt

@ -1,16 +1,15 @@
package example.todo.common.main.integration package example.todo.common.main.integration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.operator.map
import example.todo.common.main.TodoMain import example.todo.common.main.TodoMain
import example.todo.common.main.TodoMain.Dependencies import example.todo.common.main.TodoMain.Dependencies
import example.todo.common.main.TodoMain.Model
import example.todo.common.main.TodoMain.Output import example.todo.common.main.TodoMain.Output
import example.todo.common.main.store.TodoMainStore.Intent import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State
import example.todo.common.main.store.TodoMainStoreProvider import example.todo.common.main.store.TodoMainStoreProvider
import example.todo.common.main.ui.TodoMainUi import example.todo.common.utils.asValue
import example.todo.common.utils.composeState
import example.todo.common.utils.getStore import example.todo.common.utils.getStore
internal class TodoMainImpl( internal class TodoMainImpl(
@ -26,24 +25,25 @@ internal class TodoMainImpl(
).provide() ).provide()
} }
internal val state: State get() = store.state override val models: Value<Model> = store.asValue().map(stateToModel)
@Composable override fun onItemClicked(id: Long) {
override fun invoke() { mainOutput.onNext(Output.Selected(id = id))
val state by store.composeState }
override fun onItemDoneChanged(id: Long, isDone: Boolean) {
store.accept(Intent.SetItemDone(id = id, isDone = isDone))
}
TodoMainUi( override fun onItemDeleteClicked(id: Long) {
state = state, store.accept(Intent.DeleteItem(id = id))
output = mainOutput,
intents = store::accept
)
} }
internal fun onIntent(intent: Intent) { override fun onInputTextChanged(text: String) {
store.accept(intent) store.accept(Intent.SetText(text = text))
} }
internal fun onOutput(output: Output) { override fun onAddItemClicked() {
mainOutput.onNext(output) store.accept(Intent.AddItem)
} }
} }

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

@ -10,7 +10,7 @@ import com.squareup.sqldelight.Query
import example.todo.common.database.TodoDatabaseQueries import example.todo.common.database.TodoDatabaseQueries
import example.todo.common.database.TodoItemEntity import example.todo.common.database.TodoItemEntity
import example.todo.common.database.asObservable import example.todo.common.database.asObservable
import example.todo.common.main.store.TodoItem import example.todo.common.main.TodoItem
import example.todo.common.main.store.TodoMainStoreProvider import example.todo.common.main.store.TodoMainStoreProvider
internal class TodoMainStoreDatabase( internal class TodoMainStoreDatabase(
@ -40,11 +40,6 @@ internal class TodoMainStoreDatabase(
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
override fun add(text: String): Completable = override fun add(text: String): Completable =
completableFromFunction { completableFromFunction { queries.add(text = text) }
queries.transactionWithResult { .subscribeOn(ioScheduler)
queries.add(text = text)
val lastId = queries.getLastInsertId().executeAsOne()
queries.select(id = lastId).executeAsOne()
}
}.subscribeOn(ioScheduler)
} }

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

@ -1,6 +1,7 @@
package example.todo.common.main.store package example.todo.common.main.store
import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.Store
import example.todo.common.main.TodoItem
import example.todo.common.main.store.TodoMainStore.Intent import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State import example.todo.common.main.store.TodoMainStore.State

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

@ -10,6 +10,7 @@ import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.map import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.observeOn import com.badoo.reaktive.observable.observeOn
import com.badoo.reaktive.scheduler.mainScheduler import com.badoo.reaktive.scheduler.mainScheduler
import example.todo.common.main.TodoItem
import example.todo.common.main.store.TodoMainStore.Intent import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.common.main.store.TodoMainStore.State import example.todo.common.main.store.TodoMainStore.State

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

@ -12,10 +12,10 @@ import com.badoo.reaktive.test.observable.test
import com.badoo.reaktive.test.scheduler.TestScheduler import com.badoo.reaktive.test.scheduler.TestScheduler
import example.todo.common.database.TestDatabaseDriver import example.todo.common.database.TestDatabaseDriver
import example.todo.common.database.TodoItemEntity import example.todo.common.database.TodoItemEntity
import example.todo.common.main.TodoItem
import example.todo.common.main.TodoMain.Dependencies import example.todo.common.main.TodoMain.Dependencies
import example.todo.common.main.TodoMain.Model
import example.todo.common.main.TodoMain.Output import example.todo.common.main.TodoMain.Output
import example.todo.common.main.store.TodoItem
import example.todo.common.main.store.TodoMainStore.Intent
import example.todo.database.TodoDatabase import example.todo.database.TodoDatabase
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
@ -45,12 +45,16 @@ class TodoMainTest {
) )
} }
private val model: Model get() = impl.models.value
@BeforeTest @BeforeTest
fun before() { fun before() {
overrideSchedulers( overrideSchedulers(
main = { TestScheduler() }, main = { TestScheduler() },
io = { TestScheduler() } io = { TestScheduler() }
) )
queries.clear()
} }
@Test @Test
@ -67,15 +71,15 @@ class TodoMainTest {
queries.delete(id = id) queries.delete(id = id)
assertFalse(impl.state.items.any { it.id == id }) assertFalse(model.items.any { it.id == id })
} }
@Test @Test
fun WHEN_item_selected_THEN_Output_Selected_emitted() { fun WHEN_item_clicked_THEN_Output_Selected_emitted() {
queries.add("Item1") queries.add("Item1")
val id = firstItem().id val id = firstItem().id
impl.onOutput(Output.Selected(id = id)) impl.onItemClicked(id = id)
output.assertValue(Output.Selected(id = id)) output.assertValue(Output.Selected(id = id))
} }
@ -86,7 +90,7 @@ class TodoMainTest {
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.onItemDoneChanged(id = id, isDone = true)
assertTrue(queries.select(id = id).executeAsOne().isDone) assertTrue(queries.select(id = id).executeAsOne().isDone)
} }
@ -97,17 +101,17 @@ class TodoMainTest {
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.onItemDoneChanged(id = id, isDone = false)
assertFalse(queries.select(id = id).executeAsOne().isDone) assertFalse(queries.select(id = id).executeAsOne().isDone)
} }
@Test @Test
fun WHEN_delete_clicked_THEN_item_deleted_in_database() { fun WHEN_item_delete_clicked_THEN_item_deleted_in_database() {
queries.add("Item1") queries.add("Item1")
val id = firstItem().id val id = firstItem().id
impl.onIntent(Intent.DeleteItem(id = id)) impl.onItemDeleteClicked(id = id)
assertNull(queries.select(id = id).executeAsOneOrNull()) assertNull(queries.select(id = id).executeAsOneOrNull())
} }
@ -123,25 +127,25 @@ class TodoMainTest {
} }
@Test @Test
fun WHEN_text_changed_THEN_text_updated() { fun WHEN_input_text_changed_THEN_text_updated() {
impl.onIntent(Intent.SetText(text = "Item text")) impl.onInputTextChanged(text = "Item text")
assertEquals("Item text", impl.state.text) assertEquals("Item text", model.text)
} }
@Test @Test
fun GIVEN_text_entered_WHEN_add_clicked_THEN_item_added_in_database() { fun GIVEN_input_text_entered_WHEN_add_item_clicked_THEN_item_added_in_database() {
impl.onIntent(Intent.SetText(text = "Item text")) impl.onInputTextChanged(text = "Item text")
impl.onIntent(Intent.AddItem) impl.onAddItemClicked()
assertEquals("Item text", lastInsertItem().text) assertEquals("Item text", lastInsertItem().text)
} }
private fun firstItem(): TodoItem = impl.state.items[0] private fun firstItem(): TodoItem = model.items[0]
private fun lastInsertItem(): TodoItemEntity { private fun lastInsertItem(): TodoItemEntity {
val lastInsertId = queries.getLastInsertId().executeAsOne() val lastInsertId = queries.transactionWithResult<Long> { queries.getLastInsertId().executeAsOne() }
return queries.select(id = lastInsertId).executeAsOne() return queries.select(id = lastInsertId).executeAsOne()
} }

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

@ -4,6 +4,7 @@ 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.TodoItem
internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database { internal class TestTodoMainStoreDatabase : TodoMainStoreProvider.Database {

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

@ -3,6 +3,7 @@ package example.todo.common.main.store
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.badoo.reaktive.scheduler.overrideSchedulers import com.badoo.reaktive.scheduler.overrideSchedulers
import com.badoo.reaktive.test.scheduler.TestScheduler import com.badoo.reaktive.test.scheduler.TestScheduler
import example.todo.common.main.TodoItem
import example.todo.common.main.store.TodoMainStore.Intent import example.todo.common.main.store.TodoMainStore.Intent
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test

78
examples/todoapp/common/root/build.gradle.kts

@ -1,14 +1,38 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("multiplatform-compose-setup") id("android-setup")
id("kotlin-android-extensions") id("kotlin-parcelize")
} }
androidExtensions { kotlin {
features = setOf("parcelize") ios {
binaries {
framework {
baseName = "Todo"
linkerOpts.add("-lsqlite3")
export(project(":common:database"))
export(project(":common:main"))
export(project(":common:edit"))
when (val target = this.compilation.target.name) {
"iosX64" -> {
export(Deps.ArkIvanov.Decompose.decomposeIosX64)
export(Deps.ArkIvanov.MVIKotlin.mvikotlinMainIosX64)
}
"iosArm64" -> {
export(Deps.ArkIvanov.Decompose.decomposeIosArm64)
export(Deps.ArkIvanov.MVIKotlin.mvikotlinMainIosArm64)
}
else -> error("Unsupported target: $target")
}
}
}
} }
kotlin {
sourceSets { sourceSets {
named("commonMain") { named("commonMain") {
dependencies { dependencies {
@ -16,10 +40,52 @@ kotlin {
implementation(project(":common:database")) implementation(project(":common:database"))
implementation(project(":common:main")) implementation(project(":common:main"))
implementation(project(":common:edit")) implementation(project(":common:edit"))
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.Decompose.decompose) implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.ArkIvanov.Decompose.extensionsCompose)
implementation(Deps.Badoo.Reaktive.reaktive) implementation(Deps.Badoo.Reaktive.reaktive)
} }
} }
} }
sourceSets {
named("iosMain") {
dependencies {
api(project(":common:database"))
api(project(":common:main"))
api(project(":common:edit"))
}
}
named("iosX64Main") {
dependencies {
api(Deps.ArkIvanov.Decompose.decomposeIosX64)
api(Deps.ArkIvanov.MVIKotlin.mvikotlinMainIosX64)
}
}
named("iosArm64Main") {
dependencies {
api(Deps.ArkIvanov.Decompose.decomposeIosArm64)
api(Deps.ArkIvanov.MVIKotlin.mvikotlinMainIosArm64)
}
}
}
}
fun getIosTarget(): String {
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
return if (sdkName.startsWith("iphoneos")) "iosArm64" else "iosX64"
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val targetName = getIosTarget()
val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from(framework.outputDirectory)
into(targetDir)
} }

14
examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/TodoRoot.kt

@ -1,13 +1,23 @@
package example.todo.common.root package example.todo.common.root
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.RouterState
import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import example.todo.common.edit.TodoEdit
import example.todo.common.main.TodoMain
import example.todo.common.root.TodoRoot.Dependencies import example.todo.common.root.TodoRoot.Dependencies
import example.todo.common.root.integration.TodoRootImpl import example.todo.common.root.integration.TodoRootImpl
import example.todo.common.utils.Component
import example.todo.database.TodoDatabase import example.todo.database.TodoDatabase
interface TodoRoot : Component { interface TodoRoot {
val routerState: Value<RouterState<*, Child>>
sealed class Child {
data class Main(val component: TodoMain) : Child()
data class Edit(val component: TodoEdit) : Child()
}
interface Dependencies { interface Dependencies {
val storeFactory: StoreFactory val storeFactory: StoreFactory

26
examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/integration/TodoRootImpl.kt

@ -1,19 +1,18 @@
package example.todo.common.root.integration package example.todo.common.root.integration
import androidx.compose.runtime.Composable
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.jetbrains.children import com.arkivanov.decompose.RouterState
import com.arkivanov.decompose.router import com.arkivanov.decompose.router
import com.arkivanov.decompose.statekeeper.Parcelable import com.arkivanov.decompose.statekeeper.Parcelable
import com.arkivanov.decompose.statekeeper.Parcelize import com.arkivanov.decompose.statekeeper.Parcelize
import com.arkivanov.decompose.value.Value
import com.badoo.reaktive.base.Consumer import com.badoo.reaktive.base.Consumer
import example.todo.common.edit.TodoEdit import example.todo.common.edit.TodoEdit
import example.todo.common.main.TodoMain import example.todo.common.main.TodoMain
import example.todo.common.root.TodoRoot import example.todo.common.root.TodoRoot
import example.todo.common.root.TodoRoot.Child
import example.todo.common.root.TodoRoot.Dependencies import example.todo.common.root.TodoRoot.Dependencies
import example.todo.common.utils.Component
import example.todo.common.utils.Consumer import example.todo.common.utils.Consumer
import example.todo.common.utils.Crossfade
internal class TodoRootImpl( internal class TodoRootImpl(
componentContext: ComponentContext, componentContext: ComponentContext,
@ -21,16 +20,18 @@ internal class TodoRootImpl(
) : TodoRoot, ComponentContext by componentContext, Dependencies by dependencies { ) : TodoRoot, ComponentContext by componentContext, Dependencies by dependencies {
private val router = private val router =
router<Configuration, Component>( router<Configuration, Child>(
initialConfiguration = Configuration.Main, initialConfiguration = Configuration.Main,
handleBackButton = true, handleBackButton = true,
componentFactory = ::createChild componentFactory = ::createChild
) )
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Component = override val routerState: Value<RouterState<*, Child>> = router.state
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child =
when (configuration) { when (configuration) {
is Configuration.Main -> todoMain(componentContext) is Configuration.Main -> Child.Main(todoMain(componentContext))
is Configuration.Edit -> todoEdit(componentContext, itemId = configuration.itemId) is Configuration.Edit -> Child.Edit(todoEdit(componentContext, itemId = configuration.itemId))
} }
private fun todoMain(componentContext: ComponentContext): TodoMain = private fun todoMain(componentContext: ComponentContext): TodoMain =
@ -60,15 +61,6 @@ internal class TodoRootImpl(
is TodoEdit.Output.Finished -> router.pop() is TodoEdit.Output.Finished -> router.pop()
} }
@Composable
override fun invoke() {
router.state.children { child, configuration ->
Crossfade(currentChild = child, currentKey = configuration) { currentChild ->
currentChild()
}
}
}
private sealed class Configuration : Parcelable { private sealed class Configuration : Parcelable {
@Parcelize @Parcelize
object Main : Configuration() object Main : Configuration()

6
examples/todoapp/common/utils/build.gradle.kts

@ -1,16 +1,16 @@
plugins { plugins {
id("multiplatform-setup") id("multiplatform-setup")
id("multiplatform-compose-setup") id("android-setup")
} }
kotlin { kotlin {
sourceSets { sourceSets {
named("commonMain") { named("commonMain") {
dependencies { dependencies {
implementation(Deps.ArkIvanov.MVIKotlin.rx)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive)
implementation(Deps.ArkIvanov.Decompose.decompose) implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.ArkIvanov.Decompose.extensionsCompose) implementation(Deps.Badoo.Reaktive.reaktive)
} }
} }
} }

24
examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/Binder.kt

@ -1,24 +0,0 @@
package example.todo.common.utils
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.lifecycle.Lifecycle
import com.arkivanov.decompose.lifecycle.subscribe
import com.arkivanov.mvikotlin.core.binder.Binder
import com.arkivanov.mvikotlin.core.binder.BinderLifecycleMode
import com.arkivanov.mvikotlin.extensions.reaktive.BindingsBuilder
import com.arkivanov.mvikotlin.extensions.reaktive.bind
fun bind(lifecycle: Lifecycle, mode: BinderLifecycleMode, builder: BindingsBuilder.() -> Unit): Binder {
val binder = bind(builder)
when (mode) {
BinderLifecycleMode.CREATE_DESTROY -> lifecycle.subscribe(onCreate = { binder.start() }, onDestroy = { binder.stop() })
BinderLifecycleMode.START_STOP -> lifecycle.subscribe(onStart = { binder.start() }, onStop = { binder.stop() })
BinderLifecycleMode.RESUME_PAUSE -> lifecycle.subscribe(onResume = { binder.start() }, onPause = { binder.stop() })
}.let {}
return binder
}
fun ComponentContext.bind(mode: BinderLifecycleMode, builder: BindingsBuilder.() -> Unit): Binder =
bind(lifecycle, mode, builder)

9
examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/Component.kt

@ -1,9 +0,0 @@
package example.todo.common.utils
import androidx.compose.runtime.Composable
interface Component {
@Composable
operator fun invoke()
}

16
examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/CrossfadeExt.kt

@ -1,16 +0,0 @@
package example.todo.common.utils
import androidx.compose.animation.Crossfade
import androidx.compose.runtime.Composable
@Composable
fun <T> Crossfade(currentChild: T, currentKey: Any, children: @Composable() (T) -> Unit) {
Crossfade(current = ChildWrapper(currentChild, currentKey)) {
children(it.child)
}
}
private class ChildWrapper<out T>(val child: T, val key: Any) {
override fun equals(other: Any?): Boolean = key == (other as? ChildWrapper<*>)?.key
override fun hashCode(): Int = key.hashCode()
}

31
examples/todoapp/common/utils/src/commonMain/kotlin/example/todo/common/utils/StoreExt.kt

@ -1,20 +1,23 @@
package example.todo.common.utils package example.todo.common.utils
import androidx.compose.runtime.Composable import com.arkivanov.decompose.value.Value
import androidx.compose.runtime.State import com.arkivanov.decompose.value.ValueObserver
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.onDispose
import androidx.compose.runtime.remember
import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.extensions.reaktive.states import com.arkivanov.mvikotlin.rx.Disposable
import com.badoo.reaktive.observable.subscribe
@Composable fun <T : Any> Store<*, T, *>.asValue(): Value<T> =
val <T : Any> Store<*, T, *>.composeState: State<T> object : Value<T>() {
get() { override val value: T get() = state
val composeState = remember(this) { mutableStateOf(state) } private var disposables = emptyMap<ValueObserver<T>, Disposable>()
val disposable = remember(this) { states.subscribe(onNext = { composeState.value = it }) }
onDispose(disposable::dispose)
return composeState override fun subscribe(observer: ValueObserver<T>) {
val disposable = states(com.arkivanov.mvikotlin.rx.observer(onNext = observer))
this.disposables += observer to disposable
}
override fun unsubscribe(observer: ValueObserver<T>) {
val disposable = disposables[observer] ?: return
this.disposables -= observer
disposable.dispose()
}
} }

1
examples/todoapp/desktop/build.gradle.kts

@ -18,6 +18,7 @@ kotlin {
implementation(project(":common:utils")) implementation(project(":common:utils"))
implementation(project(":common:database")) implementation(project(":common:database"))
implementation(project(":common:root")) implementation(project(":common:root"))
implementation(project(":common:compose-ui"))
implementation(Deps.ArkIvanov.Decompose.decompose) implementation(Deps.ArkIvanov.Decompose.decompose)
implementation(Deps.ArkIvanov.Decompose.extensionsCompose) implementation(Deps.ArkIvanov.Decompose.extensionsCompose)
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin)

3
examples/todoapp/desktop/src/jvmMain/kotlin/example/todo/desktop/Main.kt

@ -15,6 +15,7 @@ import com.badoo.reaktive.coroutinesinterop.asScheduler
import com.badoo.reaktive.scheduler.overrideSchedulers import com.badoo.reaktive.scheduler.overrideSchedulers
import example.todo.common.database.TodoDatabaseDriver import example.todo.common.database.TodoDatabaseDriver
import example.todo.common.root.TodoRoot import example.todo.common.root.TodoRoot
import example.todo.common.ui.TodoRootContent
import example.todo.database.TodoDatabase import example.todo.database.TodoDatabase
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -28,7 +29,7 @@ fun main() {
Surface(modifier = Modifier.fillMaxSize()) { Surface(modifier = Modifier.fillMaxSize()) {
MaterialTheme { MaterialTheme {
DesktopTheme { DesktopTheme {
rootComponent(factory = ::todoRoot).invoke() TodoRootContent(rootComponent(factory = ::todoRoot))
} }
} }
} }

1
examples/todoapp/gradle.properties

@ -21,3 +21,4 @@ android.enableJetifier=true
kotlin.code.style=official kotlin.code.style=official
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
kotlin.native.disableCompilerDaemon=true

18
examples/todoapp/ios/.gitignore vendored

@ -0,0 +1,18 @@
DerivedData/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
*.moved-aside
*.xccheckout
*.xcscmblueprint
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
Pods

418
examples/todoapp/ios/TodoApp.xcodeproj/project.pbxproj

@ -0,0 +1,418 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1F00F38D257599D800CFAF97 /* TodoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F38C257599D800CFAF97 /* TodoApp.swift */; };
1F00F38F257599D800CFAF97 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F38E257599D800CFAF97 /* ContentView.swift */; };
1F00F391257599DA00CFAF97 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F00F390257599DA00CFAF97 /* Assets.xcassets */; };
1F00F394257599DA00CFAF97 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F00F393257599DA00CFAF97 /* Preview Assets.xcassets */; };
1F00F3A425759FEC00CFAF97 /* Todo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F00F3A325759FEC00CFAF97 /* Todo.framework */; };
1F00F3A525759FEC00CFAF97 /* Todo.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1F00F3A325759FEC00CFAF97 /* Todo.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1F00F3A82575A16400CFAF97 /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3A72575A16400CFAF97 /* ObservableValue.swift */; };
1F00F3AA2575A71000CFAF97 /* MutableStateBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3A92575A71000CFAF97 /* MutableStateBuilder.swift */; };
1F00F3AC2575AA4500CFAF97 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3AB2575AA4500CFAF97 /* ListView.swift */; };
1F00F3AE2575AC6A00CFAF97 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3AD2575AC6A00CFAF97 /* InputView.swift */; };
1F00F3B02575ADB500CFAF97 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3AF2575ADB500CFAF97 /* MainView.swift */; };
1F00F3B22575B07700CFAF97 /* EditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3B12575B07700CFAF97 /* EditView.swift */; };
1F00F3B42575B18200CFAF97 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3B32575B18200CFAF97 /* RootView.swift */; };
1F00F3B62575B41900CFAF97 /* SimpleRouterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3B52575B41900CFAF97 /* SimpleRouterState.swift */; };
1F00F3B82575B4F800CFAF97 /* ComponentHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F00F3B72575B4F800CFAF97 /* ComponentHolder.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
1F00F3A625759FEC00CFAF97 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
1F00F3A525759FEC00CFAF97 /* Todo.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1F00F389257599D800CFAF97 /* TodoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TodoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
1F00F38C257599D800CFAF97 /* TodoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoApp.swift; sourceTree = "<group>"; };
1F00F38E257599D800CFAF97 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
1F00F390257599DA00CFAF97 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1F00F393257599DA00CFAF97 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
1F00F395257599DA00CFAF97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1F00F3A325759FEC00CFAF97 /* Todo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Todo.framework; path = "../common/root/build/xcode-frameworks/Todo.framework"; sourceTree = "<group>"; };
1F00F3A72575A16400CFAF97 /* ObservableValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableValue.swift; sourceTree = "<group>"; };
1F00F3A92575A71000CFAF97 /* MutableStateBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableStateBuilder.swift; sourceTree = "<group>"; };
1F00F3AB2575AA4500CFAF97 /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = "<group>"; };
1F00F3AD2575AC6A00CFAF97 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = "<group>"; };
1F00F3AF2575ADB500CFAF97 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
1F00F3B12575B07700CFAF97 /* EditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditView.swift; sourceTree = "<group>"; };
1F00F3B32575B18200CFAF97 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
1F00F3B52575B41900CFAF97 /* SimpleRouterState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleRouterState.swift; sourceTree = "<group>"; };
1F00F3B72575B4F800CFAF97 /* ComponentHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentHolder.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1F00F386257599D800CFAF97 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1F00F3A425759FEC00CFAF97 /* Todo.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1F00F380257599D800CFAF97 = {
isa = PBXGroup;
children = (
1F00F38B257599D800CFAF97 /* ios */,
1F00F38A257599D800CFAF97 /* Products */,
1F00F3A225759FEC00CFAF97 /* Frameworks */,
);
sourceTree = "<group>";
};
1F00F38A257599D800CFAF97 /* Products */ = {
isa = PBXGroup;
children = (
1F00F389257599D800CFAF97 /* TodoApp.app */,
);
name = Products;
sourceTree = "<group>";
};
1F00F38B257599D800CFAF97 /* ios */ = {
isa = PBXGroup;
children = (
1F00F38C257599D800CFAF97 /* TodoApp.swift */,
1F00F38E257599D800CFAF97 /* ContentView.swift */,
1F00F390257599DA00CFAF97 /* Assets.xcassets */,
1F00F395257599DA00CFAF97 /* Info.plist */,
1F00F392257599DA00CFAF97 /* Preview Content */,
1F00F3A72575A16400CFAF97 /* ObservableValue.swift */,
1F00F3A92575A71000CFAF97 /* MutableStateBuilder.swift */,
1F00F3AB2575AA4500CFAF97 /* ListView.swift */,
1F00F3AD2575AC6A00CFAF97 /* InputView.swift */,
1F00F3AF2575ADB500CFAF97 /* MainView.swift */,
1F00F3B12575B07700CFAF97 /* EditView.swift */,
1F00F3B32575B18200CFAF97 /* RootView.swift */,
1F00F3B52575B41900CFAF97 /* SimpleRouterState.swift */,
1F00F3B72575B4F800CFAF97 /* ComponentHolder.swift */,
);
path = ios;
sourceTree = "<group>";
};
1F00F392257599DA00CFAF97 /* Preview Content */ = {
isa = PBXGroup;
children = (
1F00F393257599DA00CFAF97 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
1F00F3A225759FEC00CFAF97 /* Frameworks */ = {
isa = PBXGroup;
children = (
1F00F3A325759FEC00CFAF97 /* Todo.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1F00F388257599D800CFAF97 /* TodoApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1F00F398257599DA00CFAF97 /* Build configuration list for PBXNativeTarget "TodoApp" */;
buildPhases = (
1F00F39D25759BB300CFAF97 /* ShellScript */,
1F00F385257599D800CFAF97 /* Sources */,
1F00F386257599D800CFAF97 /* Frameworks */,
1F00F387257599D800CFAF97 /* Resources */,
1F00F3A625759FEC00CFAF97 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = TodoApp;
productName = ios;
productReference = 1F00F389257599D800CFAF97 /* TodoApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
1F00F381257599D800CFAF97 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1220;
LastUpgradeCheck = 1220;
TargetAttributes = {
1F00F388257599D800CFAF97 = {
CreatedOnToolsVersion = 12.2;
};
};
};
buildConfigurationList = 1F00F384257599D800CFAF97 /* Build configuration list for PBXProject "TodoApp" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 1F00F380257599D800CFAF97;
productRefGroup = 1F00F38A257599D800CFAF97 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1F00F388257599D800CFAF97 /* TodoApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1F00F387257599D800CFAF97 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1F00F394257599DA00CFAF97 /* Preview Assets.xcassets in Resources */,
1F00F391257599DA00CFAF97 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
1F00F39D25759BB300CFAF97 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd $SRCROOT/..\n./gradlew :common:root:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\ncd $SRCROOT\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1F00F385257599D800CFAF97 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1F00F38F257599D800CFAF97 /* ContentView.swift in Sources */,
1F00F3B02575ADB500CFAF97 /* MainView.swift in Sources */,
1F00F3B22575B07700CFAF97 /* EditView.swift in Sources */,
1F00F38D257599D800CFAF97 /* TodoApp.swift in Sources */,
1F00F3AC2575AA4500CFAF97 /* ListView.swift in Sources */,
1F00F3B82575B4F800CFAF97 /* ComponentHolder.swift in Sources */,
1F00F3B42575B18200CFAF97 /* RootView.swift in Sources */,
1F00F3B62575B41900CFAF97 /* SimpleRouterState.swift in Sources */,
1F00F3AE2575AC6A00CFAF97 /* InputView.swift in Sources */,
1F00F3AA2575A71000CFAF97 /* MutableStateBuilder.swift in Sources */,
1F00F3A82575A16400CFAF97 /* ObservableValue.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1F00F396257599DA00CFAF97 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
1F00F397257599DA00CFAF97 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
1F00F399257599DA00CFAF97 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"ios/Preview Content\"";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$SRCROOT/../common/root/build/xcode-frameworks";
INFOPLIST_FILE = ios/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jetbrains.todoapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
1F00F39A257599DA00CFAF97 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"ios/Preview Content\"";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$SRCROOT/../common/root/build/xcode-frameworks";
INFOPLIST_FILE = ios/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jetbrains.todoapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1F00F384257599D800CFAF97 /* Build configuration list for PBXProject "TodoApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1F00F396257599DA00CFAF97 /* Debug */,
1F00F397257599DA00CFAF97 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1F00F398257599DA00CFAF97 /* Build configuration list for PBXNativeTarget "TodoApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1F00F399257599DA00CFAF97 /* Debug */,
1F00F39A257599DA00CFAF97 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 1F00F381257599D800CFAF97 /* Project object */;
}

7
examples/todoapp/ios/TodoApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata generated

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:/Users/arkadiiivanov/dev/compose-jb/examples/todoapp/ios/TodoApp.xcodeproj">
</FileRef>
</Workspace>

8
examples/todoapp/ios/TodoApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

11
examples/todoapp/ios/ios/Assets.xcassets/AccentColor.colorset/Contents.json

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

98
examples/todoapp/ios/ios/Assets.xcassets/AppIcon.appiconset/Contents.json

@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

6
examples/todoapp/ios/ios/Assets.xcassets/Contents.json

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

19
examples/todoapp/ios/ios/ComponentHolder.swift

@ -0,0 +1,19 @@
import Todo
class ComponentHolder<T> {
let lifecycle: LifecycleRegistry
let component: T
init(factory: (ComponentContext) -> T) {
let lifecycle = LifecycleRegistryKt.LifecycleRegistry()
let component = factory(DefaultComponentContext(lifecycle: lifecycle))
self.lifecycle = lifecycle
self.component = component
lifecycle.onCreate()
}
deinit {
lifecycle.onDestroy()
}
}

31
examples/todoapp/ios/ios/ContentView.swift

@ -0,0 +1,31 @@
import SwiftUI
import Todo
struct ContentView: View {
@State
private var componentHolder =
ComponentHolder {
TodoRootKt.TodoRoot(
componentContext: $0,
dependencies: RootDependencies()
)
}
var body: some View {
RootView(componentHolder.component)
.onAppear { LifecycleRegistryExtKt.resume(self.componentHolder.lifecycle) }
.onDisappear { LifecycleRegistryExtKt.stop(self.componentHolder.lifecycle) }
}
}
private class RootDependencies: TodoRootDependencies {
let database: TodoDatabase = TodoDatabaseCompanion().invoke(driver: TodoDatabaseDriverFactoryKt.TodoDatabaseDriver())
let storeFactory: MvikotlinStoreFactory = DefaultStoreFactory()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

67
examples/todoapp/ios/ios/EditView.swift

@ -0,0 +1,67 @@
import SwiftUI
import Todo
struct EditView: View {
private let component: TodoEdit
@ObservedObject
private var models: ObservableValue<TodoEditModel>
init(_ component: TodoEdit) {
self.component = component
self.models = ObservableValue(component.models)
}
var body: some View {
let model = models.value
let binding = Binding(get: { model.text }, set: component.onTextChanged)
return NavigationView {
VStack{
TextEditor(text: binding)
.frame(maxHeight: .infinity)
.padding(8)
HStack {
Text("Completed")
Image(systemName: model.isDone ? "checkmark.square" : "square")
.onTapGesture { self.component.onDoneChanged(isDone: !model.isDone) }
}.padding(8)
}
.navigationBarTitle("Edit todo", displayMode: .inline)
.navigationBarItems(
leading: Button(action: { withAnimation { component.onCloseClicked() } } ) {
HStack {
Image(systemName: "chevron.left")
Text("Close")
}
}
)
}
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView(StubTodoEdit())
}
class StubTodoEdit: TodoEdit {
let models: Value<TodoEditModel> =
valueOf(
TodoEditModel(
text: "Text",
isDone: true
)
)
func onCloseClicked() {
}
func onDoneChanged(isDone: Bool) {
}
func onTextChanged(text: String) {
}
}
}

50
examples/todoapp/ios/ios/Info.plist

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

28
examples/todoapp/ios/ios/InputView.swift

@ -0,0 +1,28 @@
import SwiftUI
import Todo
struct InputView: View {
var textBinding: Binding<String>
var onAddClicked: () -> Void
var body: some View {
HStack {
TextField("Todo text", text: self.textBinding)
.textFieldStyle(RoundedBorderTextFieldStyle())
.edgesIgnoringSafeArea(Edge.Set.bottom)
Button(action: self.onAddClicked) {
Image(systemName: "plus")
}.frame(minWidth: 36, minHeight: 36)
}.padding(8)
}
}
struct InputView_Previews: PreviewProvider {
static var previews: some View {
InputView(
textBinding: Binding(get: { "Text" }, set: {_ in }),
onAddClicked: {}
)
}
}

39
examples/todoapp/ios/ios/ListView.swift

@ -0,0 +1,39 @@
import SwiftUI
import Todo
struct ListView: View {
var items: [TodoItem]
var onItemClicked: (_ id: Int64) -> Void
var onDoneChanged: (_ id: Int64, _ isDone: Bool) -> Void
var body: some View {
List(self.items) { item in
HStack {
Text(item.text)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.background(Color.white)
.onTapGesture { withAnimation { self.onItemClicked(item.id) } }
Image(systemName: item.isDone ? "checkmark.square" : "square")
.onTapGesture { self.onDoneChanged(item.id, !item.isDone) }
}
}.listStyle(PlainListStyle())
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView(
items: [
TodoItem(id: 1, order: 1, text: "Item 1", isDone: false),
TodoItem(id: 2, order: 2, text: "Item 2", isDone: true),
TodoItem(id: 3, order: 3, text: "Item 3", isDone: false)
],
onItemClicked: {_ in },
onDoneChanged: {_,_ in }
)
}
}
extension TodoItem : Identifiable {
}

68
examples/todoapp/ios/ios/MainView.swift

@ -0,0 +1,68 @@
import SwiftUI
import Todo
struct MainView: View {
private let component: TodoMain
@ObservedObject
private var models: ObservableValue<TodoMainModel>
init(_ component: TodoMain) {
self.component = component
self.models = ObservableValue(component.models)
}
var body: some View {
let model = models.value
return NavigationView {
VStack {
ListView(
items: model.items,
onItemClicked: component.onItemClicked,
onDoneChanged: component.onItemDoneChanged
)
InputView(
textBinding: Binding(get: { model.text }, set: component.onInputTextChanged),
onAddClicked: component.onAddItemClicked
)
}.navigationBarTitle("Todo Sample", displayMode: .inline)
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView(StubTodoMain())
}
class StubTodoMain: TodoMain {
let models: Value<TodoMainModel> =
valueOf(
TodoMainModel(
items: [
TodoItem(id: 1, order: 1, text: "Item 1", isDone: false),
TodoItem(id: 2, order: 2, text: "Item 2", isDone: true),
TodoItem(id: 3, order: 3, text: "Item 3", isDone: false)
],
text: "Text"
)
)
func onAddItemClicked() {
}
func onInputTextChanged(text: String) {
}
func onItemClicked(id: Int64) {
}
func onItemDeleteClicked(id: Int64) {
}
func onItemDoneChanged(id: Int64, isDone: Bool) {
}
}
}

5
examples/todoapp/ios/ios/MutableStateBuilder.swift

@ -0,0 +1,5 @@
import Todo
func valueOf<T: AnyObject>(_ value: T) -> Value<T> {
return MutableValueBuilderKt.MutableValue(initialValue: value) as! MutableValue<T>
}

22
examples/todoapp/ios/ios/ObservableValue.swift

@ -0,0 +1,22 @@
import Todo
public class ObservableValue<T : AnyObject> : ObservableObject {
private let observableValue: Value<T>
@Published
var value: T
private var observer: ((T) -> Void)?
init(_ value: Value<T>) {
self.observableValue = value
self.value = observableValue.value
self.observer = { [weak self] value in self?.value = value }
observableValue.subscribe(observer: observer!)
}
deinit {
self.observableValue.unsubscribe(observer: self.observer!)
}
}

6
examples/todoapp/ios/ios/Preview Content/Preview Assets.xcassets/Contents.json

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

43
examples/todoapp/ios/ios/RootView.swift

@ -0,0 +1,43 @@
import SwiftUI
import Todo
struct RootView: View {
@ObservedObject
private var routerStates: ObservableValue<RouterState<AnyObject, TodoRootChild>>
init(_ component: TodoRoot) {
self.routerStates = ObservableValue(component.routerState)
}
var body: some View {
let child = self.routerStates.value.activeChild.component
switch child {
case let main as TodoRootChild.Main:
MainView(main.component)
case let edit as TodoRootChild.Edit:
EditView(edit.component)
.transition(
.asymmetric(
insertion: AnyTransition.move(edge: .trailing),
removal: AnyTransition.move(edge: .trailing)
)
)
.animation(.easeInOut)
default: EmptyView()
}
}
}
struct RootView_Previews: PreviewProvider {
static var previews: some View {
RootView(StubTodoRoot())
}
class StubTodoRoot : TodoRoot {
let routerState: Value<RouterState<AnyObject, TodoRootChild>> =
simpleRouterState(TodoRootChild.Main(component: MainView_Previews.StubTodoMain()))
}
}

13
examples/todoapp/ios/ios/SimpleRouterState.swift

@ -0,0 +1,13 @@
import Todo
func simpleRouterState<T : AnyObject>(_ child: T) -> Value<RouterState<AnyObject, T>> {
return valueOf(
RouterState(
activeChild: RouterStateEntryCreated(
configuration: "config" as AnyObject,
component: child
),
backStack: []
)
)
}

10
examples/todoapp/ios/ios/TodoApp.swift

@ -0,0 +1,10 @@
import SwiftUI
@main
struct TodoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

1
examples/todoapp/settings.gradle.kts

@ -4,6 +4,7 @@ include(
":common:main", ":common:main",
":common:edit", ":common:edit",
":common:root", ":common:root",
":common:compose-ui",
":android", ":android",
":desktop" ":desktop"
) )

Loading…
Cancel
Save