From b0c79c5ab0840e40349704ec48514bff6af12a00 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:59:17 +0100 Subject: [PATCH] DROID-3039 Editor | Show types widget only for an empty title (#1936) --- .../presentation/editor/EditorViewModel.kt | 11 +- .../editor/editor/ext/ContentTextExt.kt | 18 +++ .../editor/editor/EditorInternalFlagsTest.kt | 103 ++++++++++++++++++ .../EditorObjectTypeChangeWidgetTest.kt | 17 ++- 4 files changed, 144 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index b05efe1daf..5c572ed4be 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -254,6 +254,7 @@ import com.anytypeio.anytype.core_models.TimeInMillis import com.anytypeio.anytype.core_models.TimeInSeconds import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Event.SAM.* import com.anytypeio.anytype.presentation.editor.editor.Intent.Clipboard.* +import com.anytypeio.anytype.presentation.editor.editor.ext.isAllowedToShowTypesWidget import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnDatePickerDismiss import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnDateSelected import com.anytypeio.anytype.presentation.editor.model.OnEditorDatePickerEvent.OnTodayClick @@ -7606,9 +7607,15 @@ class EditorViewModel( } } containsFlag -> { - val restrictions = orchestrator.stores.objectRestrictions.current() - if (restrictions.none { it == ObjectRestriction.TYPE_CHANGE } && isUserEditor) { + if (blocks.isAllowedToShowTypesWidget( + objectRestrictions = orchestrator.stores.objectRestrictions.current(), + isOwnerOrEditor = permission.value?.isOwnerOrEditor() == true, + objectLayout = orchestrator.stores.details.current().details[context]?.layout?.toInt() + ) + ) { setTypesWidgetVisibility(true) + } else { + Timber.d("Object doesn't allow to show types widget, skip") } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt index 55ac7d3b42..2c87145a6c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/ext/ContentTextExt.kt @@ -1,10 +1,14 @@ package com.anytypeio.anytype.presentation.editor.editor.ext import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.Block.Content import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.ext.content import com.anytypeio.anytype.core_models.ext.replaceRangeWithWord +import com.anytypeio.anytype.core_models.ext.title +import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.BuildConfig import com.anytypeio.anytype.presentation.editor.editor.Markup @@ -88,3 +92,17 @@ private fun Map.getProperObjectName(id: Id?): String? { } } +fun List.isAllowedToShowTypesWidget( + objectRestrictions: List, + isOwnerOrEditor: Boolean, + objectLayout: Int? +): Boolean { + if (objectRestrictions.any { it == ObjectRestriction.TYPE_CHANGE }) return false + if (!isOwnerOrEditor) return false + return if (objectLayout == ObjectType.Layout.NOTE.code) { + return true + } else { + return title()?.content()?.text?.isEmpty() == true + } +} + diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt index abeb9451d6..53c4e82632 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.presentation.editor.editor import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import app.cash.turbine.test import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.InternalFlags import com.anytypeio.anytype.core_models.ObjectType @@ -8,10 +9,13 @@ import com.anytypeio.anytype.core_models.ObjectTypeIds import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubHeader +import com.anytypeio.anytype.core_models.StubObjectType import com.anytypeio.anytype.core_models.StubSmartBlock import com.anytypeio.anytype.core_models.StubTitle +import com.anytypeio.anytype.presentation.editor.EditorViewModel import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule import kotlin.test.assertEquals +import kotlin.test.assertIs import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest @@ -231,4 +235,103 @@ class EditorInternalFlagsTest : EditorPresentationTestSetup() { coroutineTestRule.advanceTime(100) } + + @Test + fun `should doesn't show type widget when flag is present and title is not empty`() = runTest { + val title = StubTitle(text = "Some text") + val header = StubHeader(children = listOf(title.id)) + val page = StubSmartBlock(id = root, children = listOf(header.id)) + val document = listOf(page, header, title) + stubInterceptEvents() + + val detailsList = Block.Details( + details = mapOf( + root to Block.Fields( + mapOf( + Relations.TYPE to ObjectTypeIds.PAGE, + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.INTERNAL_FLAGS to listOf( + InternalFlags.ShouldSelectType.code.toDouble(), + ) + ) + ) + ) + ) + stubOpenDocument(document = document, details = detailsList) + stubGetObjectTypes(types = emptyList()) + stubGetDefaultObjectType() + stubFileLimitEvents() + stubSetInternalFlags() + + val vm = buildViewModel() + advanceUntilIdle() + + vm.typesWidgetState.test{ + val first = awaitItem() + assertEquals(EditorViewModel.TypesWidgetState( + items = emptyList(), + visible = false, + expanded = false + ), first) + vm.onStart(id = root, space = defaultSpace) + advanceUntilIdle() + ensureAllEventsConsumed() + } + } + + @Test + fun `should show type widget when flag is present and title is empty`() = runTest { + val title = StubTitle(text = "") + val header = StubHeader(children = listOf(title.id)) + val page = StubSmartBlock(id = root, children = listOf(header.id)) + val document = listOf(page, header, title) + stubInterceptEvents() + + val detailsList = Block.Details( + details = mapOf( + root to Block.Fields( + mapOf( + Relations.TYPE to ObjectTypeIds.PAGE, + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.INTERNAL_FLAGS to listOf( + InternalFlags.ShouldSelectType.code.toDouble(), + ) + ) + ) + ) + ) + stubOpenDocument(document = document, details = detailsList) + stubGetObjectTypes(types = emptyList()) + stubGetDefaultObjectType() + stubFileLimitEvents() + stubSetInternalFlags() + stubGetObjectTypes(listOf(StubObjectType(id = ObjectTypeIds.PAGE))) + + val vm = buildViewModel() + advanceUntilIdle() + + vm.typesWidgetState.test{ + val first = awaitItem() + assertEquals(EditorViewModel.TypesWidgetState( + items = listOf(), + visible = false, + expanded = false + ), first) + vm.onStart(id = root, space = defaultSpace) + advanceUntilIdle() + val second = awaitItem() + assertEquals(EditorViewModel.TypesWidgetState( + items = listOf(), + visible = true, + expanded = false + ), second) + val third = awaitItem() + assertEquals(EditorViewModel.TypesWidgetState( + items = listOf(EditorViewModel.TypesWidgetItem.Search), + visible = true, + expanded = false + ), third) + ensureAllEventsConsumed() + } + } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt index 4feacddf26..db0a64c492 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt @@ -63,6 +63,17 @@ class EditorObjectTypeChangeWidgetTest : EditorPresentationTestSetup() { ) ) + val title = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = Block.Content.Text.Style.TITLE + ) + ) + val featuredBlock = Block( id = "featuredRelations", fields = Block.Fields.empty(), @@ -76,17 +87,17 @@ class EditorObjectTypeChangeWidgetTest : EditorPresentationTestSetup() { type = Block.Content.Layout.Type.HEADER ), fields = Block.Fields.empty(), - children = listOf(featuredBlock.id) + children = listOf(title.id, featuredBlock.id) ) val page = Block( id = root, fields = Block.Fields(emptyMap()), content = Block.Content.Smart, - children = listOf(header.id, paragraph.id) + children = listOf(header.id, title.id, paragraph.id) ) - val doc = listOf(page, header, paragraph, featuredBlock) + val doc = listOf(page, header, title, paragraph, featuredBlock) val objectDetails = Block.Fields( mapOf(