mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 13:57:10 +09:00
Editor | Show description block based on featured relations (#1414)
This commit is contained in:
parent
84d253ce04
commit
af86afbeda
9 changed files with 255 additions and 28 deletions
|
@ -4,7 +4,7 @@
|
|||
|
||||
### Middleware ⚙
|
||||
|
||||
* Updated middleware protocol to `0.15.0-rc19`
|
||||
* Updated middleware protocol to `0.15.0-rc21`
|
||||
|
||||
## Version 0.1.7
|
||||
|
||||
|
|
|
@ -9,14 +9,13 @@ import androidx.test.filters.LargeTest
|
|||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.ext.content
|
||||
import com.anytypeio.anytype.domain.relations.Relations
|
||||
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
|
||||
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
|
||||
import com.anytypeio.anytype.features.editor.base.TestPageFragment
|
||||
import com.anytypeio.anytype.mocking.MockDataFactory
|
||||
import com.anytypeio.anytype.ui.page.PageFragment
|
||||
import com.anytypeio.anytype.utils.CoroutinesTestRule
|
||||
import com.anytypeio.anytype.utils.checkHasText
|
||||
import com.anytypeio.anytype.utils.onItemView
|
||||
import com.anytypeio.anytype.utils.rVMatcher
|
||||
import com.anytypeio.anytype.utils.*
|
||||
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
@ -52,7 +51,7 @@ class DescriptionTesting : EditorTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun shouldRenderDescriptionAfterTitle() {
|
||||
fun shouldNotRenderDescriptionAfterTitleBecauseDescriptionIsNotFeatured() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -95,6 +94,73 @@ class DescriptionTesting : EditorTestSetup() {
|
|||
|
||||
// TESTING
|
||||
|
||||
R.id.recycler.rVMatcher().apply {
|
||||
onItemView(0, R.id.title).checkHasText(
|
||||
title.content<Block.Content.Text>().text
|
||||
)
|
||||
checkIsRecyclerSize(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldRenderDescriptionAfterTitleBecauseDescriptionIsFeatured() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val description = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = "A lighthouse is a tower, building, or another type of structure designed to emit light from a system of lamps and lenses and to serve as a navigational aid for maritime pilots at sea or on inland waterways.",
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.DESCRIPTION
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id, description.id)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, description)
|
||||
|
||||
val details = Block.Details(
|
||||
mapOf(
|
||||
root to Block.Fields(
|
||||
mapOf(
|
||||
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
|
||||
"featuredRelations" to listOf(Relations.DESCRIPTION),
|
||||
"description" to description.content<Block.Content.Text>().text
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubInterceptThreadStatus()
|
||||
stubOpenDocument(
|
||||
document = document,
|
||||
details = details
|
||||
)
|
||||
|
||||
launchFragment(args)
|
||||
|
||||
// TESTING
|
||||
|
||||
R.id.recycler.rVMatcher().apply {
|
||||
onItemView(0, R.id.title).checkHasText(
|
||||
title.content<Block.Content.Text>().text
|
||||
|
@ -102,6 +168,7 @@ class DescriptionTesting : EditorTestSetup() {
|
|||
onItemView(1, R.id.tvBlockDescription).checkHasText(
|
||||
description.content<Block.Content.Text>().text
|
||||
)
|
||||
checkIsRecyclerSize(2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.test.filters.LargeTest
|
|||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Relation
|
||||
import com.anytypeio.anytype.domain.relations.Relations
|
||||
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
|
||||
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
|
||||
import com.anytypeio.anytype.features.editor.base.TestPageFragment
|
||||
|
@ -172,6 +173,117 @@ class FeaturedRelationTesting : EditorTestSetup() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldRenderTwoPrimitiveFeaturedRelationsAndExcludeDescriptionRelation() {
|
||||
|
||||
val relation1 = Relation(
|
||||
key = Relations.DESCRIPTION,
|
||||
name = "Company",
|
||||
format = Relation.Format.SHORT_TEXT,
|
||||
source = Relation.Source.values().random()
|
||||
)
|
||||
|
||||
val relation2 = Relation(
|
||||
key = MockDataFactory.randomString(),
|
||||
name = "Year",
|
||||
format = Relation.Format.NUMBER,
|
||||
source = Relation.Source.values().random()
|
||||
)
|
||||
|
||||
val relation3 = Relation(
|
||||
key = MockDataFactory.randomString(),
|
||||
name = "Phone",
|
||||
format = Relation.Format.PHONE,
|
||||
source = Relation.Source.values().random()
|
||||
)
|
||||
|
||||
val relation4 = Relation(
|
||||
key = MockDataFactory.randomString(),
|
||||
name = "Website",
|
||||
format = Relation.Format.URL,
|
||||
source = Relation.Source.values().random()
|
||||
)
|
||||
|
||||
val relation5 = Relation(
|
||||
key = MockDataFactory.randomString(),
|
||||
name = "Email",
|
||||
format = Relation.Format.EMAIL,
|
||||
source = Relation.Source.values().random()
|
||||
)
|
||||
|
||||
val value1 = "Anytype"
|
||||
val value2 = "2021"
|
||||
val value3 = "+00000000000"
|
||||
val value4 = "https://anytype.io/"
|
||||
val value5 = "team@anytype.io"
|
||||
|
||||
val customDetails = Block.Details(
|
||||
mapOf(
|
||||
root to Block.Fields(
|
||||
mapOf(
|
||||
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
|
||||
relation1.key to value1,
|
||||
relation2.key to value2,
|
||||
relation3.key to value3,
|
||||
relation4.key to value4,
|
||||
relation5.key to value5,
|
||||
"featuredRelations" to listOf(
|
||||
relation1.key,
|
||||
relation4.key,
|
||||
relation5.key
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = "Foo",
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.P
|
||||
)
|
||||
)
|
||||
|
||||
val block1 = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.FeaturedRelations
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id, paragraph.id, block1.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, paragraph, block1)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubInterceptThreadStatus()
|
||||
stubOpenDocument(
|
||||
document = document,
|
||||
details = customDetails,
|
||||
relations = listOf(relation1, relation2, relation3, relation4, relation5)
|
||||
)
|
||||
|
||||
// TESTING
|
||||
|
||||
launchFragment(args)
|
||||
|
||||
R.id.featuredRelationRoot.matchView().apply {
|
||||
checkHasViewGroupChildWithText(1, value4)
|
||||
checkHasViewGroupChildWithText(3, value5)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchFragment(args: Bundle): FragmentScenario<TestPageFragment> {
|
||||
return launchFragmentInContainer(
|
||||
fragmentArgs = args,
|
||||
|
|
|
@ -42,6 +42,7 @@ data class Block(
|
|||
fun empty(): Fields = Fields(emptyMap())
|
||||
const val NAME_KEY = "name"
|
||||
const val TYPE_KEY = "type"
|
||||
const val DESCRIPTION_KEY = "description"
|
||||
const val FEATURED_RELATIONS_KEY = "featuredRelations"
|
||||
const val IS_ARCHIVED_KEY = "isArchived"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package com.anytypeio.anytype.domain.relations
|
||||
|
||||
/**
|
||||
* Keys for predefined, bundled relations.
|
||||
*/
|
||||
object Relations {
|
||||
const val DESCRIPTION = "description"
|
||||
}
|
|
@ -2095,7 +2095,9 @@ class PageViewModel(
|
|||
|
||||
private fun onSelectAllClicked(state: ViewState.Success) =
|
||||
state.blocks.map { block ->
|
||||
if (block.id != blocks.titleId()) select(block.id)
|
||||
if (block is BlockView.Selectable) {
|
||||
select(block.id)
|
||||
}
|
||||
block.updateSelection(newSelection = true)
|
||||
}.let {
|
||||
onMultiSelectModeBlockClicked()
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.domain.editor.Editor.Cursor
|
|||
import com.anytypeio.anytype.domain.editor.Editor.Focus
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.page.EditorMode
|
||||
import com.anytypeio.anytype.domain.relations.Relations
|
||||
import com.anytypeio.anytype.presentation.mapper.*
|
||||
import com.anytypeio.anytype.presentation.page.cover.CoverColor
|
||||
import com.anytypeio.anytype.presentation.page.cover.CoverImageHashProvider
|
||||
|
@ -281,14 +282,18 @@ class DefaultBlockViewRenderer(
|
|||
}
|
||||
}
|
||||
Content.Text.Style.DESCRIPTION -> {
|
||||
counter.reset()
|
||||
result.add(
|
||||
description(
|
||||
block = block,
|
||||
content = content,
|
||||
mode = mode
|
||||
val detail = details.details.getOrDefault(root.id, Block.Fields.empty())
|
||||
val featured = detail.featuredRelations ?: emptyList()
|
||||
if (featured.contains(Relations.DESCRIPTION)) {
|
||||
counter.reset()
|
||||
result.add(
|
||||
description(
|
||||
block = block,
|
||||
content = content,
|
||||
mode = mode
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Content.Text.Style.CHECKBOX -> {
|
||||
counter.reset()
|
||||
|
@ -935,12 +940,16 @@ class DefaultBlockViewRenderer(
|
|||
return BlockView.FeaturedRelation(
|
||||
id = block.id,
|
||||
relations = featured.mapNotNull { id ->
|
||||
val relation = relations.first { it.key == id }
|
||||
relation.view(
|
||||
details = details,
|
||||
values = details.details[ctx]?.map ?: emptyMap(),
|
||||
urlBuilder = urlBuilder
|
||||
)
|
||||
if (id != Relations.DESCRIPTION) {
|
||||
val relation = relations.first { it.key == id }
|
||||
relation.view(
|
||||
details = details,
|
||||
values = details.details[ctx]?.map ?: emptyMap(),
|
||||
urlBuilder = urlBuilder
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ package com.anytypeio.anytype.presentation.page.editor
|
|||
|
||||
import MockDataFactory
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.anytypeio.anytype.domain.block.interactor.UnlinkBlocks
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.ext.content
|
||||
import com.anytypeio.anytype.domain.block.interactor.UnlinkBlocks
|
||||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.anytypeio.anytype.presentation.page.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.jraska.livedata.test
|
||||
|
@ -60,7 +60,9 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Text(
|
||||
text = "",
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.values().random()
|
||||
style = Block.Content.Text.Style.values().filter { style ->
|
||||
style != Block.Content.Text.Style.TITLE && style != Block.Content.Text.Style.DESCRIPTION
|
||||
}.random()
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -187,7 +189,9 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Text(
|
||||
text = "",
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.values().random()
|
||||
style = Block.Content.Text.Style.values().filter { style ->
|
||||
style != Block.Content.Text.Style.TITLE && style != Block.Content.Text.Style.DESCRIPTION
|
||||
}.random()
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -21,6 +21,26 @@ class EditorFocusTest : EditorPresentationTestSetup() {
|
|||
@get:Rule
|
||||
val coroutineTestRule = CoroutinesTestRule()
|
||||
|
||||
private val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
text = "Relation Block UI Testing",
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
private val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
@ -47,8 +67,10 @@ class EditorFocusTest : EditorPresentationTestSetup() {
|
|||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(Block.Content.Smart.Type.PAGE),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
block
|
||||
)
|
||||
|
||||
|
@ -111,11 +133,13 @@ class EditorFocusTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
block
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue