@ -0,0 +1,48 @@ |
|||||||
|
--- |
||||||
|
name: Performance problem |
||||||
|
about: Create a report to help us improve |
||||||
|
title: '' |
||||||
|
labels: ['submitted', 'performance'] |
||||||
|
assignees: '' |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
**Describe the problem** |
||||||
|
Explain the performance issue you're experiencing, including the following details: |
||||||
|
|
||||||
|
- What specific issue did you encounter? (e.g. missing frames, high CPU usage, memory leaks) |
||||||
|
- Have you noticed any patterns or specific circumstances under which the problem occurs? |
||||||
|
|
||||||
|
**Affected platforms** |
||||||
|
Select one of the platforms below: |
||||||
|
- All |
||||||
|
- Desktop |
||||||
|
- Web (K/Wasm) - Canvas based API |
||||||
|
- Web (K/JS) - Canvas based API |
||||||
|
- Web (K/JS) - HTML library |
||||||
|
- iOS |
||||||
|
- Other |
||||||
|
|
||||||
|
If the problem is Android-only, report it in the [Jetpack Compose tracker](https://issuetracker.google.com/issues/new?component=612128) |
||||||
|
|
||||||
|
**Versions** |
||||||
|
- Kotlin version: |
||||||
|
- Compose Multiplatform version: |
||||||
|
- OS version(s) (required for Desktop and iOS issues): |
||||||
|
- OS architecture (x86 or arm64): |
||||||
|
- JDK (for desktop issues): |
||||||
|
|
||||||
|
**Sample code** |
||||||
|
If possible, provide a small piece of code that reproduces the problem. If the code snippet is too large to paste here, please link to a Gist, a GitHub repo, or any other public code repository. |
||||||
|
|
||||||
|
**Reproduction steps** |
||||||
|
Please provide a detailed step-by-step guide on how to reproduce the issue you are experiencing. |
||||||
|
|
||||||
|
**Video** |
||||||
|
If you're reporting slow app work or missing frames, please provide a video of the problem. |
||||||
|
|
||||||
|
**Profiling data** |
||||||
|
Please provide any relevant profiling data that might be helpful. This could include information like FPS, memory usage, CPU time, or any other data that could provide insight into the performance issue. |
||||||
|
|
||||||
|
**Additional information** |
||||||
|
Provide any other details that you think might be helpful for us to understand the problem. This could include things like the system configuration, external factors, etc. |
@ -1,2 +1,2 @@ |
|||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
|
@ -1,4 +1,4 @@ |
|||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -1,4 +1,4 @@ |
|||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -0,0 +1,39 @@ |
|||||||
|
plugins { |
||||||
|
kotlin("multiplatform") |
||||||
|
id("org.jetbrains.compose") |
||||||
|
} |
||||||
|
|
||||||
|
kotlin { |
||||||
|
configureTargets() |
||||||
|
|
||||||
|
sourceSets { |
||||||
|
val commonMain by getting { |
||||||
|
dependencies { |
||||||
|
implementation(compose.runtime) |
||||||
|
implementation(getCommonLib()) |
||||||
|
} |
||||||
|
} |
||||||
|
val commonTest by getting { |
||||||
|
configureCommonTestDependencies() |
||||||
|
} |
||||||
|
|
||||||
|
val nativeMain by creating { |
||||||
|
dependsOn(commonMain) |
||||||
|
} |
||||||
|
val iosMain by getting { |
||||||
|
dependsOn(nativeMain) |
||||||
|
} |
||||||
|
val linuxX64Main by getting { |
||||||
|
dependsOn(nativeMain) |
||||||
|
} |
||||||
|
val macosX64Main by getting { |
||||||
|
dependsOn(nativeMain) |
||||||
|
} |
||||||
|
val macosArm64Main by getting { |
||||||
|
dependsOn(nativeMain) |
||||||
|
} |
||||||
|
val mingwX64Main by getting { |
||||||
|
dependsOn(nativeMain) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
class Abc |
||||||
|
|
||||||
|
|
||||||
|
val Abc.commonIntVal: Int |
||||||
|
@Composable get() = 1000 |
||||||
|
|
||||||
|
expect val Abc.composableIntVal: Int |
||||||
|
@Composable get |
@ -0,0 +1,4 @@ |
|||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
actual val Abc.composableIntVal: Int |
||||||
|
@Composable get () = 100 |
@ -0,0 +1,4 @@ |
|||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
actual val Abc.composableIntVal: Int |
||||||
|
@Composable get () = 100 |
@ -0,0 +1,4 @@ |
|||||||
|
import androidx.compose.runtime.Composable |
||||||
|
|
||||||
|
actual val Abc.composableIntVal: Int |
||||||
|
@Composable get () = 100 |
@ -0,0 +1,21 @@ |
|||||||
|
plugins { |
||||||
|
kotlin("multiplatform") |
||||||
|
id("org.jetbrains.compose") |
||||||
|
} |
||||||
|
|
||||||
|
kotlin { |
||||||
|
configureTargets() |
||||||
|
|
||||||
|
sourceSets { |
||||||
|
val commonMain by getting { |
||||||
|
dependencies { |
||||||
|
implementation(compose.runtime) |
||||||
|
implementation(getCommonLib()) |
||||||
|
implementation(getLibDependencyForMain()) |
||||||
|
} |
||||||
|
} |
||||||
|
val commonTest by getting { |
||||||
|
configureCommonTestDependencies() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import com.example.common.TextLeafNode |
||||||
|
import com.example.common.composeText |
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||||
|
import kotlinx.coroutines.test.runTest |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class) |
||||||
|
class Tests { |
||||||
|
|
||||||
|
@Test |
||||||
|
// K/JS fails. Related: https://github.com/JetBrains/compose-multiplatform/issues/3373 |
||||||
|
fun composableExpectActualValGetter() = runTest { |
||||||
|
val root = composeText { |
||||||
|
val v = Abc().composableIntVal |
||||||
|
TextLeafNode("$v") |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("root:{100}", root.dump()) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun commonComposableValGetter() = runTest { |
||||||
|
val root = composeText { |
||||||
|
val v = Abc().commonIntVal |
||||||
|
TextLeafNode("$v") |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals("root:{1000}", root.dump()) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,16 +1,15 @@ |
|||||||
# Samples |
# Samples |
||||||
| Sample | Description | Platforms | |
| Sample | Description | Platforms | |
||||||
| ------------- | ------------- | ------------- | |
| ------------- |----------------------------------------------------------------------------------------------------| ------------- | |
||||||
| [Imageviewer](imageviewer) | Image Viewer application | Android, iOS, Desktop | |
| [Imageviewer](imageviewer) | Image Viewer application | Android, iOS, Desktop | |
||||||
| [Codeviewer](codeviewer) | File browser and code viewer application | Android, iOS, Desktop | |
| [Codeviewer](codeviewer) | File browser and code viewer application | Android, iOS, Desktop | |
||||||
| [Chat](chat) | A simple chat | Android, iOS, Desktop | |
| [Chat](chat) | A simple chat | Android, iOS, Desktop | |
||||||
| [Minesweeper](minesweeper) | A simple game where you need to find hidden mines | Android, iOS, Desktop | |
| [Minesweeper](minesweeper) | A simple game where you need to find hidden mines | Android, iOS, Desktop | |
||||||
| [Falling Balls](falling-balls) | A simple game | Android, iOS, Desktop | |
| [Falling Balls](falling-balls) | A simple game | Android, iOS, Desktop | |
||||||
| [Visual effects](visual-effects) | Visual effects | Android, iOS, Desktop | |
| [Visual effects](visual-effects) | Visual effects | Android, iOS, Desktop | |
||||||
| [Widgets Gallery](widgets-gallery) | Gallery of standard widgets | Android, iOS, Desktop | |
| [Widgets Gallery](widgets-gallery) | Gallery of standard widgets | Android, iOS, Desktop | |
||||||
| [Todoapp Lite](todoapp-lite) | A simplified version of [Todoapp](todoapp), fully based on Compose | Android, iOS, Desktop | |
| [Todoapp Lite](todoapp-lite) | A simple todo app fully based on Compose | Android, iOS, Desktop | |
||||||
| [Todoapp](todoapp) | TODO items tracker with persistence and multiple screens, written with external navigation library | Android, iOS native, Desktop | |
| [Issues tracker](issues) | GitHub issue tracker with an adaptive UI and ktor-client | Android, Desktop | |
||||||
| [Issues tracker](issues) | GitHub issue tracker with an adaptive UI and ktor-client | Android, Desktop | |
| [Notepad](notepad) | Notepad, using the Composable Window API | Desktop | |
||||||
| [Notepad](notepad) | Notepad, using the Composable Window API | Desktop | |
| [IDEA plugin](intellij-plugin) | Plugin for IDEA using Compose for Desktop | Desktop | |
||||||
| [IDEA plugin](intellij-plugin) | Plugin for IDEA using Compose for Desktop | Desktop | |
| [HTML based samples](html/README.md) | Examples written with Compose HTML Library | |
||||||
| [HTML based samples](html/README.md) | Examples written with Compose HTML Library |
|
@ -1,3 +1,3 @@ |
|||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -1,4 +1,4 @@ |
|||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
||||||
kotlin.code.style=official |
kotlin.code.style=official |
||||||
kotlin.version=1.8.20 |
kotlin.version=1.9.0 |
||||||
compose.version=1.4.1 |
compose.version=1.4.3 |
||||||
|
@ -1,15 +0,0 @@ |
|||||||
*.iml |
|
||||||
.gradle |
|
||||||
/local.properties |
|
||||||
/.idea |
|
||||||
/.idea/caches |
|
||||||
/.idea/libraries |
|
||||||
/.idea/modules.xml |
|
||||||
/.idea/workspace.xml |
|
||||||
/.idea/navEditor.xml |
|
||||||
/.idea/assetWizardSettings.xml |
|
||||||
.DS_Store |
|
||||||
build/ |
|
||||||
/captures |
|
||||||
.externalNativeBuild |
|
||||||
.cxx |
|
@ -1,23 +0,0 @@ |
|||||||
<component name="ProjectRunConfigurationManager"> |
|
||||||
<configuration default="false" name="browser" type="GradleRunConfiguration" factoryName="Gradle"> |
|
||||||
<ExternalSystemSettings> |
|
||||||
<option name="executionName" /> |
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" /> |
|
||||||
<option name="externalSystemIdString" value="GRADLE" /> |
|
||||||
<option name="scriptParameters" value="" /> |
|
||||||
<option name="taskDescriptions"> |
|
||||||
<list /> |
|
||||||
</option> |
|
||||||
<option name="taskNames"> |
|
||||||
<list> |
|
||||||
<option value=":web:jsBrowserDevelopmentRun" /> |
|
||||||
</list> |
|
||||||
</option> |
|
||||||
<option name="vmOptions" /> |
|
||||||
</ExternalSystemSettings> |
|
||||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> |
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
|
||||||
<DebugAllEnabled>false</DebugAllEnabled> |
|
||||||
<method v="2" /> |
|
||||||
</configuration> |
|
||||||
</component> |
|
@ -1,23 +0,0 @@ |
|||||||
<component name="ProjectRunConfigurationManager"> |
|
||||||
<configuration default="false" name="desktop" type="GradleRunConfiguration" factoryName="Gradle"> |
|
||||||
<ExternalSystemSettings> |
|
||||||
<option name="executionName" /> |
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" /> |
|
||||||
<option name="externalSystemIdString" value="GRADLE" /> |
|
||||||
<option name="scriptParameters" value="" /> |
|
||||||
<option name="taskDescriptions"> |
|
||||||
<list /> |
|
||||||
</option> |
|
||||||
<option name="taskNames"> |
|
||||||
<list> |
|
||||||
<option value=":desktop:run" /> |
|
||||||
</list> |
|
||||||
</option> |
|
||||||
<option name="vmOptions" /> |
|
||||||
</ExternalSystemSettings> |
|
||||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> |
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
|
||||||
<DebugAllEnabled>false</DebugAllEnabled> |
|
||||||
<method v="2" /> |
|
||||||
</configuration> |
|
||||||
</component> |
|
@ -1,61 +0,0 @@ |
|||||||
An example of Kotlin Multiplatform todo app with shared Android/Desktop Compose UI and SwiftUI (not Compose) iOS. |
|
||||||
|
|
||||||
This example supports the following targets: |
|
||||||
- `Android` (Compose) |
|
||||||
- `Desktop/JVM` (Compose) |
|
||||||
- `Web/JavaScript` (Compose) |
|
||||||
- `iOS` (SwiftUI, not Compose) |
|
||||||
|
|
||||||
Libraries used: |
|
||||||
- Compose Multiplatform - shared UI |
|
||||||
- [Decompose](https://github.com/arkivanov/Decompose) - navigation and lifecycle |
|
||||||
- [MVIKotlin](https://github.com/arkivanov/MVIKotlin) - presentation and business logic |
|
||||||
- [Reaktive](https://github.com/badoo/Reaktive) - background processing and data transformation |
|
||||||
- [SQLDelight](https://github.com/cashapp/sqldelight) - data storage |
|
||||||
|
|
||||||
There are multiple modules: |
|
||||||
- `:common:utils` - just some useful helpers |
|
||||||
- `:common:database` - SQLDelight database definition |
|
||||||
- `:common:main` - displays a list of todo items and a text field |
|
||||||
- `:common:edit` - accepts an item id and allows editing |
|
||||||
- `:common:root` - navigates between `main` and `edit` screens |
|
||||||
- `:common:compose-ui` - Shared Compose UI for Android and Desktop |
|
||||||
- `:android` - Android application |
|
||||||
- `:desktop` - Desktop application |
|
||||||
- `:web` - Web browser application + Compose HTML Library |
|
||||||
- `ios` - iOS Xcode project |
|
||||||
|
|
||||||
The root module is integrated into Android, Desktop and iOS (non-Compose) apps. |
|
||||||
|
|
||||||
Features: |
|
||||||
- 99% of the code is shared: data, business logic, presentation, navigation and UI |
|
||||||
- View state is preserved when navigating between screens, Android configuration change, etc. |
|
||||||
- Model-View-Intent (aka MVI) architectural pattern |
|
||||||
- Pluggable UI - Compose UI for Android, Desktop and Web, SwiftUI (not Compose) for iOS |
|
||||||
|
|
||||||
### Running desktop application |
|
||||||
* To run, launch command: `./gradlew :desktop:run` |
|
||||||
* Or choose **desktop** configuration in IDE and run it. |
|
||||||
![desktop-run-configuration.png](screenshots/desktop-run-configuration.png) |
|
||||||
|
|
||||||
#### Building native desktop distribution |
|
||||||
``` |
|
||||||
./gradlew :desktop:packageDistributionForCurrentOS |
|
||||||
# outputs are written to desktop/build/compose/binaries |
|
||||||
``` |
|
||||||
|
|
||||||
### Running Android application |
|
||||||
|
|
||||||
Open project in Intellij IDEA or Android Studio and run "android" configuration. |
|
||||||
|
|
||||||
### Running Web browser application |
|
||||||
|
|
||||||
* To run, launch command: `./gradlew :web:jsBrowserDevelopmentRun` |
|
||||||
* Or choose **browser** configuration in IDE and run it. |
|
||||||
![browser-run-configuration.png](screenshots/browser-run-configuration.png) |
|
||||||
|
|
||||||
### Running iOS application |
|
||||||
|
|
||||||
Open and build the Xcode project located in `ios` folder. |
|
||||||
|
|
||||||
![Desktop](screenshots/todo.png) |
|
@ -1,44 +0,0 @@ |
|||||||
plugins { |
|
||||||
id("com.android.application") |
|
||||||
kotlin("android") |
|
||||||
id("org.jetbrains.compose") |
|
||||||
} |
|
||||||
|
|
||||||
android { |
|
||||||
compileSdk = 33 |
|
||||||
|
|
||||||
defaultConfig { |
|
||||||
minSdk = 26 |
|
||||||
targetSdk = 33 |
|
||||||
versionCode = 1 |
|
||||||
versionName = "1.0" |
|
||||||
} |
|
||||||
|
|
||||||
compileOptions { |
|
||||||
sourceCompatibility = JavaVersion.VERSION_11 |
|
||||||
targetCompatibility = JavaVersion.VERSION_11 |
|
||||||
} |
|
||||||
|
|
||||||
packagingOptions { |
|
||||||
exclude("META-INF/*") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
implementation(project(":common:database")) |
|
||||||
implementation(project(":common:utils")) |
|
||||||
implementation(project(":common:root")) |
|
||||||
implementation(project(":common:compose-ui")) |
|
||||||
implementation(compose.material) |
|
||||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) |
|
||||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) |
|
||||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinLogging) |
|
||||||
implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinTimeTravel) |
|
||||||
implementation(Deps.ArkIvanov.Decompose.decompose) |
|
||||||
implementation(Deps.ArkIvanov.Decompose.extensionsCompose) |
|
||||||
implementation(Deps.AndroidX.AppCompat.appCompat) |
|
||||||
implementation(Deps.AndroidX.Activity.activityCompose) |
|
||||||
|
|
||||||
// Workaround for https://github.com/JetBrains/compose-jb/issues/2340 |
|
||||||
implementation("androidx.compose.material:material:${Deps.JetpackComposeWorkaround.VERSION}") |
|
||||||
} |
|
@ -1,28 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
package="example.todo.android"> |
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
|
||||||
<uses-permission android:name="android.permission.INTERNET"/> |
|
||||||
|
|
||||||
<application |
|
||||||
android:allowBackup="true" |
|
||||||
android:icon="@mipmap/ic_launcher" |
|
||||||
android:label="@string/app_name" |
|
||||||
android:name=".App" |
|
||||||
android:roundIcon="@mipmap/ic_launcher_round" |
|
||||||
android:supportsRtl="true" |
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> |
|
||||||
<activity |
|
||||||
android:name="example.todo.android.MainActivity" |
|
||||||
android:label="@string/app_name" |
|
||||||
android:exported="true"> |
|
||||||
<intent-filter> |
|
||||||
<action android:name="android.intent.action.MAIN" /> |
|
||||||
<category android:name="android.intent.category.LAUNCHER" /> |
|
||||||
</intent-filter> |
|
||||||
</activity> |
|
||||||
</application> |
|
||||||
|
|
||||||
</manifest> |
|
@ -1,13 +0,0 @@ |
|||||||
package example.todo.android |
|
||||||
|
|
||||||
import android.app.Application |
|
||||||
import com.arkivanov.mvikotlin.timetravel.server.TimeTravelServer |
|
||||||
|
|
||||||
class App : Application() { |
|
||||||
|
|
||||||
override fun onCreate() { |
|
||||||
super.onCreate() |
|
||||||
|
|
||||||
TimeTravelServer().start() |
|
||||||
} |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
package example.todo.android |
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color |
|
||||||
|
|
||||||
val purple200 = Color(0xFFBB86FC) |
|
||||||
val purple500 = Color(0xFF6200EE) |
|
||||||
val purple700 = Color(0xFF3700B3) |
|
||||||
val teal200 = Color(0xFF03DAC5) |
|
@ -1,39 +0,0 @@ |
|||||||
package example.todo.android |
|
||||||
|
|
||||||
import android.os.Bundle |
|
||||||
import androidx.activity.compose.setContent |
|
||||||
import androidx.appcompat.app.AppCompatActivity |
|
||||||
import androidx.compose.material.MaterialTheme |
|
||||||
import androidx.compose.material.Surface |
|
||||||
import com.arkivanov.decompose.ComponentContext |
|
||||||
import com.arkivanov.decompose.defaultComponentContext |
|
||||||
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory |
|
||||||
import com.arkivanov.mvikotlin.timetravel.store.TimeTravelStoreFactory |
|
||||||
import example.todo.common.database.DefaultTodoSharedDatabase |
|
||||||
import example.todo.common.database.TodoDatabaseDriver |
|
||||||
import example.todo.common.root.TodoRoot |
|
||||||
import example.todo.common.root.integration.TodoRootComponent |
|
||||||
import example.todo.common.ui.TodoRootContent |
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() { |
|
||||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||||
super.onCreate(savedInstanceState) |
|
||||||
|
|
||||||
val root = todoRoot(defaultComponentContext()) |
|
||||||
|
|
||||||
setContent { |
|
||||||
ComposeAppTheme { |
|
||||||
Surface(color = MaterialTheme.colors.background) { |
|
||||||
TodoRootContent(root) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun todoRoot(componentContext: ComponentContext): TodoRoot = |
|
||||||
TodoRootComponent( |
|
||||||
componentContext = componentContext, |
|
||||||
storeFactory = LoggingStoreFactory(TimeTravelStoreFactory()), |
|
||||||
database = DefaultTodoSharedDatabase(TodoDatabaseDriver(context = this)) |
|
||||||
) |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
package example.todo.android |
|
||||||
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape |
|
||||||
import androidx.compose.material.Shapes |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
|
|
||||||
val shapes = Shapes( |
|
||||||
small = RoundedCornerShape(4.dp), |
|
||||||
medium = RoundedCornerShape(4.dp), |
|
||||||
large = RoundedCornerShape(0.dp) |
|
||||||
) |
|
@ -1,35 +0,0 @@ |
|||||||
package example.todo.android |
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme |
|
||||||
import androidx.compose.material.MaterialTheme |
|
||||||
import androidx.compose.material.darkColors |
|
||||||
import androidx.compose.material.lightColors |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
|
|
||||||
private val DarkColorPalette = darkColors( |
|
||||||
primary = purple200, |
|
||||||
primaryVariant = purple700, |
|
||||||
secondary = teal200 |
|
||||||
) |
|
||||||
|
|
||||||
private val LightColorPalette = lightColors( |
|
||||||
primary = purple500, |
|
||||||
primaryVariant = purple700, |
|
||||||
secondary = teal200 |
|
||||||
) |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun ComposeAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { |
|
||||||
val colors = if (darkTheme) { |
|
||||||
DarkColorPalette |
|
||||||
} else { |
|
||||||
LightColorPalette |
|
||||||
} |
|
||||||
|
|
||||||
MaterialTheme( |
|
||||||
colors = colors, |
|
||||||
typography = typography, |
|
||||||
shapes = shapes, |
|
||||||
content = content |
|
||||||
) |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
package example.todo.android |
|
||||||
|
|
||||||
import androidx.compose.material.Typography |
|
||||||
import androidx.compose.ui.text.TextStyle |
|
||||||
import androidx.compose.ui.text.font.FontFamily |
|
||||||
import androidx.compose.ui.text.font.FontWeight |
|
||||||
import androidx.compose.ui.unit.sp |
|
||||||
|
|
||||||
// Set of Material typography styles to start with |
|
||||||
val typography = Typography( |
|
||||||
body1 = TextStyle( |
|
||||||
fontFamily = FontFamily.Default, |
|
||||||
fontWeight = FontWeight.Normal, |
|
||||||
fontSize = 16.sp |
|
||||||
) |
|
||||||
) |
|
@ -1,34 +0,0 @@ |
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
xmlns:aapt="http://schemas.android.com/aapt" |
|
||||||
android:width="108dp" |
|
||||||
android:height="108dp" |
|
||||||
android:viewportWidth="108" |
|
||||||
android:viewportHeight="108"> |
|
||||||
<path |
|
||||||
android:fillType="evenOdd" |
|
||||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" |
|
||||||
android:strokeWidth="1" |
|
||||||
android:strokeColor="#00000000"> |
|
||||||
<aapt:attr name="android:fillColor"> |
|
||||||
<gradient |
|
||||||
android:endX="78.5885" |
|
||||||
android:endY="90.9159" |
|
||||||
android:startX="48.7653" |
|
||||||
android:startY="61.0927" |
|
||||||
android:type="linear"> |
|
||||||
<item |
|
||||||
android:color="#44000000" |
|
||||||
android:offset="0.0" /> |
|
||||||
<item |
|
||||||
android:color="#00000000" |
|
||||||
android:offset="1.0" /> |
|
||||||
</gradient> |
|
||||||
</aapt:attr> |
|
||||||
</path> |
|
||||||
<path |
|
||||||
android:fillColor="#FFFFFF" |
|
||||||
android:fillType="nonZero" |
|
||||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" |
|
||||||
android:strokeWidth="1" |
|
||||||
android:strokeColor="#00000000" /> |
|
||||||
</vector> |
|
@ -1,170 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
android:width="108dp" |
|
||||||
android:height="108dp" |
|
||||||
android:viewportWidth="108" |
|
||||||
android:viewportHeight="108"> |
|
||||||
<path |
|
||||||
android:fillColor="#008577" |
|
||||||
android:pathData="M0,0h108v108h-108z" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M9,0L9,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,0L19,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M29,0L29,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M39,0L39,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M49,0L49,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M59,0L59,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M69,0L69,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M79,0L79,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M89,0L89,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M99,0L99,108" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,9L108,9" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,19L108,19" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,29L108,29" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,39L108,39" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,49L108,49" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,59L108,59" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,69L108,69" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,79L108,79" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,89L108,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M0,99L108,99" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,29L89,29" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,39L89,39" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,49L89,49" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,59L89,59" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,69L89,69" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M19,79L89,79" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M29,19L29,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M39,19L39,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M49,19L49,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M59,19L59,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M69,19L69,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
<path |
|
||||||
android:fillColor="#00000000" |
|
||||||
android:pathData="M79,19L79,89" |
|
||||||
android:strokeWidth="0.8" |
|
||||||
android:strokeColor="#33FFFFFF" /> |
|
||||||
</vector> |
|
@ -1,5 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||||
<background android:drawable="@drawable/ic_launcher_background" /> |
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" /> |
|
||||||
</adaptive-icon> |
|
@ -1,5 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||||
<background android:drawable="@drawable/ic_launcher_background" /> |
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" /> |
|
||||||
</adaptive-icon> |
|
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,4 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<resources> |
|
||||||
<string name="app_name">Todo</string> |
|
||||||
</resources> |
|
@ -1,19 +0,0 @@ |
|||||||
plugins { |
|
||||||
`kotlin-dsl` |
|
||||||
} |
|
||||||
|
|
||||||
allprojects { |
|
||||||
repositories { |
|
||||||
google() |
|
||||||
mavenCentral() |
|
||||||
mavenLocal() |
|
||||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
|
||||||
} |
|
||||||
|
|
||||||
afterEvaluate { |
|
||||||
// Workaround for https://youtrack.jetbrains.com/issue/KT-52776 |
|
||||||
rootProject.extensions.findByType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>()?.apply { |
|
||||||
versions.webpackCli.version = "4.10.0" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
plugins { |
|
||||||
`kotlin-dsl` |
|
||||||
} |
|
||||||
|
|
||||||
initDeps(project) |
|
||||||
|
|
||||||
repositories { |
|
||||||
mavenLocal() |
|
||||||
google() |
|
||||||
mavenCentral() |
|
||||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
implementation(Deps.JetBrains.Compose.gradlePlugin) |
|
||||||
implementation(Deps.JetBrains.Kotlin.gradlePlugin) |
|
||||||
implementation(Deps.Android.Tools.Build.gradlePlugin) |
|
||||||
implementation(Deps.Squareup.SQLDelight.gradlePlugin) |
|
||||||
} |
|
||||||
|
|
||||||
kotlin { |
|
||||||
// Add Deps to compilation, so it will become available in main project |
|
||||||
sourceSets.getByName("main").kotlin.srcDir("buildSrc/src/main/kotlin") |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
plugins { |
|
||||||
`kotlin-dsl` |
|
||||||
} |
|
||||||
|
|
||||||
repositories { |
|
||||||
mavenCentral() |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
//todo workaround to build iOS Arm64 simulator: |
|
||||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") |
|
||||||
} |
|
@ -1,101 +0,0 @@ |
|||||||
// We store Kotlin and Compose versions in gradle.properties to |
|
||||||
// be able to override them on CI. |
|
||||||
// You probably won't need this, so you can get rid of `project` in this file. |
|
||||||
import org.gradle.api.Project |
|
||||||
|
|
||||||
lateinit var properties: Map<String, *> |
|
||||||
|
|
||||||
fun initDeps(project: Project) { |
|
||||||
properties = project.properties |
|
||||||
} |
|
||||||
|
|
||||||
object Deps { |
|
||||||
object JetpackComposeWorkaround { |
|
||||||
// Workaround for https://github.com/JetBrains/compose-jb/issues/2340 |
|
||||||
val VERSION: String = "1.4.0-rc01" |
|
||||||
} |
|
||||||
|
|
||||||
object JetBrains { |
|
||||||
object Kotlin { |
|
||||||
private val VERSION get() = properties["kotlin.version"] |
|
||||||
val gradlePlugin get() = "org.jetbrains.kotlin:kotlin-gradle-plugin:$VERSION" |
|
||||||
val testCommon get() = "org.jetbrains.kotlin:kotlin-test-common:$VERSION" |
|
||||||
val testJunit get() = "org.jetbrains.kotlin:kotlin-test-junit:$VERSION" |
|
||||||
val testJs get() = "org.jetbrains.kotlin:kotlin-test-js:$VERSION" |
|
||||||
val testAnnotationsCommon get() = "org.jetbrains.kotlin:kotlin-test-annotations-common:$VERSION" |
|
||||||
} |
|
||||||
|
|
||||||
object Coroutines { |
|
||||||
private val VERSION get() = "1.6.4" |
|
||||||
val swing get() = "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$VERSION" |
|
||||||
} |
|
||||||
|
|
||||||
object Compose { |
|
||||||
private val VERSION get() = properties["compose.version"] |
|
||||||
val gradlePlugin get() = "org.jetbrains.compose:compose-gradle-plugin:$VERSION" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
object Android { |
|
||||||
object Tools { |
|
||||||
object Build { |
|
||||||
const val gradlePlugin = "com.android.tools.build:gradle:7.2.0" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
object AndroidX { |
|
||||||
object AppCompat { |
|
||||||
const val appCompat = "androidx.appcompat:appcompat:1.3.0" |
|
||||||
} |
|
||||||
|
|
||||||
object Activity { |
|
||||||
const val activityCompose = "androidx.activity:activity-compose:1.3.0" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
object ArkIvanov { |
|
||||||
object MVIKotlin { |
|
||||||
private const val VERSION = "3.0.0" |
|
||||||
const val rx = "com.arkivanov.mvikotlin:rx:$VERSION" |
|
||||||
const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION" |
|
||||||
const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION" |
|
||||||
const val mvikotlinLogging = "com.arkivanov.mvikotlin:mvikotlin-logging:$VERSION" |
|
||||||
const val mvikotlinTimeTravel = "com.arkivanov.mvikotlin:mvikotlin-timetravel:$VERSION" |
|
||||||
const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION" |
|
||||||
} |
|
||||||
|
|
||||||
object Decompose { |
|
||||||
private const val VERSION = "1.0.0-alpha-05" |
|
||||||
const val decompose = "com.arkivanov.decompose:decompose:$VERSION" |
|
||||||
const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION" |
|
||||||
} |
|
||||||
|
|
||||||
object Essenty { |
|
||||||
private const val VERSION = "0.6.0" |
|
||||||
const val lifecycle = "com.arkivanov.essenty:lifecycle:$VERSION" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
object Badoo { |
|
||||||
object Reaktive { |
|
||||||
private const val VERSION = "1.2.1" |
|
||||||
const val reaktive = "com.badoo.reaktive:reaktive:$VERSION" |
|
||||||
const val reaktiveTesting = "com.badoo.reaktive:reaktive-testing:$VERSION" |
|
||||||
const val utils = "com.badoo.reaktive:utils:$VERSION" |
|
||||||
const val coroutinesInterop = "com.badoo.reaktive:coroutines-interop:$VERSION" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
object Squareup { |
|
||||||
object SQLDelight { |
|
||||||
private const val VERSION = "1.5.5" |
|
||||||
|
|
||||||
const val gradlePlugin = "com.squareup.sqldelight:gradle-plugin:$VERSION" |
|
||||||
const val androidDriver = "com.squareup.sqldelight:android-driver:$VERSION" |
|
||||||
const val sqliteDriver = "com.squareup.sqldelight:sqlite-driver:$VERSION" |
|
||||||
const val nativeDriver = "com.squareup.sqldelight:native-driver:$VERSION" |
|
||||||
const val sqljsDriver = "com.squareup.sqldelight:sqljs-driver:$VERSION" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinTargetContainerWithNativeShortcuts |
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget |
|
||||||
|
|
||||||
fun KotlinTargetContainerWithNativeShortcuts.iosWorkaroundSupportArm64Simulator( |
|
||||||
configure: KotlinNativeTarget.() -> Unit |
|
||||||
) { |
|
||||||
val isBuildToSimulator = System.getenv("SDK_NAME")?.startsWith("iphonesimulator") ?: false |
|
||||||
val isArm64Target = System.getenv("NATIVE_ARCH") == "arm64" |
|
||||||
|
|
||||||
if (isBuildToSimulator && isArm64Target) { |
|
||||||
//workaround: |
|
||||||
iosSimulatorArm64(name = "ios", configure = configure) |
|
||||||
} else { |
|
||||||
//default behavior: |
|
||||||
ios(configure = configure) |
|
||||||
} |
|
||||||
} |
|
@ -1,3 +0,0 @@ |
|||||||
# TODO can we get rid of duplication with root gradle.properties? |
|
||||||
kotlin.version=1.8.20 |
|
||||||
compose.version=1.4.0 |
|
@ -1,26 +0,0 @@ |
|||||||
plugins { |
|
||||||
id("com.android.library") |
|
||||||
} |
|
||||||
|
|
||||||
initDeps(project) |
|
||||||
|
|
||||||
android { |
|
||||||
compileSdk = 33 |
|
||||||
|
|
||||||
defaultConfig { |
|
||||||
minSdk = 23 |
|
||||||
targetSdk = 33 |
|
||||||
} |
|
||||||
|
|
||||||
compileOptions { |
|
||||||
sourceCompatibility = JavaVersion.VERSION_11 |
|
||||||
targetCompatibility = JavaVersion.VERSION_11 |
|
||||||
} |
|
||||||
|
|
||||||
sourceSets { |
|
||||||
named("main") { |
|
||||||
manifest.srcFile("src/androidMain/AndroidManifest.xml") |
|
||||||
res.srcDirs("src/androidMain/res") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
plugins { |
|
||||||
id("com.android.library") |
|
||||||
id("kotlin-multiplatform") |
|
||||||
id("org.jetbrains.compose") |
|
||||||
} |
|
||||||
|
|
||||||
initDeps(project) |
|
||||||
|
|
||||||
kotlin { |
|
||||||
jvm("desktop") |
|
||||||
android() |
|
||||||
|
|
||||||
sourceSets { |
|
||||||
named("commonMain") { |
|
||||||
dependencies { |
|
||||||
implementation(compose.runtime) |
|
||||||
implementation(compose.foundation) |
|
||||||
implementation(compose.material) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
named("androidMain") { |
|
||||||
dependencies { |
|
||||||
implementation("androidx.appcompat:appcompat:1.3.0") |
|
||||||
implementation("androidx.core:core-ktx:1.3.1") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
named("desktopMain") { |
|
||||||
dependencies { |
|
||||||
implementation(compose.desktop.common) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { |
|
||||||
kotlinOptions.jvmTarget = "11" |
|
||||||
} |
|
||||||
} |
|
@ -1,75 +0,0 @@ |
|||||||
plugins { |
|
||||||
id("com.android.library") |
|
||||||
id("kotlin-multiplatform") |
|
||||||
} |
|
||||||
|
|
||||||
initDeps(project) |
|
||||||
|
|
||||||
kotlin { |
|
||||||
jvm("desktop") |
|
||||||
android() |
|
||||||
iosX64() |
|
||||||
iosArm64() |
|
||||||
iosSimulatorArm64() |
|
||||||
|
|
||||||
js(IR) { |
|
||||||
browser() |
|
||||||
} |
|
||||||
|
|
||||||
sourceSets { |
|
||||||
create("iosMain") { |
|
||||||
dependsOn(getByName("commonMain")) |
|
||||||
} |
|
||||||
create("iosTest") { |
|
||||||
dependsOn(getByName("commonTest")) |
|
||||||
} |
|
||||||
|
|
||||||
getByName("iosX64Main") { |
|
||||||
dependsOn(getByName("iosMain")) |
|
||||||
} |
|
||||||
getByName("iosX64Test") { |
|
||||||
dependsOn(getByName("iosTest")) |
|
||||||
} |
|
||||||
|
|
||||||
getByName("iosArm64Main") { |
|
||||||
dependsOn(getByName("iosMain")) |
|
||||||
} |
|
||||||
getByName("iosArm64Test") { |
|
||||||
dependsOn(getByName("iosTest")) |
|
||||||
} |
|
||||||
|
|
||||||
getByName("iosSimulatorArm64Main") { |
|
||||||
dependsOn(getByName("iosMain")) |
|
||||||
} |
|
||||||
getByName("iosSimulatorArm64Test") { |
|
||||||
dependsOn(getByName("iosTest")) |
|
||||||
} |
|
||||||
|
|
||||||
named("commonTest") { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.JetBrains.Kotlin.testCommon) |
|
||||||
implementation(Deps.JetBrains.Kotlin.testAnnotationsCommon) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
named("androidTest") { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.JetBrains.Kotlin.testJunit) |
|
||||||
} |
|
||||||
} |
|
||||||
named("desktopTest") { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.JetBrains.Kotlin.testJunit) |
|
||||||
} |
|
||||||
} |
|
||||||
named("jsTest") { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.JetBrains.Kotlin.testJs) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { |
|
||||||
kotlinOptions.jvmTarget = "11" |
|
||||||
} |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
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) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
named("androidMain") { |
|
||||||
dependencies { |
|
||||||
// Workaround for https://github.com/JetBrains/compose-jb/issues/2340 |
|
||||||
implementation("androidx.compose.foundation:foundation:${Deps.JetpackComposeWorkaround.VERSION}") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,2 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<manifest package="example.todo.common.ui"/> |
|
@ -1,26 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.Dp |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
|
|
||||||
actual val MARGIN_SCROLLBAR: Dp = 0.dp |
|
||||||
|
|
||||||
actual interface ScrollbarAdapter |
|
||||||
|
|
||||||
@Composable |
|
||||||
actual fun rememberScrollbarAdapter( |
|
||||||
scrollState: LazyListState, |
|
||||||
itemCount: Int, |
|
||||||
averageItemSize: Dp |
|
||||||
): ScrollbarAdapter = |
|
||||||
object : ScrollbarAdapter {} |
|
||||||
|
|
||||||
@Composable |
|
||||||
actual fun VerticalScrollbar( |
|
||||||
modifier: Modifier, |
|
||||||
adapter: ScrollbarAdapter |
|
||||||
) { |
|
||||||
} |
|
@ -1,23 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.Dp |
|
||||||
|
|
||||||
expect val MARGIN_SCROLLBAR: Dp |
|
||||||
|
|
||||||
expect interface ScrollbarAdapter |
|
||||||
|
|
||||||
@Composable |
|
||||||
expect fun rememberScrollbarAdapter( |
|
||||||
scrollState: LazyListState, |
|
||||||
itemCount: Int, |
|
||||||
averageItemSize: Dp |
|
||||||
): ScrollbarAdapter |
|
||||||
|
|
||||||
@Composable |
|
||||||
expect fun VerticalScrollbar( |
|
||||||
modifier: Modifier, |
|
||||||
adapter: ScrollbarAdapter |
|
||||||
) |
|
@ -1,15 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.ui.input.key.Key |
|
||||||
import androidx.compose.ui.input.key.KeyEvent |
|
||||||
import androidx.compose.ui.input.key.key |
|
||||||
|
|
||||||
fun onKeyUp(key: Key, onEvent: () -> Unit): (KeyEvent) -> Boolean = |
|
||||||
{ keyEvent -> |
|
||||||
if (keyEvent.key == key) { |
|
||||||
onEvent() |
|
||||||
true |
|
||||||
} else { |
|
||||||
false |
|
||||||
} |
|
||||||
} |
|
@ -1,60 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column |
|
||||||
import androidx.compose.foundation.layout.Row |
|
||||||
import androidx.compose.foundation.layout.Spacer |
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth |
|
||||||
import androidx.compose.foundation.layout.padding |
|
||||||
import androidx.compose.foundation.layout.width |
|
||||||
import androidx.compose.material.Checkbox |
|
||||||
import androidx.compose.material.Icon |
|
||||||
import androidx.compose.material.IconButton |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.material.TextField |
|
||||||
import androidx.compose.material.TopAppBar |
|
||||||
import androidx.compose.material.icons.Icons |
|
||||||
import androidx.compose.material.icons.filled.ArrowBack |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.runtime.getValue |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState |
|
||||||
import example.todo.common.edit.TodoEdit |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun TodoEditContent(component: TodoEdit) { |
|
||||||
val model by component.models.subscribeAsState() |
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) { |
|
||||||
TopAppBar( |
|
||||||
title = { Text("Edit todo") }, |
|
||||||
navigationIcon = { |
|
||||||
IconButton(onClick = component::onCloseClicked) { |
|
||||||
Icon( |
|
||||||
imageVector = Icons.Default.ArrowBack, |
|
||||||
contentDescription = null |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
TextField( |
|
||||||
value = model.text, |
|
||||||
modifier = Modifier.weight(1F).fillMaxWidth().padding(8.dp), |
|
||||||
label = { Text("Todo text") }, |
|
||||||
onValueChange = component::onTextChanged |
|
||||||
) |
|
||||||
|
|
||||||
Row(modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) { |
|
||||||
Text(text = "Completed") |
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||||
|
|
||||||
Checkbox( |
|
||||||
checked = model.isDone, |
|
||||||
onCheckedChange = component::onDoneChanged |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,158 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable |
|
||||||
import androidx.compose.foundation.layout.Box |
|
||||||
import androidx.compose.foundation.layout.Column |
|
||||||
import androidx.compose.foundation.layout.Row |
|
||||||
import androidx.compose.foundation.layout.Spacer |
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight |
|
||||||
import androidx.compose.foundation.layout.padding |
|
||||||
import androidx.compose.foundation.layout.width |
|
||||||
import androidx.compose.foundation.lazy.LazyColumn |
|
||||||
import androidx.compose.foundation.lazy.items |
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState |
|
||||||
import androidx.compose.material.Checkbox |
|
||||||
import androidx.compose.material.Divider |
|
||||||
import androidx.compose.material.Icon |
|
||||||
import androidx.compose.material.IconButton |
|
||||||
import androidx.compose.material.OutlinedTextField |
|
||||||
import androidx.compose.material.Text |
|
||||||
import androidx.compose.material.TopAppBar |
|
||||||
import androidx.compose.material.icons.Icons |
|
||||||
import androidx.compose.material.icons.filled.Add |
|
||||||
import androidx.compose.material.icons.filled.Delete |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.runtime.getValue |
|
||||||
import androidx.compose.ui.Alignment |
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.input.key.Key |
|
||||||
import androidx.compose.ui.input.key.onKeyEvent |
|
||||||
import androidx.compose.ui.text.AnnotatedString |
|
||||||
import androidx.compose.ui.text.style.TextOverflow |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState |
|
||||||
import example.todo.common.main.TodoItem |
|
||||||
import example.todo.common.main.TodoMain |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun TodoMainContent(component: TodoMain) { |
|
||||||
val model by component.models.subscribeAsState() |
|
||||||
|
|
||||||
Column { |
|
||||||
TopAppBar(title = { Text(text = "Todo List") }) |
|
||||||
|
|
||||||
Box(Modifier.weight(1F)) { |
|
||||||
TodoList( |
|
||||||
items = model.items, |
|
||||||
onItemClicked = component::onItemClicked, |
|
||||||
onDoneChanged = component::onItemDoneChanged, |
|
||||||
onDeleteItemClicked = component::onItemDeleteClicked |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
TodoInput( |
|
||||||
text = model.text, |
|
||||||
onAddClicked = component::onAddItemClicked, |
|
||||||
onTextChanged = component::onInputTextChanged |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
private fun TodoList( |
|
||||||
items: List<TodoItem>, |
|
||||||
onItemClicked: (id: Long) -> Unit, |
|
||||||
onDoneChanged: (id: Long, isDone: Boolean) -> Unit, |
|
||||||
onDeleteItemClicked: (id: Long) -> Unit |
|
||||||
) { |
|
||||||
Box { |
|
||||||
val listState = rememberLazyListState() |
|
||||||
|
|
||||||
LazyColumn(state = listState) { |
|
||||||
items(items) { |
|
||||||
Item( |
|
||||||
item = it, |
|
||||||
onItemClicked = onItemClicked, |
|
||||||
onDoneChanged = onDoneChanged, |
|
||||||
onDeleteItemClicked = onDeleteItemClicked |
|
||||||
) |
|
||||||
|
|
||||||
Divider() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
VerticalScrollbar( |
|
||||||
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), |
|
||||||
adapter = rememberScrollbarAdapter( |
|
||||||
scrollState = listState, |
|
||||||
itemCount = items.size, |
|
||||||
averageItemSize = 37.dp |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Composable |
|
||||||
private fun Item( |
|
||||||
item: TodoItem, |
|
||||||
onItemClicked: (id: Long) -> Unit, |
|
||||||
onDoneChanged: (id: Long, isDone: Boolean) -> Unit, |
|
||||||
onDeleteItemClicked: (id: Long) -> Unit |
|
||||||
) { |
|
||||||
Row(modifier = Modifier.clickable(onClick = { onItemClicked(item.id) })) { |
|
||||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||||
|
|
||||||
Checkbox( |
|
||||||
checked = item.isDone, |
|
||||||
modifier = Modifier.align(Alignment.CenterVertically), |
|
||||||
onCheckedChange = { onDoneChanged(item.id, it) } |
|
||||||
) |
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||||
|
|
||||||
Text( |
|
||||||
text = AnnotatedString(item.text), |
|
||||||
modifier = Modifier.weight(1F).align(Alignment.CenterVertically), |
|
||||||
maxLines = 1, |
|
||||||
overflow = TextOverflow.Ellipsis |
|
||||||
) |
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||||
|
|
||||||
IconButton(onClick = { onDeleteItemClicked(item.id) }) { |
|
||||||
Icon( |
|
||||||
imageVector = Icons.Default.Delete, |
|
||||||
contentDescription = null |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(MARGIN_SCROLLBAR)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class) |
|
||||||
@Composable |
|
||||||
private fun TodoInput( |
|
||||||
text: String, |
|
||||||
onTextChanged: (String) -> Unit, |
|
||||||
onAddClicked: () -> Unit |
|
||||||
) { |
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp)) { |
|
||||||
OutlinedTextField( |
|
||||||
value = text, |
|
||||||
modifier = Modifier.weight(weight = 1F).onKeyEvent(onKeyUp(Key.Enter, onAddClicked)), |
|
||||||
onValueChange = onTextChanged, |
|
||||||
label = { Text(text = "Add a todo") } |
|
||||||
) |
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp)) |
|
||||||
|
|
||||||
IconButton(onClick = onAddClicked) { |
|
||||||
Icon( |
|
||||||
imageVector = Icons.Default.Add, |
|
||||||
contentDescription = null |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
@file:Suppress("EXPERIMENTAL_API_USAGE") |
|
||||||
|
|
||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.plus |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.scale |
|
||||||
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation |
|
||||||
import example.todo.common.root.TodoRoot |
|
||||||
import example.todo.common.root.TodoRoot.Child |
|
||||||
|
|
||||||
@Composable |
|
||||||
fun TodoRootContent(component: TodoRoot) { |
|
||||||
Children( |
|
||||||
stack = component.childStack, |
|
||||||
animation = stackAnimation(fade() + scale()), |
|
||||||
) { |
|
||||||
when (val child = it.instance) { |
|
||||||
is Child.Main -> TodoMainContent(child.component) |
|
||||||
is Child.Edit -> TodoEditContent(child.component) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi |
|
||||||
import androidx.compose.foundation.lazy.LazyListState |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import androidx.compose.ui.Modifier |
|
||||||
import androidx.compose.ui.unit.Dp |
|
||||||
import androidx.compose.ui.unit.dp |
|
||||||
|
|
||||||
actual val MARGIN_SCROLLBAR: Dp = 8.dp |
|
||||||
|
|
||||||
actual typealias ScrollbarAdapter = androidx.compose.foundation.v2.ScrollbarAdapter |
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class) |
|
||||||
@Composable |
|
||||||
actual fun rememberScrollbarAdapter( |
|
||||||
scrollState: LazyListState, |
|
||||||
itemCount: Int, |
|
||||||
averageItemSize: Dp |
|
||||||
): ScrollbarAdapter = |
|
||||||
androidx.compose.foundation.rememberScrollbarAdapter( |
|
||||||
scrollState = scrollState |
|
||||||
) |
|
||||||
|
|
||||||
@Composable |
|
||||||
actual fun VerticalScrollbar( |
|
||||||
modifier: Modifier, |
|
||||||
adapter: ScrollbarAdapter |
|
||||||
) { |
|
||||||
androidx.compose.foundation.VerticalScrollbar( |
|
||||||
modifier = modifier, |
|
||||||
adapter = adapter |
|
||||||
) |
|
||||||
} |
|
@ -1,28 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import com.arkivanov.decompose.value.MutableValue |
|
||||||
import com.arkivanov.decompose.value.Value |
|
||||||
import example.todo.common.edit.TodoEdit |
|
||||||
import example.todo.common.edit.TodoEdit.Model |
|
||||||
|
|
||||||
@Composable |
|
||||||
@Preview |
|
||||||
fun TodoEditContentPreview() { |
|
||||||
TodoEditContent(TodoEditPreview()) |
|
||||||
} |
|
||||||
|
|
||||||
class TodoEditPreview : TodoEdit { |
|
||||||
override val models: Value<Model> = |
|
||||||
MutableValue( |
|
||||||
Model( |
|
||||||
text = "Some text", |
|
||||||
isDone = true |
|
||||||
) |
|
||||||
) |
|
||||||
|
|
||||||
override fun onTextChanged(text: String) {} |
|
||||||
override fun onDoneChanged(isDone: Boolean) {} |
|
||||||
override fun onCloseClicked() {} |
|
||||||
} |
|
@ -1,37 +0,0 @@ |
|||||||
package example.todo.common.ui |
|
||||||
|
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview |
|
||||||
import androidx.compose.runtime.Composable |
|
||||||
import com.arkivanov.decompose.value.MutableValue |
|
||||||
import com.arkivanov.decompose.value.Value |
|
||||||
import example.todo.common.main.TodoItem |
|
||||||
import example.todo.common.main.TodoMain |
|
||||||
import example.todo.common.main.TodoMain.Model |
|
||||||
|
|
||||||
@Preview |
|
||||||
@Composable |
|
||||||
fun TodoMainContentPreview() { |
|
||||||
TodoMainContent(TodoMainPreview()) |
|
||||||
} |
|
||||||
|
|
||||||
class TodoMainPreview : TodoMain { |
|
||||||
override val models: Value<Model> = |
|
||||||
MutableValue( |
|
||||||
Model( |
|
||||||
items = List(5) { index -> |
|
||||||
TodoItem( |
|
||||||
id = index.toLong(), |
|
||||||
text = "Item $index", |
|
||||||
isDone = index % 2 == 0 |
|
||||||
) |
|
||||||
}, |
|
||||||
text = "Some text" |
|
||||||
) |
|
||||||
) |
|
||||||
|
|
||||||
override fun onItemClicked(id: Long) {} |
|
||||||
override fun onItemDoneChanged(id: Long, isDone: Boolean) {} |
|
||||||
override fun onItemDeleteClicked(id: Long) {} |
|
||||||
override fun onInputTextChanged(text: String) {} |
|
||||||
override fun onAddItemClicked() {} |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
plugins { |
|
||||||
id("multiplatform-setup") |
|
||||||
id("android-setup") |
|
||||||
id("com.squareup.sqldelight") |
|
||||||
} |
|
||||||
|
|
||||||
sqldelight { |
|
||||||
database("TodoDatabase") { |
|
||||||
packageName = "example.todo.database" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
kotlin { |
|
||||||
sourceSets { |
|
||||||
commonMain { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.Badoo.Reaktive.reaktive) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
androidMain { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.Squareup.SQLDelight.androidDriver) |
|
||||||
implementation(Deps.Squareup.SQLDelight.sqliteDriver) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
desktopMain { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.Squareup.SQLDelight.sqliteDriver) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
iosMain { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.Squareup.SQLDelight.nativeDriver) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
jsMain { |
|
||||||
dependencies { |
|
||||||
implementation(Deps.Squareup.SQLDelight.sqljsDriver) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,2 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<manifest package="example.todo.common.database"/> |
|
@ -1,14 +0,0 @@ |
|||||||
package example.todo.common.database |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.squareup.sqldelight.android.AndroidSqliteDriver |
|
||||||
import com.squareup.sqldelight.db.SqlDriver |
|
||||||
import example.todo.database.TodoDatabase |
|
||||||
|
|
||||||
@Suppress("FunctionName") // FactoryFunction |
|
||||||
fun TodoDatabaseDriver(context: Context): SqlDriver = |
|
||||||
AndroidSqliteDriver( |
|
||||||
schema = TodoDatabase.Schema, |
|
||||||
context = context, |
|
||||||
name = "TodoDatabase.db" |
|
||||||
) |
|
@ -1,91 +0,0 @@ |
|||||||
package example.todo.common.database |
|
||||||
|
|
||||||
import com.badoo.reaktive.base.setCancellable |
|
||||||
import com.badoo.reaktive.completable.Completable |
|
||||||
import com.badoo.reaktive.maybe.Maybe |
|
||||||
import com.badoo.reaktive.observable.Observable |
|
||||||
import com.badoo.reaktive.observable.autoConnect |
|
||||||
import com.badoo.reaktive.observable.firstOrError |
|
||||||
import com.badoo.reaktive.observable.map |
|
||||||
import com.badoo.reaktive.observable.observable |
|
||||||
import com.badoo.reaktive.observable.observeOn |
|
||||||
import com.badoo.reaktive.observable.replay |
|
||||||
import com.badoo.reaktive.scheduler.ioScheduler |
|
||||||
import com.badoo.reaktive.single.Single |
|
||||||
import com.badoo.reaktive.single.asCompletable |
|
||||||
import com.badoo.reaktive.single.asObservable |
|
||||||
import com.badoo.reaktive.single.doOnBeforeSuccess |
|
||||||
import com.badoo.reaktive.single.flatMapObservable |
|
||||||
import com.badoo.reaktive.single.map |
|
||||||
import com.badoo.reaktive.single.mapNotNull |
|
||||||
import com.badoo.reaktive.single.observeOn |
|
||||||
import com.badoo.reaktive.single.singleOf |
|
||||||
import com.squareup.sqldelight.Query |
|
||||||
import com.squareup.sqldelight.db.SqlDriver |
|
||||||
import example.todo.database.TodoDatabase |
|
||||||
|
|
||||||
class DefaultTodoSharedDatabase(driver: Single<SqlDriver>) : TodoSharedDatabase { |
|
||||||
|
|
||||||
constructor(driver: SqlDriver) : this(singleOf(driver)) |
|
||||||
|
|
||||||
private val queries: Single<TodoDatabaseQueries> = |
|
||||||
driver |
|
||||||
.map { TodoDatabase(it).todoDatabaseQueries } |
|
||||||
.asObservable() |
|
||||||
.replay() |
|
||||||
.autoConnect() |
|
||||||
.firstOrError() |
|
||||||
|
|
||||||
override fun observeAll(): Observable<List<TodoItemEntity>> = |
|
||||||
query(TodoDatabaseQueries::selectAll) |
|
||||||
.observe { it.executeAsList() } |
|
||||||
|
|
||||||
override fun select(id: Long): Maybe<TodoItemEntity> = |
|
||||||
query { it.select(id = id) } |
|
||||||
.mapNotNull { it.executeAsOneOrNull() } |
|
||||||
|
|
||||||
override fun add(text: String): Completable = |
|
||||||
execute { it.add(text = text) } |
|
||||||
|
|
||||||
override fun setText(id: Long, text: String): Completable = |
|
||||||
execute { it.setText(id = id, text = text) } |
|
||||||
|
|
||||||
override fun setDone(id: Long, isDone: Boolean): Completable = |
|
||||||
execute { it.setDone(id = id, isDone = isDone) } |
|
||||||
|
|
||||||
override fun delete(id: Long): Completable = |
|
||||||
execute { it.delete(id = id) } |
|
||||||
|
|
||||||
override fun clear(): Completable = |
|
||||||
execute { it.clear() } |
|
||||||
|
|
||||||
private fun <T : Any> query(query: (TodoDatabaseQueries) -> Query<T>): Single<Query<T>> = |
|
||||||
queries |
|
||||||
.observeOn(ioScheduler) |
|
||||||
.map(query) |
|
||||||
|
|
||||||
private fun execute(query: (TodoDatabaseQueries) -> Unit): Completable = |
|
||||||
queries |
|
||||||
.observeOn(ioScheduler) |
|
||||||
.doOnBeforeSuccess(query) |
|
||||||
.asCompletable() |
|
||||||
|
|
||||||
private fun <T : Any, R> Single<Query<T>>.observe(get: (Query<T>) -> R): Observable<R> = |
|
||||||
flatMapObservable { it.observed() } |
|
||||||
.observeOn(ioScheduler) |
|
||||||
.map(get) |
|
||||||
|
|
||||||
private fun <T : Any> Query<T>.observed(): Observable<Query<T>> = |
|
||||||
observable { emitter -> |
|
||||||
val listener = |
|
||||||
object : Query.Listener { |
|
||||||
override fun queryResultsChanged() { |
|
||||||
emitter.onNext(this@observed) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
emitter.onNext(this@observed) |
|
||||||
addListener(listener) |
|
||||||
emitter.setCancellable { removeListener(listener) } |
|
||||||
} |
|
||||||
} |
|
@ -1,105 +0,0 @@ |
|||||||
package example.todo.common.database |
|
||||||
|
|
||||||
import com.badoo.reaktive.base.invoke |
|
||||||
import com.badoo.reaktive.completable.Completable |
|
||||||
import com.badoo.reaktive.completable.completableFromFunction |
|
||||||
import com.badoo.reaktive.completable.observeOn |
|
||||||
import com.badoo.reaktive.maybe.Maybe |
|
||||||
import com.badoo.reaktive.maybe.observeOn |
|
||||||
import com.badoo.reaktive.observable.Observable |
|
||||||
import com.badoo.reaktive.observable.map |
|
||||||
import com.badoo.reaktive.observable.observeOn |
|
||||||
import com.badoo.reaktive.scheduler.Scheduler |
|
||||||
import com.badoo.reaktive.single.notNull |
|
||||||
import com.badoo.reaktive.single.singleFromFunction |
|
||||||
import com.badoo.reaktive.subject.behavior.BehaviorSubject |
|
||||||
|
|
||||||
// There were problems when using real database in JS tests, hence the in-memory test implementation |
|
||||||
class TestTodoSharedDatabase( |
|
||||||
private val scheduler: Scheduler |
|
||||||
) : TodoSharedDatabase { |
|
||||||
|
|
||||||
private val itemsSubject = BehaviorSubject<Map<Long, TodoItemEntity>>(emptyMap()) |
|
||||||
private val itemsObservable = itemsSubject.observeOn(scheduler) |
|
||||||
val testing: Testing = Testing() |
|
||||||
|
|
||||||
override fun observeAll(): Observable<List<TodoItemEntity>> = |
|
||||||
itemsObservable.map { it.values.toList() } |
|
||||||
|
|
||||||
override fun select(id: Long): Maybe<TodoItemEntity> = |
|
||||||
singleFromFunction { testing.select(id = id) } |
|
||||||
.notNull() |
|
||||||
.observeOn(scheduler) |
|
||||||
|
|
||||||
override fun add(text: String): Completable = |
|
||||||
execute { testing.add(text = text) } |
|
||||||
|
|
||||||
override fun setText(id: Long, text: String): Completable = |
|
||||||
execute { testing.setText(id = id, text = text) } |
|
||||||
|
|
||||||
override fun setDone(id: Long, isDone: Boolean): Completable = |
|
||||||
execute { testing.setDone(id = id, isDone = isDone) } |
|
||||||
|
|
||||||
override fun delete(id: Long): Completable = |
|
||||||
execute { testing.delete(id = id) } |
|
||||||
|
|
||||||
override fun clear(): Completable = |
|
||||||
execute { testing.clear() } |
|
||||||
|
|
||||||
private fun execute(block: () -> Unit): Completable = |
|
||||||
completableFromFunction(block) |
|
||||||
.observeOn(scheduler) |
|
||||||
|
|
||||||
inner class Testing { |
|
||||||
fun select(id: Long): TodoItemEntity? = |
|
||||||
itemsSubject.value[id] |
|
||||||
|
|
||||||
fun selectRequired(id: Long): TodoItemEntity = |
|
||||||
requireNotNull(select(id = id)) |
|
||||||
|
|
||||||
fun add(text: String) { |
|
||||||
updateItems { items -> |
|
||||||
val nextId = items.keys.maxOrNull()?.plus(1L) ?: 1L |
|
||||||
|
|
||||||
val item = |
|
||||||
TodoItemEntity( |
|
||||||
id = nextId, |
|
||||||
orderNum = items.size.toLong(), |
|
||||||
text = text, |
|
||||||
isDone = false |
|
||||||
) |
|
||||||
|
|
||||||
items + (nextId to item) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun setText(id: Long, text: String) { |
|
||||||
updateItem(id = id) { it.copy(text = text) } |
|
||||||
} |
|
||||||
|
|
||||||
fun setDone(id: Long, isDone: Boolean) { |
|
||||||
updateItem(id = id) { it.copy(isDone = isDone) } |
|
||||||
} |
|
||||||
|
|
||||||
fun delete(id: Long) { |
|
||||||
updateItems { it - id } |
|
||||||
} |
|
||||||
|
|
||||||
fun clear() { |
|
||||||
updateItems { emptyMap() } |
|
||||||
} |
|
||||||
|
|
||||||
fun getLastInsertId(): Long? = |
|
||||||
itemsSubject.value.values.lastOrNull()?.id |
|
||||||
|
|
||||||
private fun updateItems(func: (Map<Long, TodoItemEntity>) -> Map<Long, TodoItemEntity>) { |
|
||||||
itemsSubject(func(itemsSubject.value)) |
|
||||||
} |
|
||||||
|
|
||||||
private fun updateItem(id: Long, func: (TodoItemEntity) -> TodoItemEntity) { |
|
||||||
updateItems { |
|
||||||
it + (id to it.getValue(id).let(func)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,22 +0,0 @@ |
|||||||
package example.todo.common.database |
|
||||||
|
|
||||||
import com.badoo.reaktive.completable.Completable |
|
||||||
import com.badoo.reaktive.maybe.Maybe |
|
||||||
import com.badoo.reaktive.observable.Observable |
|
||||||
|
|
||||||
interface TodoSharedDatabase { |
|
||||||
|
|
||||||
fun observeAll(): Observable<List<TodoItemEntity>> |
|
||||||
|
|
||||||
fun select(id: Long): Maybe<TodoItemEntity> |
|
||||||
|
|
||||||
fun add(text: String): Completable |
|
||||||
|
|
||||||
fun setText(id: Long, text: String): Completable |
|
||||||
|
|
||||||
fun setDone(id: Long, isDone: Boolean): Completable |
|
||||||
|
|
||||||
fun delete(id: Long): Completable |
|
||||||
|
|
||||||
fun clear(): Completable |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
CREATE TABLE IF NOT EXISTS TodoItemEntity ( |
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
||||||
orderNum INTEGER NOT NULL, |
|
||||||
text TEXT NOT NULL, |
|
||||||
isDone INTEGER AS Boolean NOT NULL DEFAULT 0 |
|
||||||
); |
|
||||||
|
|
||||||
selectAll: |
|
||||||
SELECT * |
|
||||||
FROM TodoItemEntity; |
|
||||||
|
|
||||||
select: |
|
||||||
SELECT * |
|
||||||
FROM TodoItemEntity |
|
||||||
WHERE id = :id; |
|
||||||
|
|
||||||
add: |
|
||||||
INSERT INTO TodoItemEntity (orderNum, text) |
|
||||||
VALUES ((CASE (SELECT COUNT(*) FROM TodoItemEntity) WHEN 0 THEN 1 ELSE (SELECT MAX(orderNum)+1 FROM TodoItemEntity) END), :text); |
|
||||||
|
|
||||||
setText: |
|
||||||
UPDATE TodoItemEntity |
|
||||||
SET text = :text |
|
||||||
WHERE id = :id; |
|
||||||
|
|
||||||
setDone: |
|
||||||
UPDATE TodoItemEntity |
|
||||||
SET isDone = :isDone |
|
||||||
WHERE id = :id; |
|
||||||
|
|
||||||
delete: |
|
||||||
DELETE FROM TodoItemEntity |
|
||||||
WHERE id = :id; |
|
||||||
|
|
||||||
getLastInsertId: |
|
||||||
SELECT last_insert_rowid(); |
|
||||||
|
|
||||||
clear: |
|
||||||
DELETE FROM TodoItemEntity; |
|
@ -1,15 +0,0 @@ |
|||||||
package example.todo.common.database |
|
||||||
|
|
||||||
import com.squareup.sqldelight.db.SqlDriver |
|
||||||
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver |
|
||||||
import example.todo.database.TodoDatabase |
|
||||||
import java.io.File |
|
||||||
|
|
||||||
@Suppress("FunctionName") // FactoryFunction |
|
||||||
fun TodoDatabaseDriver(): SqlDriver { |
|
||||||
val databasePath = File(System.getProperty("java.io.tmpdir"), "ComposeTodoDatabase.db") |
|
||||||
val driver = JdbcSqliteDriver(url = "jdbc:sqlite:${databasePath.absolutePath}") |
|
||||||
TodoDatabase.Schema.create(driver) |
|
||||||
|
|
||||||
return driver |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
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") |
|
@ -1,10 +0,0 @@ |
|||||||
package example.todo.common.database |
|
||||||
|
|
||||||
import com.badoo.reaktive.promise.asSingle |
|
||||||
import com.badoo.reaktive.single.Single |
|
||||||
import com.squareup.sqldelight.db.SqlDriver |
|
||||||
import com.squareup.sqldelight.drivers.sqljs.initSqlDriver |
|
||||||
import example.todo.database.TodoDatabase |
|
||||||
|
|
||||||
fun todoDatabaseDriver(): Single<SqlDriver> = |
|
||||||
initSqlDriver(TodoDatabase.Schema).asSingle() |
|
@ -1,19 +0,0 @@ |
|||||||
plugins { |
|
||||||
id("multiplatform-setup") |
|
||||||
id("android-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.edit"/> |
|
@ -1,23 +0,0 @@ |
|||||||
package example.todo.common.edit |
|
||||||
|
|
||||||
import com.arkivanov.decompose.value.Value |
|
||||||
|
|
||||||
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 |
|
||||||
) |
|
||||||
|
|
||||||
sealed class Output { |
|
||||||
object Finished : Output() |
|
||||||
} |
|
||||||
} |
|
@ -1,6 +0,0 @@ |
|||||||
package example.todo.common.edit |
|
||||||
|
|
||||||
internal data class TodoItem( |
|
||||||
val text: String, |
|
||||||
val isDone: Boolean |
|
||||||
) |
|
@ -1,12 +0,0 @@ |
|||||||
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 |
|
||||||
) |
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
package example.todo.common.edit.integration |
|
||||||
|
|
||||||
import com.arkivanov.decompose.ComponentContext |
|
||||||
import com.arkivanov.decompose.value.Value |
|
||||||
import com.arkivanov.decompose.value.operator.map |
|
||||||
import com.arkivanov.mvikotlin.core.store.StoreFactory |
|
||||||
import com.badoo.reaktive.base.Consumer |
|
||||||
import com.badoo.reaktive.base.invoke |
|
||||||
import example.todo.common.database.TodoSharedDatabase |
|
||||||
import example.todo.common.edit.TodoEdit |
|
||||||
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.utils.asValue |
|
||||||
import example.todo.common.utils.getStore |
|
||||||
|
|
||||||
class TodoEditComponent( |
|
||||||
componentContext: ComponentContext, |
|
||||||
storeFactory: StoreFactory, |
|
||||||
database: TodoSharedDatabase, |
|
||||||
itemId: Long, |
|
||||||
private val output: Consumer<Output> |
|
||||||
) : TodoEdit, ComponentContext by componentContext { |
|
||||||
|
|
||||||
private val store = |
|
||||||
instanceKeeper.getStore { |
|
||||||
TodoEditStoreProvider( |
|
||||||
storeFactory = storeFactory, |
|
||||||
database = TodoEditStoreDatabase(database = database), |
|
||||||
id = itemId |
|
||||||
).provide() |
|
||||||
} |
|
||||||
|
|
||||||
override val models: Value<Model> = store.asValue().map(stateToModel) |
|
||||||
|
|
||||||
override fun onTextChanged(text: String) { |
|
||||||
store.accept(Intent.SetText(text = text)) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onDoneChanged(isDone: Boolean) { |
|
||||||
store.accept(Intent.SetDone(isDone = isDone)) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onCloseClicked() { |
|
||||||
output(Output.Finished) |
|
||||||
} |
|
||||||
} |
|
@ -1,31 +0,0 @@ |
|||||||
package example.todo.common.edit.integration |
|
||||||
|
|
||||||
import com.badoo.reaktive.completable.Completable |
|
||||||
import com.badoo.reaktive.maybe.Maybe |
|
||||||
import com.badoo.reaktive.maybe.map |
|
||||||
import example.todo.common.database.TodoItemEntity |
|
||||||
import example.todo.common.database.TodoSharedDatabase |
|
||||||
import example.todo.common.edit.TodoItem |
|
||||||
import example.todo.common.edit.store.TodoEditStoreProvider.Database |
|
||||||
|
|
||||||
internal class TodoEditStoreDatabase( |
|
||||||
private val database: TodoSharedDatabase |
|
||||||
) : Database { |
|
||||||
|
|
||||||
override fun load(id: Long): Maybe<TodoItem> = |
|
||||||
database |
|
||||||
.select(id = id) |
|
||||||
.map { it.toItem() } |
|
||||||
|
|
||||||
private fun TodoItemEntity.toItem(): TodoItem = |
|
||||||
TodoItem( |
|
||||||
text = text, |
|
||||||
isDone = isDone |
|
||||||
) |
|
||||||
|
|
||||||
override fun setText(id: Long, text: String): Completable = |
|
||||||
database.setText(id = id, text = text) |
|
||||||
|
|
||||||
override fun setDone(id: Long, isDone: Boolean): Completable = |
|
||||||
database.setDone(id = id, isDone = isDone) |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
package example.todo.common.edit.store |
|
||||||
|
|
||||||
import com.arkivanov.mvikotlin.core.store.Store |
|
||||||
import example.todo.common.edit.TodoItem |
|
||||||
import example.todo.common.edit.store.TodoEditStore.Intent |
|
||||||
import example.todo.common.edit.store.TodoEditStore.Label |
|
||||||
import example.todo.common.edit.store.TodoEditStore.State |
|
||||||
|
|
||||||
internal interface TodoEditStore : Store<Intent, State, Label> { |
|
||||||
|
|
||||||
sealed class Intent { |
|
||||||
data class SetText(val text: String) : Intent() |
|
||||||
data class SetDone(val isDone: Boolean) : Intent() |
|
||||||
} |
|
||||||
|
|
||||||
data class State( |
|
||||||
val text: String = "", |
|
||||||
val isDone: Boolean = false |
|
||||||
) |
|
||||||
|
|
||||||
sealed class Label { |
|
||||||
data class Changed(val item: TodoItem) : Label() |
|
||||||
} |
|
||||||
} |
|
@ -1,83 +0,0 @@ |
|||||||
package example.todo.common.edit.store |
|
||||||
|
|
||||||
import com.arkivanov.mvikotlin.core.store.Reducer |
|
||||||
import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper |
|
||||||
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 com.badoo.reaktive.maybe.Maybe |
|
||||||
import com.badoo.reaktive.maybe.map |
|
||||||
import com.badoo.reaktive.maybe.observeOn |
|
||||||
import com.badoo.reaktive.scheduler.mainScheduler |
|
||||||
import example.todo.common.edit.TodoItem |
|
||||||
import example.todo.common.edit.store.TodoEditStore.Intent |
|
||||||
import example.todo.common.edit.store.TodoEditStore.Label |
|
||||||
import example.todo.common.edit.store.TodoEditStore.State |
|
||||||
|
|
||||||
internal class TodoEditStoreProvider( |
|
||||||
private val storeFactory: StoreFactory, |
|
||||||
private val database: Database, |
|
||||||
private val id: Long |
|
||||||
) { |
|
||||||
|
|
||||||
fun provide(): TodoEditStore = |
|
||||||
object : TodoEditStore, Store<Intent, State, Label> by storeFactory.create( |
|
||||||
name = "EditStore", |
|
||||||
initialState = State(), |
|
||||||
bootstrapper = SimpleBootstrapper(Unit), |
|
||||||
executorFactory = ::ExecutorImpl, |
|
||||||
reducer = ReducerImpl |
|
||||||
) {} |
|
||||||
|
|
||||||
private sealed class Msg { |
|
||||||
data class Loaded(val item: TodoItem) : Msg() |
|
||||||
data class TextChanged(val text: String) : Msg() |
|
||||||
data class DoneChanged(val isDone: Boolean) : Msg() |
|
||||||
} |
|
||||||
|
|
||||||
private inner class ExecutorImpl : ReaktiveExecutor<Intent, Unit, State, Msg, Label>() { |
|
||||||
override fun executeAction(action: Unit, getState: () -> State) { |
|
||||||
database |
|
||||||
.load(id = id) |
|
||||||
.map(Msg::Loaded) |
|
||||||
.observeOn(mainScheduler) |
|
||||||
.subscribeScoped(onSuccess = ::dispatch) |
|
||||||
} |
|
||||||
|
|
||||||
override fun executeIntent(intent: Intent, getState: () -> State) = |
|
||||||
when (intent) { |
|
||||||
is Intent.SetText -> setText(text = intent.text, state = getState()) |
|
||||||
is Intent.SetDone -> setDone(isDone = intent.isDone, state = getState()) |
|
||||||
} |
|
||||||
|
|
||||||
private fun setText(text: String, state: State) { |
|
||||||
dispatch(Msg.TextChanged(text = text)) |
|
||||||
publish(Label.Changed(TodoItem(text = text, isDone = state.isDone))) |
|
||||||
database.setText(id = id, text = text).subscribeScoped() |
|
||||||
} |
|
||||||
|
|
||||||
private fun setDone(isDone: Boolean, state: State) { |
|
||||||
dispatch(Msg.DoneChanged(isDone = isDone)) |
|
||||||
publish(Label.Changed(TodoItem(text = state.text, isDone = isDone))) |
|
||||||
database.setDone(id = id, isDone = isDone).subscribeScoped() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private object ReducerImpl : Reducer<State, Msg> { |
|
||||||
override fun State.reduce(msg: Msg): State = |
|
||||||
when (msg) { |
|
||||||
is Msg.Loaded -> copy(text = msg.item.text, isDone = msg.item.isDone) |
|
||||||
is Msg.TextChanged -> copy(text = msg.text) |
|
||||||
is Msg.DoneChanged -> copy(isDone = msg.isDone) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
interface Database { |
|
||||||
fun load(id: Long): Maybe<TodoItem> |
|
||||||
|
|
||||||
fun setText(id: Long, text: String): Completable |
|
||||||
|
|
||||||
fun setDone(id: Long, isDone: Boolean): Completable |
|
||||||
} |
|
||||||
} |
|