mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Editor | Enhancement | Callout cleanup (#2389)
Editor | Enhancement | Callout cleanup
This commit is contained in:
parent
439ab0c502
commit
c5198bae5b
12 changed files with 149 additions and 494 deletions
|
@ -57,7 +57,6 @@ import com.anytypeio.anytype.core_ui.extensions.color
|
|||
import com.anytypeio.anytype.core_ui.extensions.cursorYBottomCoordinate
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.DragAndDropAdapterDelegate
|
||||
import com.anytypeio.anytype.core_ui.features.editor.TurnIntoActionReceiver
|
||||
import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.DefaultScrollAndMoveTargetDescriptor
|
||||
import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.ScrollAndMoveStateListener
|
||||
import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.ScrollAndMoveTargetHighlighter
|
||||
|
@ -106,7 +105,6 @@ import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
|
|||
import com.anytypeio.anytype.presentation.editor.editor.ViewState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
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
|
||||
|
@ -164,7 +162,6 @@ import kotlin.math.abs
|
|||
|
||||
open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.fragment_editor),
|
||||
OnFragmentInteractionListener,
|
||||
TurnIntoActionReceiver,
|
||||
SelectProgrammingLanguageReceiver,
|
||||
RelationTextValueFragment.TextValueEditReceiver,
|
||||
RelationDateValueFragment.DateValueEditReceiver,
|
||||
|
@ -739,14 +736,6 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
|
|||
vm.onAddBookmarkUrl(target = target, url = url)
|
||||
}
|
||||
|
||||
override fun onTurnIntoBlockClicked(target: String, uiBlock: UiBlock) {
|
||||
vm.onTurnIntoBlockClicked(target, uiBlock)
|
||||
}
|
||||
|
||||
override fun onTurnIntoMultiSelectBlockClicked(block: UiBlock) {
|
||||
vm.onTurnIntoMultiSelectBlockClicked(block)
|
||||
}
|
||||
|
||||
override fun onAddMarkupLinkClicked(blockId: String, link: String, range: IntRange) {
|
||||
vm.onAddLinkPressed(blockId, link, range)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ class Callout(
|
|||
override val root: View = itemView
|
||||
override val content: TextInputWidget = binding.calloutText
|
||||
private val icon: ObjectIconWidget = binding.calloutIcon
|
||||
private val container = binding.calloutContainer
|
||||
|
||||
private val mentionIconSize: Int
|
||||
private val mentionIconPadding: Int
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package com.anytypeio.anytype.core_ui
|
||||
|
||||
import com.anytypeio.anytype.core_models.RelationFormat
|
||||
import com.anytypeio.anytype.core_models.StubParagraph
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil.Companion.CALLOUT_ICON_CHANGED
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil.Companion.DECORATION_CHANGED
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil.Companion.MARKUP_CHANGED
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil.Companion.TEXT_CHANGED
|
||||
|
@ -1581,4 +1581,54 @@ class BlockViewDiffUtilTest {
|
|||
expected = null
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should detect icon change for callout`() {
|
||||
val index = 0
|
||||
|
||||
val oldBlock = StubCalloutView(
|
||||
icon = ObjectIcon.Basic.Emoji("stub")
|
||||
)
|
||||
|
||||
val newBlock = oldBlock.copy(
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
|
||||
val old = listOf(oldBlock)
|
||||
|
||||
val new = listOf(newBlock)
|
||||
|
||||
val diff = BlockViewDiffUtil(old = old, new = new)
|
||||
|
||||
val payload = diff.getChangePayload(index, index)
|
||||
|
||||
assertEquals(
|
||||
actual = payload,
|
||||
expected = Payload(listOf(CALLOUT_ICON_CHANGED))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not detect icon change for callout`() {
|
||||
val index = 0
|
||||
|
||||
val oldBlock = StubCalloutView(
|
||||
icon = ObjectIcon.Basic.Emoji("stub")
|
||||
)
|
||||
|
||||
val newBlock = oldBlock
|
||||
|
||||
val old = listOf(oldBlock)
|
||||
|
||||
val new = listOf(newBlock)
|
||||
|
||||
val diff = BlockViewDiffUtil(old = old, new = new)
|
||||
|
||||
val payload = diff.getChangePayload(index, index)
|
||||
|
||||
assertEquals(
|
||||
actual = payload,
|
||||
expected = null
|
||||
)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import com.anytypeio.anytype.presentation.editor.editor.Markup
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.Alignment
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.model.Indent
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
|
||||
fun StubParagraphView(
|
||||
|
@ -22,7 +23,7 @@ fun StubParagraphView(
|
|||
ghostSelection: IntRange? = null,
|
||||
cursor: Int? = null,
|
||||
alignment: Alignment? = null
|
||||
) : BlockView.Text.Paragraph = BlockView.Text.Paragraph(
|
||||
): BlockView.Text.Paragraph = BlockView.Text.Paragraph(
|
||||
id = id,
|
||||
text = text,
|
||||
marks = marks,
|
||||
|
@ -55,7 +56,7 @@ fun StubNumberedView(
|
|||
cursor: Int? = null,
|
||||
alignment: Alignment? = null,
|
||||
number: Int = 1
|
||||
) : BlockView.Text.Numbered = BlockView.Text.Numbered(
|
||||
): BlockView.Text.Numbered = BlockView.Text.Numbered(
|
||||
id = id,
|
||||
text = text,
|
||||
marks = marks,
|
||||
|
@ -88,7 +89,7 @@ fun StubBulletedView(
|
|||
ghostSelection: IntRange? = null,
|
||||
cursor: Int? = null,
|
||||
alignment: Alignment? = null,
|
||||
) : BlockView.Text.Bulleted = BlockView.Text.Bulleted(
|
||||
): BlockView.Text.Bulleted = BlockView.Text.Bulleted(
|
||||
id = id,
|
||||
text = text,
|
||||
marks = marks,
|
||||
|
@ -121,7 +122,7 @@ fun StubCheckboxView(
|
|||
cursor: Int? = null,
|
||||
alignment: Alignment? = null,
|
||||
isChecked: Boolean = false
|
||||
) : BlockView.Text.Checkbox = BlockView.Text.Checkbox(
|
||||
): BlockView.Text.Checkbox = BlockView.Text.Checkbox(
|
||||
id = id,
|
||||
text = text,
|
||||
marks = marks,
|
||||
|
@ -156,7 +157,7 @@ fun StubToggleView(
|
|||
alignment: Alignment? = null,
|
||||
isEmpty: Boolean = false,
|
||||
toggled: Boolean = false
|
||||
) : BlockView.Text.Toggle = BlockView.Text.Toggle(
|
||||
): BlockView.Text.Toggle = BlockView.Text.Toggle(
|
||||
id = id,
|
||||
text = text,
|
||||
marks = marks,
|
||||
|
@ -173,4 +174,36 @@ fun StubToggleView(
|
|||
alignment = alignment,
|
||||
isEmpty = isEmpty,
|
||||
toggled = toggled
|
||||
)
|
||||
|
||||
fun StubCalloutView(
|
||||
id: Id = MockDataFactory.randomString(),
|
||||
text: String = MockDataFactory.randomString(),
|
||||
marks: List<Markup.Mark> = emptyList(),
|
||||
isFocused: Boolean = MockDataFactory.randomBoolean(),
|
||||
isSelected: Boolean = MockDataFactory.randomBoolean(),
|
||||
color: String? = null,
|
||||
indent: Indent = 0,
|
||||
searchFields: List<BlockView.Searchable.Field> = emptyList(),
|
||||
backgroundColor: String? = null,
|
||||
mode: BlockView.Mode = BlockView.Mode.EDIT,
|
||||
decorations: List<BlockView.Decoration> = emptyList(),
|
||||
ghostSelection: IntRange? = null,
|
||||
cursor: Int? = null,
|
||||
icon: ObjectIcon = ObjectIcon.None
|
||||
): BlockView.Text.Callout = BlockView.Text.Callout(
|
||||
id = id,
|
||||
text = text,
|
||||
marks = marks,
|
||||
isFocused = isFocused,
|
||||
isSelected = isSelected,
|
||||
color = color,
|
||||
indent = indent,
|
||||
searchFields = searchFields,
|
||||
backgroundColor = backgroundColor,
|
||||
mode = mode,
|
||||
decorations = decorations,
|
||||
ghostEditorSelection = ghostSelection,
|
||||
cursor = cursor,
|
||||
icon = icon
|
||||
)
|
|
@ -1,11 +1,13 @@
|
|||
package com.anytypeio.anytype.presentation.editor
|
||||
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Document
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.domain.editor.Editor
|
||||
import com.anytypeio.anytype.domain.editor.Editor.Focus
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Proxy
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Store
|
||||
import com.anytypeio.anytype.presentation.editor.editor.actions.ActionItemType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -13,21 +15,66 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
|
||||
interface Editor {
|
||||
|
||||
/**
|
||||
* Mode of user interaction with the editor.
|
||||
*/
|
||||
sealed class Mode {
|
||||
|
||||
/**
|
||||
* Default mode of interaction with the editor: content editing is fully enabled.
|
||||
*/
|
||||
object Edit : Mode()
|
||||
|
||||
/**
|
||||
* Editor in preview state.
|
||||
* Currently used for templates, which can't be edited.
|
||||
*/
|
||||
object Read : Mode()
|
||||
|
||||
/**
|
||||
* Editor in locked state.
|
||||
* Locked mode is toggled in object menu.
|
||||
*/
|
||||
object Locked: Mode()
|
||||
|
||||
/**
|
||||
* Editor in select mode: when one or multiple blocks are selected.
|
||||
* To enter this mode, user long-clicks a block.
|
||||
*/
|
||||
object Select : Mode()
|
||||
|
||||
/**
|
||||
* Editor in scroll-and-move mode: one or multiple blocks are selected, then moved to new position by scrolling.
|
||||
* @see [ActionItemType.SAM]
|
||||
*/
|
||||
object SAM : Mode()
|
||||
object Action: Mode()
|
||||
|
||||
/**
|
||||
* Editor in search-on-page state: searching plain text through blocks.
|
||||
* @see [Block.Content.Text.Style]
|
||||
*/
|
||||
object Search : Mode()
|
||||
|
||||
/**
|
||||
* Editor in text styling mode.
|
||||
*/
|
||||
sealed class Styling : Mode() {
|
||||
/**
|
||||
* Enabled when user selects “style” option in BlockToolbar for focused block.
|
||||
* @property [target] id of the selected block
|
||||
* @property [cursor] cursor position before selection (we might need to restore cursor position after unselecting [target] block)
|
||||
*/
|
||||
data class Single(
|
||||
val target: Id,
|
||||
val cursor: Int?
|
||||
) : Styling()
|
||||
|
||||
/**
|
||||
* Enabled when user selects multiple blocks and choose [ActionItemType.Style] from ActionToolbar
|
||||
* @property [targets] ids of the selected blocks.
|
||||
*/
|
||||
data class Multi(val targets: Set<Id>) : Styling()
|
||||
}
|
||||
object Locked: Mode()
|
||||
}
|
||||
|
||||
class Storage {
|
||||
|
|
|
@ -2460,7 +2460,7 @@ class EditorViewModel(
|
|||
|
||||
// ----------------- Turn Into -----------------------------------------
|
||||
|
||||
fun onTurnIntoBlockClicked(target: String, uiBlock: UiBlock) {
|
||||
private fun onTurnIntoBlockClicked(target: String, uiBlock: UiBlock) {
|
||||
Timber.d("onTurnIntoBlockClicked, taget:[$target] uiBlock:[$uiBlock]")
|
||||
proceedUpdateBlockStyle(
|
||||
targets = listOf(target),
|
||||
|
@ -5071,19 +5071,6 @@ class EditorViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onTurnIntoMultiSelectBlockClicked(uiBlock: UiBlock) {
|
||||
Timber.d("onTurnIntoMultiSelectBlockClicked, uiBlock:[$uiBlock]")
|
||||
proceedUpdateBlockStyle(
|
||||
targets = currentSelection().toList(),
|
||||
uiBlock = uiBlock,
|
||||
action = {
|
||||
clearSelections()
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.MultiSelect.OnTurnInto)
|
||||
},
|
||||
errorAction = { sendToast("Cannot convert selected blocks to $uiBlock") }
|
||||
)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region MENTION WIDGET
|
||||
|
|
|
@ -430,12 +430,12 @@ sealed class BlockView : ViewType {
|
|||
override val mode: Mode = Mode.EDIT,
|
||||
override val isSelected: Boolean = false,
|
||||
override var cursor: Int? = null,
|
||||
override val alignment: Alignment? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration>,
|
||||
val icon: ObjectIcon,
|
||||
) : Text() {
|
||||
override val alignment: Alignment? = null
|
||||
override fun getViewType() = HOLDER_CALLOUT
|
||||
override val body: String get() = text
|
||||
}
|
||||
|
|
|
@ -1055,7 +1055,7 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
text = normalizedText,
|
||||
marks = normalizedMarks,
|
||||
indent = indent,
|
||||
alignment = content.align?.toView(), color = content.color,
|
||||
color = content.color,
|
||||
backgroundColor = block.backgroundColor ?: ThemeColor.GREY.code,
|
||||
cursor = if (block.id == focus.id) setCursor(focus, content) else null,
|
||||
isSelected = checkIfSelected(
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.anytypeio.anytype.analytics.base.Analytics
|
|||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Position
|
||||
import com.anytypeio.anytype.core_models.Relation
|
||||
|
@ -85,8 +84,8 @@ import com.anytypeio.anytype.presentation.editor.editor.actions.ActionItemType
|
|||
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.UiBlock
|
||||
import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMatcher
|
||||
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashItem
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StylingEvent
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
|
@ -2070,7 +2069,7 @@ open class EditorViewModelTest {
|
|||
|
||||
val newStyle = Block.Content.Text.Style.H1
|
||||
|
||||
vm.onTurnIntoBlockClicked(secondChild, UiBlock.HEADER_ONE)
|
||||
vm.onSlashItemClicked(SlashItem.Style.Type.Title)
|
||||
|
||||
runBlockingTest {
|
||||
verify(turnIntoStyle, times(1)).invoke(
|
||||
|
|
|
@ -3,11 +3,8 @@ package com.anytypeio.anytype.presentation.editor.editor
|
|||
import android.os.Build
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.ext.content
|
||||
import com.anytypeio.anytype.domain.block.interactor.TurnIntoStyle
|
||||
import com.anytypeio.anytype.domain.block.interactor.UnlinkBlocks
|
||||
import com.anytypeio.anytype.domain.block.interactor.UpdateTextStyle
|
||||
import com.anytypeio.anytype.domain.clipboard.Copy
|
||||
import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubLinkContent
|
||||
import com.anytypeio.anytype.presentation.MockBlockFactory
|
||||
|
@ -19,7 +16,6 @@ import com.anytypeio.anytype.presentation.editor.editor.actions.ActionItemType
|
|||
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.UiBlock
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarState
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.anytypeio.anytype.presentation.util.TXT
|
||||
|
@ -71,333 +67,6 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
MockitoAnnotations.openMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should clear selection after turn-into in multi-select mode`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(),
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
stubTurnIntoStyle(
|
||||
params = TurnIntoStyle.Params(
|
||||
style = Block.Content.Text.Style.QUOTE,
|
||||
context = root,
|
||||
targets = listOf(a.id)
|
||||
),
|
||||
events = listOf(
|
||||
Event.Command.GranularChange(
|
||||
context = root,
|
||||
id = a.id,
|
||||
style = Block.Content.Text.Style.QUOTE
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
vm.onStart(root)
|
||||
|
||||
// TESTING
|
||||
|
||||
// Try entering multi-select mode
|
||||
|
||||
vm.apply {
|
||||
onBlockFocusChanged(id = a.id, hasFocus = true)
|
||||
onEnterMultiSelectModeClicked()
|
||||
}
|
||||
|
||||
// Checking control panel entered multi-select mode
|
||||
|
||||
vm.controlPanelViewState.test().apply {
|
||||
assertValue(
|
||||
ControlPanelState(
|
||||
multiSelect = ControlPanelState.Toolbar.MultiSelect(
|
||||
isVisible = true,
|
||||
isScrollAndMoveEnabled = false,
|
||||
count = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Checking editor entered multi-select mode
|
||||
|
||||
coroutineTestRule.advanceTime(DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE)
|
||||
|
||||
vm.state.test().apply {
|
||||
assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Basic(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
),
|
||||
BlockView.Text.Numbered(
|
||||
id = a.id,
|
||||
isSelected = false,
|
||||
isFocused = false,
|
||||
marks = emptyList(),
|
||||
backgroundColor = null,
|
||||
indent = 0,
|
||||
number = 1,
|
||||
text = a.content<Block.Content.Text>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Perform click, to select block A
|
||||
|
||||
vm.onTextInputClicked(
|
||||
target = a.id
|
||||
)
|
||||
|
||||
// Checking whether block A is selected
|
||||
|
||||
vm.state.test().apply {
|
||||
assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Basic(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
),
|
||||
BlockView.Text.Numbered(
|
||||
id = a.id,
|
||||
isSelected = true,
|
||||
isFocused = false,
|
||||
marks = emptyList(),
|
||||
backgroundColor = null,
|
||||
indent = 0,
|
||||
number = 1,
|
||||
text = a.content<Block.Content.Text>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Turning block A into a highlight block.
|
||||
|
||||
vm.onTurnIntoMultiSelectBlockClicked(
|
||||
UiBlock.HIGHLIGHTED
|
||||
)
|
||||
|
||||
// Checking control panel state after turn-into
|
||||
|
||||
vm.controlPanelViewState.test().apply {
|
||||
assertValue(
|
||||
ControlPanelState(
|
||||
multiSelect = ControlPanelState.Toolbar.MultiSelect(
|
||||
isVisible = true,
|
||||
isScrollAndMoveEnabled = false,
|
||||
count = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Checking view state state after turn-into
|
||||
|
||||
vm.state.test().apply {
|
||||
assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Basic(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
),
|
||||
BlockView.Text.Highlight(
|
||||
id = a.id,
|
||||
isSelected = false,
|
||||
isFocused = false,
|
||||
marks = emptyList(),
|
||||
backgroundColor = null,
|
||||
color = null,
|
||||
indent = 0,
|
||||
text = a.content<Block.Content.Text>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
clearPendingCoroutines()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should show main toolbar when block view holder returning to edit mode gains focus after turn-into in multi-select-mode`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.P
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(),
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
stubUpdateTextStyle(
|
||||
params = UpdateTextStyle.Params(
|
||||
style = Block.Content.Text.Style.QUOTE,
|
||||
context = root,
|
||||
targets = listOf(a.id)
|
||||
),
|
||||
events = listOf(
|
||||
Event.Command.GranularChange(
|
||||
context = root,
|
||||
id = a.id,
|
||||
style = Block.Content.Text.Style.QUOTE
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onStart(root)
|
||||
|
||||
// Try entering multi-select mode
|
||||
|
||||
vm.apply {
|
||||
onBlockFocusChanged(id = a.id, hasFocus = true)
|
||||
onEnterMultiSelectModeClicked()
|
||||
}
|
||||
|
||||
// Checking control panel entered multi-select mode
|
||||
|
||||
vm.controlPanelViewState.test().apply {
|
||||
assertValue(
|
||||
ControlPanelState(
|
||||
multiSelect = ControlPanelState.Toolbar.MultiSelect(
|
||||
isVisible = true,
|
||||
isScrollAndMoveEnabled = false,
|
||||
count = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
coroutineTestRule.advanceTime(DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE)
|
||||
|
||||
// Perform click, to select block A
|
||||
|
||||
vm.onTextInputClicked(
|
||||
target = a.id
|
||||
)
|
||||
|
||||
// Turning block A into a highlight block.
|
||||
|
||||
vm.onTurnIntoMultiSelectBlockClicked(
|
||||
UiBlock.HIGHLIGHTED
|
||||
)
|
||||
|
||||
vm.onExitMultiSelectModeClicked()
|
||||
|
||||
vm.onTextInputClicked(
|
||||
target = a.id
|
||||
)
|
||||
|
||||
vm.onBlockFocusChanged(
|
||||
id = a.id,
|
||||
hasFocus = true
|
||||
)
|
||||
|
||||
vm.controlPanelViewState.test().apply {
|
||||
assertValue(
|
||||
ControlPanelState(
|
||||
navigationToolbar = ControlPanelState.Toolbar.Navigation(
|
||||
isVisible = false
|
||||
),
|
||||
mainToolbar = ControlPanelState.Toolbar.Main(
|
||||
isVisible = true
|
||||
),
|
||||
styleTextToolbar = ControlPanelState.Toolbar.Styling.reset(),
|
||||
multiSelect = ControlPanelState.Toolbar.MultiSelect(
|
||||
isVisible = false,
|
||||
isScrollAndMoveEnabled = false,
|
||||
count = 0
|
||||
),
|
||||
mentionToolbar = ControlPanelState.Toolbar.MentionToolbar(
|
||||
isVisible = false,
|
||||
cursorCoordinate = null,
|
||||
mentionFilter = null,
|
||||
mentionFrom = null
|
||||
),
|
||||
slashWidget = ControlPanelState.Toolbar.SlashWidget.reset()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
clearPendingCoroutines()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should select all children when selecting parent and unselect children when unselecting parent and exit multi-select mode`() {
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.anytypeio.anytype.domain.block.interactor.TurnIntoDocument
|
|||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.anytypeio.anytype.presentation.editor.EditorViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.UiBlock
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
import org.junit.Before
|
||||
|
@ -49,127 +48,6 @@ class EditorTurnIntoTest : EditorPresentationTestSetup() {
|
|||
MockitoAnnotations.openMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should start turning text block into page in edit mode`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val child = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomUuid(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.values().random()
|
||||
)
|
||||
)
|
||||
|
||||
val parent = Block(
|
||||
id = "PARENT",
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(child.id),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.P
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(),
|
||||
children = listOf(parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child)
|
||||
|
||||
val params = TurnIntoDocument.Params(context = root, targets = listOf(child.id))
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptThreadStatus()
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
stubTurnIntoDocument(params)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = child.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onTurnIntoBlockClicked(
|
||||
target = child.id,
|
||||
uiBlock = UiBlock.PAGE
|
||||
)
|
||||
}
|
||||
|
||||
verifyBlocking(turnIntoDocument, times(1)) { invoke(params) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should start turning into page in multi-select mode`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val child = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomUuid(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.values().random()
|
||||
)
|
||||
)
|
||||
|
||||
val parent = Block(
|
||||
id = "PARENT",
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(child.id),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.P
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(),
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, parent, child)
|
||||
|
||||
val params = TurnIntoDocument.Params(context = root, targets = listOf(child.id))
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
stubInterceptThreadStatus()
|
||||
stubTurnIntoDocument(params)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(id = child.id, hasFocus = true)
|
||||
onEnterMultiSelectModeClicked()
|
||||
coroutineTestRule.advanceTime(EditorViewModel.DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE)
|
||||
onTextInputClicked(child.id)
|
||||
onTurnIntoMultiSelectBlockClicked(UiBlock.PAGE)
|
||||
}
|
||||
|
||||
verifyBlocking(turnIntoDocument, times(1)) { invoke(params) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should invoke turnIntoDocument on for one text block and one file block in multi-select mode`() {
|
||||
|
||||
|
|
|
@ -149,13 +149,17 @@ fun StubCallout(
|
|||
text: String = MockDataFactory.randomString(),
|
||||
children: List<Id> = emptyList(),
|
||||
marks: List<Block.Content.Text.Mark> = emptyList(),
|
||||
backgroundColor: String? = null
|
||||
backgroundColor: String? = null,
|
||||
iconEmoji: String? = null,
|
||||
iconImage: String? = null,
|
||||
): Block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = StubTextContent(
|
||||
text = text,
|
||||
style = Block.Content.Text.Style.CALLOUT,
|
||||
marks = marks
|
||||
marks = marks,
|
||||
iconEmoji = iconEmoji,
|
||||
iconImage = iconImage,
|
||||
),
|
||||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue