mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-12 10:40:25 +09:00
Editor | Fix | Note title (#1930)
* legacy * update note title logic * tests * pr fix * pr fixes
This commit is contained in:
parent
ecdcc2cde5
commit
1776331d42
14 changed files with 184 additions and 56 deletions
|
@ -87,6 +87,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.UiBlock
|
|||
import com.anytypeio.anytype.presentation.editor.editor.sam.ScrollAndMoveTarget
|
||||
import com.anytypeio.anytype.presentation.editor.editor.sam.ScrollAndMoveTargetDescriptor
|
||||
import com.anytypeio.anytype.presentation.editor.markup.MarkupColorView
|
||||
import com.anytypeio.anytype.presentation.editor.model.EditorFooter
|
||||
import com.anytypeio.anytype.ui.alert.AlertUpdateAppFragment
|
||||
import com.anytypeio.anytype.ui.base.NavigationFragment
|
||||
import com.anytypeio.anytype.ui.editor.cover.CoverSliderObjectFragment
|
||||
|
@ -207,6 +208,9 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
|
|||
}
|
||||
|
||||
private val footerMentionDecorator by lazy { MentionFooterItemDecorator(screen) }
|
||||
private val noteHeaderDecorator by lazy {
|
||||
NoteHeaderItemDecorator(offset = dimen(R.dimen.default_note_title_offset))
|
||||
}
|
||||
private val markupColorToolbarFooter by lazy { MarkupColorToolbarFooter(screen) }
|
||||
private val slashWidgetFooter by lazy { SlashWidgetFooterItemDecorator(screen) }
|
||||
private val styleToolbarFooter by lazy { StyleToolbarItemDecorator(screen) }
|
||||
|
@ -738,6 +742,20 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
|
|||
}
|
||||
}
|
||||
}
|
||||
jobs += subscribe(vm.footers) { footer ->
|
||||
when (footer) {
|
||||
EditorFooter.None -> {
|
||||
if (recycler.containsItemDecoration(noteHeaderDecorator)) {
|
||||
recycler.removeItemDecoration(noteHeaderDecorator)
|
||||
}
|
||||
}
|
||||
EditorFooter.Note -> {
|
||||
if (!recycler.containsItemDecoration(noteHeaderDecorator)) {
|
||||
recycler.addItemDecoration(noteHeaderDecorator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ object BlockActionToolbarFactory {
|
|||
is BlockView.Description -> TODO()
|
||||
is BlockView.FeaturedRelation -> TODO()
|
||||
is BlockView.Unsupported -> TODO()
|
||||
is BlockView.TitleNote -> TODO()
|
||||
is BlockView.Latex -> TODO()
|
||||
is BlockView.LinkToObject.Archived -> TODO()
|
||||
is BlockView.LinkToObject.Deleted -> TODO()
|
||||
|
|
|
@ -158,6 +158,7 @@ data class Block(
|
|||
enum class Type { ROW, COLUMN, DIV, HEADER }
|
||||
}
|
||||
|
||||
@Deprecated("Legacy class")
|
||||
data class Page(val style: Style) : Content() {
|
||||
enum class Style { EMPTY, TASK, SET }
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_TWO
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HIGHLIGHT
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_LATEX
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_NOTE_TITLE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_NUMBERED
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_ARCHIVE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_DEFAULT
|
||||
|
@ -255,15 +254,6 @@ class BlockAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
HOLDER_NOTE_TITLE -> {
|
||||
TitleNoteHolder(
|
||||
view = inflater.inflate(
|
||||
R.layout.item_block_note_title,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
HOLDER_HEADER_ONE -> {
|
||||
HeaderOne(
|
||||
view = inflater.inflate(
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package com.anytypeio.anytype.core_ui.features.editor.holders.other
|
||||
|
||||
import android.view.View
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
|
||||
|
||||
class TitleNoteHolder(view: View) : BlockViewHolder(view)
|
|
@ -0,0 +1,20 @@
|
|||
package com.anytypeio.anytype.core_ui.tools
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class NoteHeaderItemDecorator(private val offset: Int) : RecyclerView.ItemDecoration() {
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
val position = parent.getChildAdapterPosition(view)
|
||||
if (position == 0) {
|
||||
outRect.top = offset
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="124dp">
|
||||
</FrameLayout>
|
|
@ -57,6 +57,8 @@
|
|||
<dimen name="default_toolbar_color_item_divider_width">1dp</dimen>
|
||||
<dimen name="default_toolbar_color_placeholder_text_size">22sp</dimen>
|
||||
|
||||
<dimen name="default_note_title_offset">124dp</dimen>
|
||||
|
||||
<dimen name="modal_rect_margin_top">6dp</dimen>
|
||||
<dimen name="modal_main_margin_start">20dp</dimen>
|
||||
<dimen name="modal_main_margin_end">20dp</dimen>
|
||||
|
|
|
@ -85,6 +85,7 @@ import com.anytypeio.anytype.presentation.editor.editor.slash.SlashExtensions.ge
|
|||
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashExtensions.getSlashWidgetStyleItems
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StylingEvent
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StylingMode
|
||||
import com.anytypeio.anytype.presentation.editor.model.EditorFooter
|
||||
import com.anytypeio.anytype.presentation.editor.model.TextUpdate
|
||||
import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
|
@ -99,7 +100,6 @@ import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
|
|||
import com.anytypeio.anytype.presentation.navigation.SupportNavigation
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectTypeView
|
||||
import com.anytypeio.anytype.presentation.objects.SupportedLayouts
|
||||
import com.anytypeio.anytype.presentation.objects.sortByType
|
||||
import com.anytypeio.anytype.presentation.objects.toView
|
||||
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
|
||||
import com.anytypeio.anytype.presentation.relations.views
|
||||
|
@ -172,6 +172,8 @@ class EditorViewModel(
|
|||
|
||||
var mode: EditorMode = EditorMode.Edit
|
||||
|
||||
val footers = MutableStateFlow<EditorFooter>(EditorFooter.None)
|
||||
|
||||
private val controlPanelInteractor = Interactor(viewModelScope)
|
||||
val controlPanelViewState = MutableLiveData<ControlPanelState>()
|
||||
|
||||
|
@ -459,12 +461,14 @@ class EditorViewModel(
|
|||
orchestrator.stores.focus.stream(),
|
||||
orchestrator.stores.details.stream()
|
||||
) { models, focus, details ->
|
||||
val root = models.first { it.id == context }
|
||||
footers.value = getFooterState(root, details)
|
||||
models.asMap().render(
|
||||
mode = mode,
|
||||
indent = INITIAL_INDENT,
|
||||
anchor = context,
|
||||
focus = focus,
|
||||
root = models.first { it.id == context },
|
||||
root = root,
|
||||
details = details,
|
||||
relations = orchestrator.stores.relations.current(),
|
||||
restrictions = orchestrator.stores.objectRestrictions.current(),
|
||||
|
@ -5401,4 +5405,13 @@ class EditorViewModel(
|
|||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region FOOTER
|
||||
private fun getFooterState(root: Block, details: Block.Details): EditorFooter {
|
||||
return when (details.details[root.id]?.layout?.toInt()) {
|
||||
ObjectType.Layout.NOTE.code -> EditorFooter.Note
|
||||
else -> EditorFooter.None
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
}
|
|
@ -25,7 +25,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_TWO
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HIGHLIGHT
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_LATEX
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_NOTE_TITLE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_NUMBERED
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_ARCHIVE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_OBJECT_LINK_DEFAULT
|
||||
|
@ -545,24 +544,6 @@ sealed class BlockView : ViewType, Parcelable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* UI-model for a note-layout title block.
|
||||
* In fact it's just an empty space, because NOTE LAYOUT doesn't have title
|
||||
* @property id block's id
|
||||
*/
|
||||
@Parcelize
|
||||
data class TitleNote(
|
||||
override val id: String
|
||||
) : BlockView() {
|
||||
override fun getViewType() = HOLDER_NOTE_TITLE
|
||||
|
||||
companion object {
|
||||
const val INTERNAL_ID = "HOLDER_NOTE_TITLE"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UI-model for a code-snippet block.
|
||||
* @property id block's id
|
||||
|
|
|
@ -6,7 +6,6 @@ object Types {
|
|||
const val HOLDER_PROFILE_TITLE = 35
|
||||
const val HOLDER_ARCHIVE_TITLE = 36
|
||||
const val HOLDER_TODO_TITLE = 48
|
||||
const val HOLDER_NOTE_TITLE = 50
|
||||
const val HOLDER_HEADER_ONE = 2
|
||||
const val HOLDER_HEADER_TWO = 3
|
||||
const val HOLDER_HEADER_THREE = 4
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.anytypeio.anytype.presentation.editor.model
|
||||
|
||||
sealed class EditorFooter {
|
||||
object Note : EditorFooter()
|
||||
object None : EditorFooter()
|
||||
}
|
|
@ -54,9 +54,6 @@ class DefaultBlockViewRenderer(
|
|||
)
|
||||
)
|
||||
}
|
||||
if (isLayoutNote(root, details)) {
|
||||
result.add(BlockView.TitleNote(id = BlockView.TitleNote.INTERNAL_ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1399,9 +1396,4 @@ class DefaultBlockViewRenderer(
|
|||
is Cursor.Range -> cursor.range.first
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLayoutNote(root: Block, details: Block.Details): Boolean {
|
||||
val layoutCode = details.details[root.id]?.layout?.toInt()
|
||||
return layoutCode == ObjectType.Layout.NOTE.code
|
||||
}
|
||||
}
|
|
@ -3,19 +3,24 @@ package com.anytypeio.anytype.presentation.editor.editor
|
|||
import MockDataFactory
|
||||
import android.util.Log
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.core_models.*
|
||||
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory
|
||||
import com.anytypeio.anytype.presentation.editor.EditorViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.model.EditorFooter
|
||||
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.jraska.livedata.test
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.lachlanmckee.timberjunit.TimberTestRule
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.MockitoAnnotations
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
class EditorNoteLayoutTest : EditorPresentationTestSetup() {
|
||||
|
||||
|
@ -43,8 +48,9 @@ class EditorNoteLayoutTest : EditorPresentationTestSetup() {
|
|||
coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
}
|
||||
|
||||
@ExperimentalTime
|
||||
@Test
|
||||
fun `should render note title block with featured relations block`() {
|
||||
fun `should render featured relations block and show note header as footer`() = runBlocking {
|
||||
|
||||
val featuredBlock = Block(
|
||||
id = "featuredRelations",
|
||||
|
@ -128,9 +134,6 @@ class EditorNoteLayoutTest : EditorPresentationTestSetup() {
|
|||
vm.onStart(root)
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.TitleNote(
|
||||
id = BlockView.TitleNote.INTERNAL_ID
|
||||
),
|
||||
BlockView.FeaturedRelation(
|
||||
id = featuredBlock.id,
|
||||
relations = listOf(
|
||||
|
@ -146,6 +149,121 @@ class EditorNoteLayoutTest : EditorPresentationTestSetup() {
|
|||
)
|
||||
|
||||
vm.state.test().assertValue(ViewState.Success(expected))
|
||||
|
||||
vm.footers.test {
|
||||
val result = awaitItem()
|
||||
assertEquals(EditorFooter.Note, result)
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalTime
|
||||
@Test
|
||||
fun `should render featured relations block and not show note header as footer`() = runBlocking {
|
||||
|
||||
val featuredBlock = Block(
|
||||
id = "featuredRelations",
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.FeaturedRelations
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = "header",
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(featuredBlock.id)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(SmartBlockType.PAGE),
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val doc = listOf(page, header, featuredBlock)
|
||||
|
||||
val objectTypeId = "objectTypeId"
|
||||
val objectTypeName = "objectTypeName"
|
||||
val objectTypeDescription = "objectTypeDesc"
|
||||
|
||||
val r1 = MockTypicalDocumentFactory.relation("Ad")
|
||||
val r2 = MockTypicalDocumentFactory.relation("De")
|
||||
val r3 = MockTypicalDocumentFactory.relation("HJ")
|
||||
val relationObjectType = Relation(
|
||||
key = Block.Fields.TYPE_KEY,
|
||||
name = "Object Type",
|
||||
format = Relation.Format.OBJECT,
|
||||
source = Relation.Source.DERIVED
|
||||
)
|
||||
|
||||
|
||||
val value1 = MockDataFactory.randomString()
|
||||
val value2 = MockDataFactory.randomString()
|
||||
val value3 = MockDataFactory.randomString()
|
||||
val objectFields = Block.Fields(
|
||||
mapOf(
|
||||
r1.key to value1,
|
||||
r2.key to value2,
|
||||
r3.key to value3,
|
||||
relationObjectType.key to objectTypeId,
|
||||
Relations.FEATURED_RELATIONS to listOf(relationObjectType.key),
|
||||
Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble()
|
||||
)
|
||||
)
|
||||
|
||||
val objectTypeFields = Block.Fields(
|
||||
mapOf(
|
||||
Block.Fields.NAME_KEY to objectTypeName,
|
||||
Block.Fields.DESCRIPTION_KEY to objectTypeDescription
|
||||
)
|
||||
)
|
||||
val customDetails = Block.Details(
|
||||
mapOf(
|
||||
root to objectFields,
|
||||
objectTypeId to objectTypeFields
|
||||
)
|
||||
)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubInterceptThreadStatus()
|
||||
stubGetObjectTypes(objectTypes = listOf())
|
||||
stubGetDefaultObjectType()
|
||||
stubOpenDocument(
|
||||
document = doc,
|
||||
details = customDetails,
|
||||
relations = listOf(r1, r2, r3, relationObjectType)
|
||||
)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
vm.onStart(root)
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.FeaturedRelation(
|
||||
id = featuredBlock.id,
|
||||
relations = listOf(
|
||||
DocumentRelationView.ObjectType(
|
||||
relationId = relationObjectType.key,
|
||||
name = objectTypeName,
|
||||
value = null,
|
||||
isFeatured = true,
|
||||
type = objectTypeId
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
vm.state.test().assertValue(ViewState.Success(expected))
|
||||
|
||||
vm.footers.test {
|
||||
val result = awaitItem()
|
||||
assertEquals(EditorFooter.None, result)
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue