From 460c728ad554fa5c9bac6205d6c4e84636999622 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Fri, 27 Sep 2024 20:44:46 -0500 Subject: [PATCH 01/28] remove google run project file --- api/project.toml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 api/project.toml diff --git a/api/project.toml b/api/project.toml deleted file mode 100644 index 2f4a4524..00000000 --- a/api/project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[build.env]] -name = "GOOGLE_RUNTIME_VERSION" -value = "17" From f8e55b3278d24cba0ed233227b1fb952956b6b6c Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Fri, 27 Sep 2024 20:50:56 -0500 Subject: [PATCH 02/28] replace placeholder text in API --- api/build.gradle.kts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 268e1ebd..3c1c1da6 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -33,27 +33,21 @@ kotlin { } application { - mainClass = "org.pointyware.replace-me.api.ServerKt" + mainClass = "org.pointyware.commonsense.api.ServerKt" } ktor { fatJar { - archiveFileName = "replace-me-API-${version}.jar" + archiveFileName = "Common-Sense-API-${version}.jar" } } -//publishing { -// publications { -// create("maven") { -// groupId = "org.pointyware.replace-me" -// artifactId = "replace-me-api" -// from(components["java"]) -// } -// } -// repositories { -// maven { -// val releaseURL = "artifactregistry://us-central1-maven.pkg.dev//" -// url = uri(releaseURL) -// } -// } -//} +publishing { + publications { + create("maven") { + groupId = "org.pointyware.replace-me" + artifactId = "replace-me-api" + from(components["java"]) + } + } +} From d1b4bb0d030a52a3a6179dffb27f9d6c13b4fdb2 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Fri, 27 Sep 2024 22:43:31 -0500 Subject: [PATCH 03/28] remove artifact registry --- api/build.gradle.kts | 1 - build.gradle.kts | 1 - gradle/libs.versions.toml | 2 -- 3 files changed, 4 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 3c1c1da6..76aea86c 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -3,7 +3,6 @@ plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.ktor) `maven-publish` - alias(libs.plugins.artifactRegistry) } tasks.named("distZip") { diff --git a/build.gradle.kts b/build.gradle.kts index 740b3d72..242e5671 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,6 @@ plugins { alias(libs.plugins.commonsense.koin).apply(false) alias(libs.plugins.commonsense.kmp).apply(false) - alias(libs.plugins.artifactRegistry).apply(false) } tasks.dokkaHtmlMultiModule { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32697f12..dd1b7ecb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] agp = "8.2.2" -artifactRegistry = "2.2.0" androidx-activity = "1.9.0" androidx-compose-ui = "1.6.8" compose = "1.6.10" @@ -56,7 +55,6 @@ truth = { module = "com.google.truth:truth", version.ref = "truth" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } -artifactRegistry = { id = "com.google.cloud.artifactregistry.gradle-plugin", version.ref = "artifactRegistry" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } From b33da106fe9b534caf8bf1a691d1d73c13c14fa1 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 00:09:46 -0500 Subject: [PATCH 04/28] Upgrade koin --- gradle/libs.versions.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd1b7ecb..6d28aeda 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,8 @@ androidx-compose-ui = "1.6.8" compose = "1.6.10" dokka = "1.9.20" jupiter = "5.10.2" -koin = "3.5.3" +#noinspection GradleDependency +koin = "3.5.6" koin-bom = "3.5.3" kotlin = "2.0.20" kotlinx-datetime = "0.5.0" From fd2b43f4cda47a5876f2614023541f49160cf384 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 00:10:01 -0500 Subject: [PATCH 05/28] Add mockk to jvmSharedTest source set --- app-shared/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app-shared/build.gradle.kts b/app-shared/build.gradle.kts index 9a730202..c09eef39 100644 --- a/app-shared/build.gradle.kts +++ b/app-shared/build.gradle.kts @@ -77,6 +77,8 @@ kotlin { dependsOn(commonTest) dependencies { implementation(libs.koin.test) + + implementation(libs.mockk) } } From 3632bccdc0e98a1bcd3b4b506bb7821c820bd8d4 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 00:10:41 -0500 Subject: [PATCH 06/28] Add setupKoin function for platforms --- app-shared/src/commonMain/kotlin/di/AppModule.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app-shared/src/commonMain/kotlin/di/AppModule.kt b/app-shared/src/commonMain/kotlin/di/AppModule.kt index eb96408b..88568fca 100644 --- a/app-shared/src/commonMain/kotlin/di/AppModule.kt +++ b/app-shared/src/commonMain/kotlin/di/AppModule.kt @@ -1,5 +1,6 @@ package org.pointyware.commonsense.shared.di +import org.koin.core.context.startKoin import org.koin.core.module.Module import org.koin.dsl.module import org.pointyware.commonsense.core.data.di.dataModule @@ -15,6 +16,13 @@ import org.pointyware.commonsense.shared.FileViewModel import org.pointyware.commonsense.shared.home.di.homeModule +fun setupKoin(platformModule: Module = module{}) = startKoin { + modules( + appModule(), + platformModule + ) +} + fun appModule(): Module = module { includes( coreModule(), From f9c3af8337540ced84ff03a73ff6794cbc897a3e Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 00:11:20 -0500 Subject: [PATCH 07/28] replace explicit startKoin with setupKoin function --- app-android/src/main/java/CSApplication.kt | 15 +++++++-------- app-android/src/main/java/di/Module.kt | 7 ++++++- app-desktop/src/main/kotlin/Main.kt | 8 ++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app-android/src/main/java/CSApplication.kt b/app-android/src/main/java/CSApplication.kt index a127aecf..6df181c9 100644 --- a/app-android/src/main/java/CSApplication.kt +++ b/app-android/src/main/java/CSApplication.kt @@ -1,9 +1,9 @@ package org.pointyware.commonsense.android import android.app.Application -import org.koin.core.context.startKoin +import org.koin.dsl.module import org.pointyware.commonsense.android.di.androidModule -import org.pointyware.commonsense.shared.di.appModule +import org.pointyware.commonsense.shared.di.setupKoin /** * This is the production Common Sense application; it performs production environment setup. @@ -12,11 +12,10 @@ class CSApplication: Application() { override fun onCreate() { super.onCreate() - startKoin { - modules( - androidModule(), - appModule() - ) - } + setupKoin( + platformModule = module { + includes(androidModule(this@CSApplication)) + } + ) } } diff --git a/app-android/src/main/java/di/Module.kt b/app-android/src/main/java/di/Module.kt index 6b695a05..754b80e7 100644 --- a/app-android/src/main/java/di/Module.kt +++ b/app-android/src/main/java/di/Module.kt @@ -1,5 +1,7 @@ package org.pointyware.commonsense.android.di +import android.app.Application +import android.content.Context import org.koin.dsl.module import org.pointyware.commonsense.shared.entities.SharedFileResources import org.pointyware.commonsense.shared.entities.SharedStringResources @@ -9,9 +11,12 @@ import org.pointyware.commonsense.shared.ui.SharedFontResources /** * */ -fun androidModule() = module { +fun androidModule(application: Application) = module { single { AndroidStringResources() } single { AndroidFontResources() } single { AndroidDrawableResources() } single { AndroidFileResources() } + + single { application } + single { application } } diff --git a/app-desktop/src/main/kotlin/Main.kt b/app-desktop/src/main/kotlin/Main.kt index 71377543..8c2b8fe8 100644 --- a/app-desktop/src/main/kotlin/Main.kt +++ b/app-desktop/src/main/kotlin/Main.kt @@ -15,15 +15,11 @@ import org.pointyware.commonsense.shared.CommonSenseApp import org.pointyware.commonsense.shared.FileViewModel import org.pointyware.commonsense.shared.di.appModule import org.pointyware.commonsense.shared.di.getDependencies +import org.pointyware.commonsense.shared.di.setupKoin fun main() = application { - startKoin { - modules( - desktopModule(), - appModule() - ) - } + setupKoin(platformModule = desktopModule()) val appDependencies = remember { getDependencies() } val drawableResources = remember { appDependencies.getDrawableResources() } From 388f7589132e98144c5e93f90a6866693b7fdd57 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 00:11:36 -0500 Subject: [PATCH 08/28] checkmodule of koin application --- .../kotlin/di/KoinValidationTest.kt | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app-shared/src/jvmSharedTest/kotlin/di/KoinValidationTest.kt b/app-shared/src/jvmSharedTest/kotlin/di/KoinValidationTest.kt index 9ccd9ed7..ae4536f4 100644 --- a/app-shared/src/jvmSharedTest/kotlin/di/KoinValidationTest.kt +++ b/app-shared/src/jvmSharedTest/kotlin/di/KoinValidationTest.kt @@ -1,34 +1,32 @@ package di -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngine -import org.koin.core.annotation.KoinExperimentalAPI -import org.koin.core.module.Module -import org.koin.test.check.checkKoinModules -import org.koin.test.verify.verify -import org.pointyware.commonsense.shared.di.appModule +import org.koin.core.KoinApplication +import org.koin.test.check.checkModules +import org.pointyware.commonsense.feature.ontology.di.ontologyJvmSharedModule +import org.pointyware.commonsense.shared.di.setupKoin +import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test /** + * */ class KoinValidationTest { - lateinit var topLevelModule: Module + lateinit var koinApp: KoinApplication @BeforeTest fun setUp() { - topLevelModule = appModule() + koinApp = setupKoin(platformModule = ontologyJvmSharedModule()) } - @OptIn(KoinExperimentalAPI::class) - @Test - fun verifyKoinConfiguration() { - topLevelModule.verify(extraTypes = listOf(HttpClientEngine::class, HttpClientConfig::class)) + @AfterTest + fun stopKoin() { + koinApp.close() } @Test fun checkKoinModules() { - checkKoinModules(listOf(topLevelModule)) + koinApp.checkModules() } } From d6ee5646be1e1a5cce38802df5e16e3199805a97 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:10:31 -0500 Subject: [PATCH 09/28] Add editor state enum --- .../viewmodels/CategoryExplorerViewModel.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt index 775a404d..e081a8a1 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt @@ -23,17 +23,23 @@ class CategoryExplorerViewModel( private val conceptEditorViewModel: ConceptEditorViewModel, ): ViewModel(), ConceptEditorViewModel by conceptEditorViewModel { + enum class EditorState { + Disabled, + Concept, + Category + } + private val _loadingState = MutableStateFlow(false) private val _categoryUiState = MutableStateFlow(CategoryUiState()) - private val _conceptEditorEnabled = MutableStateFlow(false) + private val _editorState = MutableStateFlow(EditorState.Disabled) val state: StateFlow get() = combine( - _loadingState, _categoryUiState, conceptEditorViewModel.editorState, _conceptEditorEnabled - ) { loading, currentCategory, conceptEditor, conceptEditorEnabled -> + _loadingState, _categoryUiState, conceptEditorViewModel.editorState, _editorState + ) { loading, currentCategory, conceptEditor, editorState -> CategoryExplorerUiState( loading = loading, currentCategory = currentCategory, - conceptEditor = if (conceptEditorEnabled) conceptEditor else null + conceptEditor = if (editorState == EditorState.Concept) conceptEditor else null ).also { Log.v("onChange state\n$it") } @@ -72,7 +78,7 @@ class CategoryExplorerViewModel( getSelectedConceptUseCase.invoke(categoryId = category.id, conceptId = conceptId) .onSuccess { conceptEditorViewModel.prepareFor(it) - _conceptEditorEnabled.value = true + _editorState.value = EditorState.Concept } .onFailure { // TODO: post error to user @@ -84,23 +90,18 @@ class CategoryExplorerViewModel( fun onAddCard() { conceptEditorViewModel.prepareFor(null) - _conceptEditorEnabled.value = true + _editorState.value = EditorState.Concept } fun onAddCategory() { - +// categoryEditorViewModel.prepareFor(null) // TODO: implement category editor + _editorState.value = EditorState.Concept } init { viewModelScope.launch { conceptEditorViewModel.onFinish.collect { - Log.v("finish") - _conceptEditorEnabled.value = false - } - } - viewModelScope.launch { - _conceptEditorEnabled.collect { - Log.v("conceptEditorEnabled: $it") + _editorState.value = EditorState.Disabled } } } From bf70b2ea6e80542bdc1b86701f9ae14979546262 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:19:44 -0500 Subject: [PATCH 10/28] Add category editor test case --- .../ontology/CategoryExplorerScreenUiTest.kt | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt b/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt index 4da273b1..5a526e51 100644 --- a/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt +++ b/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt @@ -47,7 +47,7 @@ class CategoryExplorerScreenUiTest { } @Test - fun should_display_concepts() = runComposeUiTest { + fun tapping_add_concept_should_display_concept_editor() = runComposeUiTest { /* Given: - A concept space with concepts @@ -106,4 +106,47 @@ class CategoryExplorerScreenUiTest { // TODO: add test for new concept presence } + + @Test + fun tapping_add_category_should_display_category_editor() = runComposeUiTest { + /* + Given: + - A concept space + */ + contentUnderTest() + + /* + When: + - The user taps "New Category" + Then: + - The category editor is displayed + - And the name field is empty + - And the "Save" button is disabled + */ + onNodeWithText("New Category").performClick() + + waitUntilExactlyOneExists(hasContentDescription("Category Editor")) + + /* + When: + - The user enters a name + Then: + - The "Save" button is enabled + */ + onNodeWithText("Name") + .performTextInput("Category Name") + onNodeWithText("Save") + .assertIsEnabled() + + /* + When: + - The user taps "Save" + Then: + - The category editor is dismissed + */ + onNodeWithText("Save").performClick() + waitUntilDoesNotExist(hasContentDescription("Category Editor")) + + // TODO: add test for new category presence + } } From 524e91ccedd62c6bdffc059a680d96a5547627a7 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:24:06 -0500 Subject: [PATCH 11/28] Add category editor view model to explorer view model --- .../viewmodels/CategoryEditorViewModel.kt | 26 +++++++++++++++++++ .../viewmodels/CategoryExplorerViewModel.kt | 8 ++++++ 2 files changed, 34 insertions(+) create mode 100644 feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt new file mode 100644 index 00000000..00826e5e --- /dev/null +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt @@ -0,0 +1,26 @@ +package org.pointyware.commonsense.feature.ontology.category.viewmodels + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.pointyware.commonsense.core.viewmodels.ViewModel +import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState + +/** + * Maintains the state of a Category Editor UI, reflected in [state]. + */ +interface CategoryEditorViewModel { + + val state: StateFlow +} + +/** + */ +class CategoryEditorViewModelImpl( + +): CategoryEditorViewModel, ViewModel() { + + + private val mutableState = MutableStateFlow(CategoryEditorUiState.Empty) + override val state: StateFlow + get() = TODO("Not yet implemented") +} diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt index e081a8a1..f56ab934 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt @@ -21,8 +21,16 @@ class CategoryExplorerViewModel( private val getSelectedCategoryUseCase: GetSelectedCategoryUseCase, private val getSelectedConceptUseCase: GetSelectedConceptUseCase, private val conceptEditorViewModel: ConceptEditorViewModel, + private val categoryEditorViewModel: CategoryEditorViewModel ): ViewModel(), ConceptEditorViewModel by conceptEditorViewModel { + /* + TODO: remove implementation by delegation + 1. Simplify concept editor view model signatures + 2. Do not inherit from ConceptEditorViewModel or CategoryEditorViewModel + 3. Define concept/category-specific functions in this class + */ + enum class EditorState { Disabled, Concept, From c78b7a94d8a04ed36ecf896c2f7aec0bf10d6012 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:24:59 -0500 Subject: [PATCH 12/28] replace explicit construction with inline constructor reference --- .../ontology/src/commonMain/kotlin/di/OntologyModule.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt index dd44ad44..cdfb58f8 100644 --- a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt +++ b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt @@ -1,6 +1,7 @@ package org.pointyware.commonsense.feature.ontology.di import kotlinx.serialization.json.Json +import org.koin.core.module.dsl.singleOf import org.koin.dsl.module import org.pointyware.commonsense.core.navigation.CymaticsNavController import org.pointyware.commonsense.feature.ontology.category.data.CategoryRepository @@ -93,11 +94,7 @@ fun ontologyViewModelModule() = module { ) } - single { CategoryExplorerViewModel( - get(), - get(), - get(), - ) } + singleOf(::CategoryExplorerViewModel) single { ConceptEditorViewModelImpl( get(), ) } From 5fcbe5446728a3b93dd1fc41707c6e8856aa5233 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:28:24 -0500 Subject: [PATCH 13/28] replace concept editor view model explicit constructor with inline binding --- feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt index cdfb58f8..8458c672 100644 --- a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt +++ b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt @@ -1,6 +1,7 @@ package org.pointyware.commonsense.feature.ontology.di import kotlinx.serialization.json.Json +import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.module import org.pointyware.commonsense.core.navigation.CymaticsNavController @@ -95,9 +96,7 @@ fun ontologyViewModelModule() = module { } singleOf(::CategoryExplorerViewModel) - single { ConceptEditorViewModelImpl( - get(), - ) } + singleOf(::ConceptEditorViewModelImpl) { bind() } single { CategoryCreatorViewModel( get(), get() From db128f98bcdb7e4e4f0d6e9c0fb83fdc435d171a Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:28:48 -0500 Subject: [PATCH 14/28] replace category editor view model explicit constructor with inline binding --- feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt index 8458c672..40af2362 100644 --- a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt +++ b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt @@ -11,6 +11,8 @@ import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNe import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNewConceptUseCase import org.pointyware.commonsense.feature.ontology.category.interactors.GetSelectedCategoryUseCase import org.pointyware.commonsense.feature.ontology.category.interactors.GetSelectedConceptUseCase +import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryEditorViewModel +import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryEditorViewModelImpl import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryExplorerViewModel import org.pointyware.commonsense.feature.ontology.data.ArrangementController import org.pointyware.commonsense.feature.ontology.data.ConceptEditorController @@ -97,6 +99,7 @@ fun ontologyViewModelModule() = module { singleOf(::CategoryExplorerViewModel) singleOf(::ConceptEditorViewModelImpl) { bind() } + singleOf(::CategoryEditorViewModelImpl) { bind() } single { CategoryCreatorViewModel( get(), get() From 452fd9ea30eab07b9bd480b3f69c6123614b5623 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:29:16 -0500 Subject: [PATCH 15/28] remove category creator view model binding --- feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt index 40af2362..353f77de 100644 --- a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt +++ b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt @@ -100,10 +100,6 @@ fun ontologyViewModelModule() = module { singleOf(::CategoryExplorerViewModel) singleOf(::ConceptEditorViewModelImpl) { bind() } singleOf(::CategoryEditorViewModelImpl) { bind() } - single { CategoryCreatorViewModel( - get(), - get() - ) } } fun ontologyUiModule() = module { From 2071e84e3e67927b4c394ac6f06dc0ca21988545 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:34:50 -0500 Subject: [PATCH 16/28] Swap concept creator view model for editor view model --- .../src/commonMain/kotlin/navigation/OntologyRouting.kt | 4 ++-- feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt b/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt index 63d1ff3f..278b62c6 100644 --- a/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt +++ b/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt @@ -12,10 +12,10 @@ import org.pointyware.commonsense.core.navigation.LocationRootScope import org.pointyware.commonsense.core.navigation.StaticRoute import org.pointyware.commonsense.feature.ontology.ConceptSpaceScreen import org.pointyware.commonsense.feature.ontology.category.ui.CategoryExplorerScreen +import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryEditorViewModel import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryExplorerViewModel import org.pointyware.commonsense.feature.ontology.ui.CategoryCreator import org.pointyware.commonsense.feature.ontology.ui.ConceptEditor -import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryCreatorViewModel import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorViewModel import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptSpaceViewModel @@ -59,7 +59,7 @@ fun LocationRootScope.ontologyRouting() { ) { Log.v("CategoryCreator") val koin = remember { getKoin() } - val viewModel = remember { koin.get() } + val viewModel = remember { koin.get() } val state by viewModel.state.collectAsState() CategoryCreator( diff --git a/feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt b/feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt index 88994d47..2c391580 100644 --- a/feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt +++ b/feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorUiState /** @@ -14,7 +15,7 @@ import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorUiSta */ @Composable fun CategoryCreator( - state: ConceptEditorUiState, + state: CategoryEditorUiState, modifier: Modifier = Modifier, onNameChange: (String) -> Unit, onCancel: () -> Unit, From ee7e7c721245ffd568ff1d7faadfc22e434043aa Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:35:18 -0500 Subject: [PATCH 17/28] implement category editor view model based on creator view model --- .../viewmodels/CategoryEditorViewModel.kt | 41 ++++++++++++++-- .../commonMain/kotlin/di/OntologyModule.kt | 2 - .../viewmodels/CategoryCreatorViewModel.kt | 47 ------------------- 3 files changed, 37 insertions(+), 53 deletions(-) delete mode 100644 feature/ontology/src/commonMain/kotlin/viewmodels/CategoryCreatorViewModel.kt diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt index 00826e5e..7b058bd3 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt @@ -1,26 +1,59 @@ package org.pointyware.commonsense.feature.ontology.category.viewmodels +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import org.pointyware.commonsense.core.viewmodels.ViewModel +import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNewCategoryUseCase import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState /** * Maintains the state of a Category Editor UI, reflected in [state]. */ interface CategoryEditorViewModel { - val state: StateFlow + val onFinish: SharedFlow + fun onNameChange(newName: String) + fun onConfirm() + fun onCancel() } /** */ class CategoryEditorViewModelImpl( - + private val createNewCategoryUseCase: CreateNewCategoryUseCase, ): CategoryEditorViewModel, ViewModel() { - private val mutableState = MutableStateFlow(CategoryEditorUiState.Empty) override val state: StateFlow - get() = TODO("Not yet implemented") + get() = mutableState.asStateFlow() + + private val mutableFinish = MutableSharedFlow() + override val onFinish: SharedFlow + get() = mutableFinish.asSharedFlow() + + override fun onNameChange(newName: String) { + mutableState.update { + it.copy(name = newName) + } + } + + override fun onConfirm() { + viewModelScope.launch { + val state = state.value + createNewCategoryUseCase.invoke(state.name) + mutableFinish.emit(Unit) + } + } + + override fun onCancel() { + viewModelScope.launch { + mutableFinish.emit(Unit) + } + } } diff --git a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt index 353f77de..788a5071 100644 --- a/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt +++ b/feature/ontology/src/commonMain/kotlin/di/OntologyModule.kt @@ -4,7 +4,6 @@ import kotlinx.serialization.json.Json import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.module -import org.pointyware.commonsense.core.navigation.CymaticsNavController import org.pointyware.commonsense.feature.ontology.category.data.CategoryRepository import org.pointyware.commonsense.feature.ontology.category.data.CategoryRepositoryImpl import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNewCategoryUseCase @@ -30,7 +29,6 @@ import org.pointyware.commonsense.feature.ontology.interactors.SelectFileUseCase import org.pointyware.commonsense.feature.ontology.interactors.UpdateNodeUseCase import org.pointyware.commonsense.feature.ontology.local.ConceptSpaceDataSource import org.pointyware.commonsense.feature.ontology.ui.ConceptSpaceUiStateMapper -import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryCreatorViewModel import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorViewModel import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorViewModelImpl import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptSpaceViewModel diff --git a/feature/ontology/src/commonMain/kotlin/viewmodels/CategoryCreatorViewModel.kt b/feature/ontology/src/commonMain/kotlin/viewmodels/CategoryCreatorViewModel.kt deleted file mode 100644 index 71d5f305..00000000 --- a/feature/ontology/src/commonMain/kotlin/viewmodels/CategoryCreatorViewModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.pointyware.commonsense.feature.ontology.viewmodels - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import org.pointyware.commonsense.core.navigation.CymaticsNavController -import org.pointyware.commonsense.core.viewmodels.ViewModel -import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNewCategoryUseCase - -/** - * - */ -class CategoryCreatorViewModel( - private val createNewCategoryUseCase: CreateNewCategoryUseCase, - private val navigationController: CymaticsNavController, -): ViewModel() { - - private val mutableState = MutableStateFlow(ConceptEditorUiState.Empty) - val state: StateFlow get() = mutableState.asStateFlow() - - - fun onCancel() { - navigationController.goBack() - } - - fun onConfirm() { - viewModelScope.launch { - val state = state.value - createNewCategoryUseCase.invoke(state.name) - navigationController.goBack() - } - } - - fun onNameChange(newName: String) { - mutableState.update { - it.copy(name = newName) - } - } - - fun onDescriptionChange(newDescription: String) { - mutableState.update { - it.copy(description = newDescription) - } - } -} From 4c29d69d250fcc7127cfa252fc06f61bdf5b240b Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:35:58 -0500 Subject: [PATCH 18/28] rename CategoryCreator.kt -> -Editor --- .../src/commonMain/kotlin/navigation/OntologyRouting.kt | 4 ++-- .../kotlin/ui/{CategoryCreator.kt => CategoryEditor.kt} | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) rename feature/ontology/src/commonMain/kotlin/ui/{CategoryCreator.kt => CategoryEditor.kt} (91%) diff --git a/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt b/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt index 278b62c6..c71ce968 100644 --- a/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt +++ b/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt @@ -14,7 +14,7 @@ import org.pointyware.commonsense.feature.ontology.ConceptSpaceScreen import org.pointyware.commonsense.feature.ontology.category.ui.CategoryExplorerScreen import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryEditorViewModel import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryExplorerViewModel -import org.pointyware.commonsense.feature.ontology.ui.CategoryCreator +import org.pointyware.commonsense.feature.ontology.ui.CategoryEditor import org.pointyware.commonsense.feature.ontology.ui.ConceptEditor import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorViewModel import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptSpaceViewModel @@ -62,7 +62,7 @@ fun LocationRootScope.ontologyRouting() { val viewModel = remember { koin.get() } val state by viewModel.state.collectAsState() - CategoryCreator( + CategoryEditor( state = state, modifier = Modifier.fillMaxSize(), onNameChange = viewModel::onNameChange, diff --git a/feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt b/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt similarity index 91% rename from feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt rename to feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt index 2c391580..3e5ef5de 100644 --- a/feature/ontology/src/commonMain/kotlin/ui/CategoryCreator.kt +++ b/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt @@ -8,13 +8,12 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState -import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorUiState /** * Allows a user to edit the properties of a new or existing concept. */ @Composable -fun CategoryCreator( +fun CategoryEditor( state: CategoryEditorUiState, modifier: Modifier = Modifier, onNameChange: (String) -> Unit, From b134830011864f990fcac1e7c21eb1a9b556e90a Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:44:47 -0500 Subject: [PATCH 19/28] Add CategoryEditor to explorer Screen * replace nullable editor state with sealed interface --- .../category/ui/CategoryExplorerScreen.kt | 31 +++++++++++++------ .../viewmodels/CategoryExplorerUiState.kt | 13 +++++++- .../viewmodels/CategoryExplorerViewModel.kt | 10 ++++-- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt index 95aae08d..d7418e82 100644 --- a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt +++ b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt @@ -11,7 +11,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.window.Dialog +import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryExplorerEditorState import org.pointyware.commonsense.feature.ontology.category.viewmodels.CategoryExplorerViewModel +import org.pointyware.commonsense.feature.ontology.ui.CategoryEditor import org.pointyware.commonsense.feature.ontology.ui.ConceptEditor /** @@ -34,20 +36,31 @@ fun CategoryExplorerScreen( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - state.conceptEditor?.let { conceptEditorUiState -> - Dialog( - onDismissRequest = { - viewModel.onCancel() + when (val capture = state.editorState) { + is CategoryExplorerEditorState.Concept -> { + Dialog( + onDismissRequest = { + viewModel.onCancel() + } + ) { + ConceptEditor( + state = capture.concept, + onNameChange = viewModel::onNameChange, + onDescriptionChange = viewModel::onDescriptionChange, + onConfirm = viewModel::onCommitConcept, + onCancel = viewModel::onCancel + ) } - ) { - ConceptEditor( - state = conceptEditorUiState, + } + is CategoryExplorerEditorState.Category -> { + CategoryEditor( + state = capture.category, onNameChange = viewModel::onNameChange, - onDescriptionChange = viewModel::onDescriptionChange, - onConfirm = viewModel::onCommitConcept, + onConfirm = TODO(), onCancel = viewModel::onCancel ) } + else -> { /* Show Nothing */ } } CategoryExplorer( diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerUiState.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerUiState.kt index 197526cc..71b29543 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerUiState.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerUiState.kt @@ -2,6 +2,7 @@ package org.pointyware.commonsense.feature.ontology.category.viewmodels import org.pointyware.commonsense.feature.ontology.Concept import org.pointyware.commonsense.feature.ontology.entities.Category +import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorUiState /** @@ -14,7 +15,7 @@ import org.pointyware.commonsense.feature.ontology.viewmodels.ConceptEditorUiSta data class CategoryExplorerUiState( val loading: Boolean = false, val currentCategory: CategoryUiState, - val conceptEditor: ConceptEditorUiState? = null + val editorState: CategoryExplorerEditorState = CategoryExplorerEditorState.Disabled, ) { companion object { val Loading = CategoryExplorerUiState(true, CategoryUiState()) @@ -26,3 +27,13 @@ data class CategoryUiState( val subcategories: List = emptyList(), val concepts: List = emptyList(), ) + +sealed interface CategoryExplorerEditorState { + data object Disabled : CategoryExplorerEditorState + data class Concept( + val concept: ConceptEditorUiState + ) : CategoryExplorerEditorState + data class Category( + val category: CategoryEditorUiState + ) : CategoryExplorerEditorState +} diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt index f56ab934..cbc80eff 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt @@ -42,12 +42,16 @@ class CategoryExplorerViewModel( private val _editorState = MutableStateFlow(EditorState.Disabled) val state: StateFlow get() = combine( - _loadingState, _categoryUiState, conceptEditorViewModel.editorState, _editorState - ) { loading, currentCategory, conceptEditor, editorState -> + _loadingState, _categoryUiState, conceptEditorViewModel.editorState, categoryEditorViewModel.state, _editorState + ) { loading, currentCategory, conceptEditor, categoryEditor, editorState -> CategoryExplorerUiState( loading = loading, currentCategory = currentCategory, - conceptEditor = if (editorState == EditorState.Concept) conceptEditor else null + editorState = when (editorState) { + EditorState.Concept -> CategoryExplorerEditorState.Concept(conceptEditor) + EditorState.Category -> CategoryExplorerEditorState.Category(categoryEditor) + EditorState.Disabled -> CategoryExplorerEditorState.Disabled + }, ).also { Log.v("onChange state\n$it") } From bc480a74652e7e84716211f6022011d4bea4bb16 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:50:16 -0500 Subject: [PATCH 20/28] Remove implementation by delegation 1. Simplify concept editor view model signatures 2. Do not inherit from ConceptEditorViewModel or CategoryEditorViewModel 3. Define concept/category-specific functions in this class --- .../category/ui/CategoryExplorerScreen.kt | 6 +-- .../viewmodels/CategoryEditorViewModel.kt | 8 ++++ .../viewmodels/CategoryExplorerViewModel.kt | 37 ++++++++++++++----- .../kotlin/navigation/OntologyRouting.kt | 4 +- .../viewmodels/ConceptEditorViewModel.kt | 12 +++--- 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt index d7418e82..db5ae679 100644 --- a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt +++ b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt @@ -45,7 +45,7 @@ fun CategoryExplorerScreen( ) { ConceptEditor( state = capture.concept, - onNameChange = viewModel::onNameChange, + onNameChange = viewModel::onConceptNameChange, onDescriptionChange = viewModel::onDescriptionChange, onConfirm = viewModel::onCommitConcept, onCancel = viewModel::onCancel @@ -55,8 +55,8 @@ fun CategoryExplorerScreen( is CategoryExplorerEditorState.Category -> { CategoryEditor( state = capture.category, - onNameChange = viewModel::onNameChange, - onConfirm = TODO(), + onNameChange = viewModel::onCategoryNameChange, + onConfirm = viewModel::onCommitCategory, onCancel = viewModel::onCancel ) } diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt index 7b058bd3..3bc7d8a9 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryEditorViewModel.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.pointyware.commonsense.core.viewmodels.ViewModel import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNewCategoryUseCase +import org.pointyware.commonsense.feature.ontology.entities.Category import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState /** @@ -18,6 +19,7 @@ import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiSt interface CategoryEditorViewModel { val state: StateFlow val onFinish: SharedFlow + fun prepareFor(category: Category?) fun onNameChange(newName: String) fun onConfirm() fun onCancel() @@ -37,6 +39,12 @@ class CategoryEditorViewModelImpl( override val onFinish: SharedFlow get() = mutableFinish.asSharedFlow() + override fun prepareFor(category: Category?) { + mutableState.update { + it.copy(name = category?.name ?: "") + } + } + override fun onNameChange(newName: String) { mutableState.update { it.copy(name = newName) diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt index cbc80eff..1c317b1f 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt @@ -22,14 +22,7 @@ class CategoryExplorerViewModel( private val getSelectedConceptUseCase: GetSelectedConceptUseCase, private val conceptEditorViewModel: ConceptEditorViewModel, private val categoryEditorViewModel: CategoryEditorViewModel -): ViewModel(), ConceptEditorViewModel by conceptEditorViewModel { - - /* - TODO: remove implementation by delegation - 1. Simplify concept editor view model signatures - 2. Do not inherit from ConceptEditorViewModel or CategoryEditorViewModel - 3. Define concept/category-specific functions in this class - */ +): ViewModel() { enum class EditorState { Disabled, @@ -42,7 +35,7 @@ class CategoryExplorerViewModel( private val _editorState = MutableStateFlow(EditorState.Disabled) val state: StateFlow get() = combine( - _loadingState, _categoryUiState, conceptEditorViewModel.editorState, categoryEditorViewModel.state, _editorState + _loadingState, _categoryUiState, conceptEditorViewModel.state, categoryEditorViewModel.state, _editorState ) { loading, currentCategory, conceptEditor, categoryEditor, editorState -> CategoryExplorerUiState( loading = loading, @@ -83,6 +76,14 @@ class CategoryExplorerViewModel( } } + fun onCategoryNameChange(name: String) { + categoryEditorViewModel.onNameChange(name) + } + + fun onCommitCategory() { + categoryEditorViewModel.onConfirm() + } + fun onConceptSelected(conceptId: Uuid) { _loadingState.value = true viewModelScope.launch { @@ -100,13 +101,29 @@ class CategoryExplorerViewModel( } } + fun onConceptNameChange(name: String) { + conceptEditorViewModel.onNameChange(name) + } + + fun onDescriptionChange(description: String) { + conceptEditorViewModel.onDescriptionChange(description) + } + + fun onCommitConcept() { + conceptEditorViewModel.onConfirm() + } + + fun onCancel() { + _editorState.value = EditorState.Disabled + } + fun onAddCard() { conceptEditorViewModel.prepareFor(null) _editorState.value = EditorState.Concept } fun onAddCategory() { -// categoryEditorViewModel.prepareFor(null) // TODO: implement category editor + categoryEditorViewModel.prepareFor(null) _editorState.value = EditorState.Concept } diff --git a/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt b/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt index c71ce968..51e42ea7 100644 --- a/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt +++ b/feature/ontology/src/commonMain/kotlin/navigation/OntologyRouting.kt @@ -79,14 +79,14 @@ fun LocationRootScope.ontologyRouting() { val koin = remember { getKoin() } val viewModel = remember { koin.get() } - val state by viewModel.editorState.collectAsState() + val state by viewModel.state.collectAsState() ConceptEditor( state = state, modifier = Modifier.fillMaxSize(), onNameChange = viewModel::onNameChange, onDescriptionChange = viewModel::onDescriptionChange, onCancel = viewModel::onCancel, - onConfirm = viewModel::onCommitConcept, + onConfirm = viewModel::onConfirm, ) } } diff --git a/feature/ontology/src/commonMain/kotlin/viewmodels/ConceptEditorViewModel.kt b/feature/ontology/src/commonMain/kotlin/viewmodels/ConceptEditorViewModel.kt index c4be4f06..7d3fed0c 100644 --- a/feature/ontology/src/commonMain/kotlin/viewmodels/ConceptEditorViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/viewmodels/ConceptEditorViewModel.kt @@ -13,13 +13,13 @@ import org.pointyware.commonsense.feature.ontology.Concept import org.pointyware.commonsense.feature.ontology.category.interactors.CreateNewConceptUseCase /** - * Maintains the state of a Concept Editor UI, reflected in [editorState]. + * Maintains the state of a Concept Editor UI, reflected in [state]. */ interface ConceptEditorViewModel { /** * Observable that reports the current state of the editor. */ - val editorState: StateFlow + val state: StateFlow /** * Observable that reports when the editor is closed. @@ -49,7 +49,7 @@ interface ConceptEditorViewModel { /** * Commit the changes and close the editor. */ - fun onCommitConcept() + fun onConfirm() } class ConceptEditorViewModelImpl( @@ -57,7 +57,7 @@ class ConceptEditorViewModelImpl( ): ViewModel(), ConceptEditorViewModel { private val mutableState = MutableStateFlow(ConceptEditorUiState.Empty) - override val editorState: StateFlow get() = mutableState.asStateFlow() + override val state: StateFlow get() = mutableState.asStateFlow() private val mutableOnFinish = MutableSharedFlow() override val onFinish: SharedFlow = mutableOnFinish.asSharedFlow() @@ -91,9 +91,9 @@ class ConceptEditorViewModelImpl( } } - override fun onCommitConcept() { + override fun onConfirm() { viewModelScope.launch { - val state = editorState.value + val state = state.value createNewConceptUseCase.invoke(state.name, state.description) mutableOnFinish.emit(Unit) } From 1838a164603955333667dd4d0647071d7cc246f1 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:53:05 -0500 Subject: [PATCH 21/28] Add expected content description --- feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt b/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt index 3e5ef5de..e687470b 100644 --- a/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt +++ b/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt @@ -7,6 +7,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import org.pointyware.commonsense.feature.ontology.viewmodels.CategoryEditorUiState /** @@ -22,6 +24,7 @@ fun CategoryEditor( ) { Column( modifier = modifier + .semantics { contentDescription = "Category Editor" } ) { TextField( label = { Text("Name") }, From f664c5dd1ddb85ac054087cb4c31186f2cbd6abb Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:53:52 -0500 Subject: [PATCH 22/28] Fix wrong state set --- .../kotlin/category/viewmodels/CategoryExplorerViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt index 1c317b1f..237cd929 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt @@ -124,7 +124,7 @@ class CategoryExplorerViewModel( fun onAddCategory() { categoryEditorViewModel.prepareFor(null) - _editorState.value = EditorState.Concept + _editorState.value = EditorState.Category } init { From 828d51e8d9d1546af52c3b2d3963d49326d37407 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:54:37 -0500 Subject: [PATCH 23/28] fix confirmation button with wrong text --- feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt b/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt index e687470b..8b9deffb 100644 --- a/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt +++ b/feature/ontology/src/commonMain/kotlin/ui/CategoryEditor.kt @@ -37,7 +37,7 @@ fun CategoryEditor( Text("Cancel") } Button(onClick = onConfirm) { - Text("Confirm") + Text("Save") } } } From ce24da44d59846f6e7a5c6679adb574fbfb54da3 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 12:56:14 -0500 Subject: [PATCH 24/28] observe finish events --- .../kotlin/category/viewmodels/CategoryExplorerViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt index 237cd929..ae739e76 100644 --- a/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt +++ b/feature/ontology/src/commonMain/kotlin/category/viewmodels/CategoryExplorerViewModel.kt @@ -133,5 +133,10 @@ class CategoryExplorerViewModel( _editorState.value = EditorState.Disabled } } + viewModelScope.launch { + categoryEditorViewModel.onFinish.collect { + _editorState.value = EditorState.Disabled + } + } } } From 0a7352819c1ad369e32ffe4b91cd7e2b5ef3a71e Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 13:59:42 -0500 Subject: [PATCH 25/28] wrap category editor in dialog --- .../category/ui/CategoryExplorerScreen.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt index db5ae679..88768de8 100644 --- a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt +++ b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt @@ -53,12 +53,18 @@ fun CategoryExplorerScreen( } } is CategoryExplorerEditorState.Category -> { - CategoryEditor( - state = capture.category, - onNameChange = viewModel::onCategoryNameChange, - onConfirm = viewModel::onCommitCategory, - onCancel = viewModel::onCancel - ) + Dialog( + onDismissRequest = { + viewModel.onCancel() + } + ) { + CategoryEditor( + state = capture.category, + onNameChange = viewModel::onCategoryNameChange, + onConfirm = viewModel::onCommitCategory, + onCancel = viewModel::onCancel + ) + } } else -> { /* Show Nothing */ } } From 7b51f38c59d9034af0b83ddfc15851baa9d4a8eb Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 14:25:57 -0500 Subject: [PATCH 26/28] move dialogs below constant UI --- .../category/ui/CategoryExplorerScreen.kt | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt index 88768de8..4248818e 100644 --- a/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt +++ b/feature/ontology/src/commonMain/kotlin/category/ui/CategoryExplorerScreen.kt @@ -36,6 +36,32 @@ fun CategoryExplorerScreen( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { + CategoryExplorer( + state = mappedState, + modifier = Modifier.fillMaxSize(), + onCategorySelected = viewModel::onCategorySelected, + onConceptSelected = viewModel::onConceptSelected, + ) + + Column( + modifier = Modifier.align(Alignment.BottomEnd) + ) { + Button( + onClick = viewModel::onAddCard, + ) { + Text( + text = "New Concept" + ) + } + Button( + onClick = viewModel::onAddCategory, + ) { + Text( + text = "New Category" + ) + } + } + when (val capture = state.editorState) { is CategoryExplorerEditorState.Concept -> { Dialog( @@ -68,31 +94,5 @@ fun CategoryExplorerScreen( } else -> { /* Show Nothing */ } } - - CategoryExplorer( - state = mappedState, - modifier = Modifier.fillMaxSize(), - onCategorySelected = viewModel::onCategorySelected, - onConceptSelected = viewModel::onConceptSelected, - ) - - Column( - modifier = Modifier.align(Alignment.BottomEnd) - ) { - Button( - onClick = viewModel::onAddCard, - ) { - Text( - text = "New Concept" - ) - } - Button( - onClick = viewModel::onAddCategory, - ) { - Text( - text = "New Category" - ) - } - } } } From ff72e45364f5c09d92aaecfb1a95d751e4c13373 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 15:15:57 -0500 Subject: [PATCH 27/28] Create negative test cases --- .../ontology/CategoryExplorerScreenUiTest.kt | 104 +++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt b/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt index 5a526e51..639bf177 100644 --- a/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt +++ b/feature/ontology/src/commonTest/kotlin/org/pointyware/commonsense/feature/ontology/CategoryExplorerScreenUiTest.kt @@ -107,8 +107,65 @@ class CategoryExplorerScreenUiTest { // TODO: add test for new concept presence } + /** + * User Journey: Create New Concept and Cancel + */ @Test - fun tapping_add_category_should_display_category_editor() = runComposeUiTest { + fun tapping_add_concept_should_display_concept_editor_and_cancel() = runComposeUiTest { + /* + Given: + - A concept space + */ + contentUnderTest() + + /* + When: + - The user taps "New Concept" + Then: + - The concept editor is displayed + - And the name and description fields are empty + - And the "Save" button is disabled + */ + onNodeWithText("New Concept").performClick() + + waitUntilExactlyOneExists(hasContentDescription("Concept Editor")) + onNodeWithText("Name") + .assertEditableTextEquals("") + onNodeWithText("Description") + .assertEditableTextEquals("") + onNodeWithText("Save") + .assertIsNotEnabled() + + /* + When: + - The user enters a name and description + Then: + - The "Save" button is enabled + */ + onNodeWithText("Name") + .performTextInput("Concept Name") + onNodeWithText("Description") + .performTextInput("Concept Description") + onNodeWithText("Save") + .assertIsEnabled() + + /* + When: + - The user taps "Cancel" + Then: + - The concept editor is dismissed + */ + onNodeWithText("Cancel").performClick() + waitUntilDoesNotExist(hasContentDescription("Concept Editor")) + + // TODO: add test for new concept absence + } + + /** + * User Journey: Create New Category and Confirm + */ + @Test + fun tapping_add_category_should_display_category_editor_and_confirm() = runComposeUiTest { /* Given: - A concept space @@ -149,4 +206,49 @@ class CategoryExplorerScreenUiTest { // TODO: add test for new category presence } + + /** + * User Journey: Create New Category and Cancel + */ + @Test + fun tapping_add_category_should_display_category_editor_and_cancel() = runComposeUiTest { + /* + Given: + - A concept space + */ + contentUnderTest() + + /* + When: + - The user taps "New Category" + Then: + - The category editor is displayed + - And the name field is empty + - And the "Save" button is disabled + */ + onNodeWithText("New Category").performClick() + + waitUntilExactlyOneExists(hasContentDescription("Category Editor")) + + /* + When: + - The user enters a name + Then: + - The "Save" button is enabled + */ + onNodeWithText("Name") + .performTextInput("Category Name") + onNodeWithText("Save") + .assertIsEnabled() + + /* + When: + - The user taps "Cancel" + Then: + - The category editor is dismissed + */ + onNodeWithText("Cancel").performClick() + waitUntilDoesNotExist(hasContentDescription("Category Editor")) + // TODO: add test for new category absence + } } From 597019059c2bc2fbc4b83f83ac4bd04b3bd59f17 Mon Sep 17 00:00:00 2001 From: Taush Sampley Date: Sat, 28 Sep 2024 16:25:04 -0500 Subject: [PATCH 28/28] restrict checks to jvmTests --- .github/workflows/feature-pr-checks.yml | 2 +- .github/workflows/main-pr-checks.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-pr-checks.yml b/.github/workflows/feature-pr-checks.yml index 53c58972..962c2f8a 100644 --- a/.github/workflows/feature-pr-checks.yml +++ b/.github/workflows/feature-pr-checks.yml @@ -32,6 +32,6 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: "Build: Assemble and Test entire project" - run: ./gradlew build + run: ./gradlew jvmTest # - run: ./gradlew generate coverage report, upload test/coverage reports diff --git a/.github/workflows/main-pr-checks.yml b/.github/workflows/main-pr-checks.yml index 85580091..aed27bc1 100644 --- a/.github/workflows/main-pr-checks.yml +++ b/.github/workflows/main-pr-checks.yml @@ -32,6 +32,6 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: "Build: Assemble and Test entire project" - run: ./gradlew build + run: ./gradlew jvmTest # - run: ./gradlew generate coverage report, upload test/coverage reports