mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-10 01:51:05 +09:00
Should replace current text block instead of adding new block after this text block if this text block is empty when adding new block via add-block screen (#790)
This commit is contained in:
parent
e5dd935fa4
commit
16bc727318
4 changed files with 295 additions and 43 deletions
|
@ -8,6 +8,7 @@
|
|||
|
||||
### Fixes & tech 🚒
|
||||
|
||||
* When adding new block via add-block screen, should replace current text block instead of adding a new block after this text block if this text block is empty (#325)
|
||||
* Divider block should be selectable in multi-select and scroll-and-move mode (#778)
|
||||
|
||||
## Version 0.0.45
|
||||
|
|
|
@ -54,6 +54,7 @@ import com.agileburo.anytype.core_utils.common.EventWrapper
|
|||
import com.agileburo.anytype.core_utils.ext.*
|
||||
import com.agileburo.anytype.core_utils.ext.PopupExtensions.calculateRectInWindow
|
||||
import com.agileburo.anytype.di.common.componentManager
|
||||
import com.agileburo.anytype.domain.block.model.Block
|
||||
import com.agileburo.anytype.domain.block.model.Block.Content.Text
|
||||
import com.agileburo.anytype.domain.common.Id
|
||||
import com.agileburo.anytype.domain.ext.getFirstLinkMarkupParam
|
||||
|
@ -505,9 +506,9 @@ open class PageFragment :
|
|||
UiBlock.TOGGLE -> vm.onAddTextBlockClicked(Text.Style.TOGGLE)
|
||||
UiBlock.CODE -> vm.onAddTextBlockClicked(Text.Style.CODE_SNIPPET)
|
||||
UiBlock.PAGE -> vm.onAddNewPageClicked()
|
||||
UiBlock.FILE -> vm.onAddFileBlockClicked()
|
||||
UiBlock.IMAGE -> vm.onAddImageBlockClicked()
|
||||
UiBlock.VIDEO -> vm.onAddVideoBlockClicked()
|
||||
UiBlock.FILE -> vm.onAddFileBlockClicked(Block.Content.File.Type.FILE)
|
||||
UiBlock.IMAGE -> vm.onAddFileBlockClicked(Block.Content.File.Type.IMAGE)
|
||||
UiBlock.VIDEO -> vm.onAddFileBlockClicked(Block.Content.File.Type.VIDEO)
|
||||
UiBlock.BOOKMARK -> vm.onAddBookmarkBlockClicked()
|
||||
UiBlock.LINE_DIVIDER -> vm.onAddDividerBlockClicked()
|
||||
else -> toast(NOT_IMPLEMENTED_MESSAGE)
|
||||
|
|
|
@ -1168,18 +1168,29 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onAddTextBlockClicked(style: Content.Text.Style) {
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)
|
||||
proceedWithCreatingNewTextBlock(
|
||||
id = orchestrator.stores.focus.current().id,
|
||||
style = style
|
||||
)
|
||||
}
|
||||
|
||||
fun onAddVideoBlockClicked() {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = orchestrator.stores.focus.current().id,
|
||||
type = Content.File.Type.VIDEO
|
||||
)
|
||||
val target = blocks.first { it.id == orchestrator.stores.focus.current().id }
|
||||
|
||||
val content = target.content
|
||||
|
||||
if (content is Content.Text && content.text.isEmpty()) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Replace(
|
||||
context = context,
|
||||
target = target.id,
|
||||
prototype = Prototype.Text(style = style)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
proceedWithCreatingNewTextBlock(
|
||||
id = orchestrator.stores.focus.current().id,
|
||||
style = style
|
||||
)
|
||||
}
|
||||
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)
|
||||
}
|
||||
|
||||
private fun onAddLocalVideoClicked(blockId: String) {
|
||||
|
@ -1217,18 +1228,21 @@ class PageViewModel(
|
|||
dispatch(Command.OpenGallery(mediaType = MIME_FILE_ALL))
|
||||
}
|
||||
|
||||
fun onAddImageBlockClicked() {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = orchestrator.stores.focus.current().id,
|
||||
type = Content.File.Type.IMAGE
|
||||
)
|
||||
}
|
||||
fun onAddFileBlockClicked(type: Content.File.Type) {
|
||||
val target = blocks.first { it.id == orchestrator.stores.focus.current().id }
|
||||
val content = target.content
|
||||
|
||||
fun onAddFileBlockClicked() {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = orchestrator.stores.focus.current().id,
|
||||
type = Content.File.Type.FILE
|
||||
)
|
||||
if (content is Content.Text && content.text.isEmpty()) {
|
||||
proceedWithReplacingByEmptyFileBlock(
|
||||
id = target.id,
|
||||
type = type
|
||||
)
|
||||
} else {
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = target.id,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithCreatingEmptyFileBlock(
|
||||
|
@ -1249,6 +1263,22 @@ class PageViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun proceedWithReplacingByEmptyFileBlock(
|
||||
id: String,
|
||||
type: Content.File.Type,
|
||||
state: Content.File.State = Content.File.State.EMPTY
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Replace(
|
||||
context = context,
|
||||
target = id,
|
||||
prototype = Prototype.File(type = type, state = state)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCheckboxClicked(view: BlockView.Text.Checkbox) {
|
||||
|
||||
blocks = blocks.map { block ->
|
||||
|
@ -1511,17 +1541,33 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onAddDividerBlockClicked() {
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Create(
|
||||
context = context,
|
||||
target = orchestrator.stores.focus.current().id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Prototype.Divider
|
||||
val target = blocks.first { it.id == orchestrator.stores.focus.current().id }
|
||||
val content = target.content
|
||||
|
||||
if (content is Content.Text && content.text.isEmpty()) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Replace(
|
||||
context = context,
|
||||
target = target.id,
|
||||
prototype = Prototype.Divider
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Create(
|
||||
context = context,
|
||||
target = target.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Prototype.Divider
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)
|
||||
}
|
||||
|
||||
private fun proceedWithUpdatingTextStyle(
|
||||
|
@ -1639,17 +1685,32 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onAddBookmarkBlockClicked() {
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Create(
|
||||
context = context,
|
||||
position = Position.BOTTOM,
|
||||
target = orchestrator.stores.focus.current().id,
|
||||
prototype = Prototype.Bookmark
|
||||
val target = blocks.first { it.id == orchestrator.stores.focus.current().id }
|
||||
val content = target.content
|
||||
|
||||
if (content is Content.Text && content.text.isEmpty()) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Replace(
|
||||
context = context,
|
||||
target = target.id,
|
||||
prototype = Prototype.Bookmark
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.CRUD.Create(
|
||||
context = context,
|
||||
position = Position.BOTTOM,
|
||||
target = target.id,
|
||||
prototype = Prototype.Bookmark
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)
|
||||
}
|
||||
|
||||
fun onAddBookmarkUrl(target: String, url: String) {
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
package com.agileburo.anytype.presentation.page.editor
|
||||
|
||||
import MockDataFactory
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.agileburo.anytype.domain.base.Either
|
||||
import com.agileburo.anytype.domain.block.interactor.CreateBlock
|
||||
import com.agileburo.anytype.domain.block.interactor.ReplaceBlock
|
||||
import com.agileburo.anytype.domain.block.model.Block
|
||||
import com.agileburo.anytype.domain.block.model.Position
|
||||
import com.agileburo.anytype.domain.event.model.Payload
|
||||
import com.agileburo.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class EditorAddBlockTest : EditorPresentationTestSetup() {
|
||||
|
||||
@get:Rule
|
||||
val rule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val coroutineTestRule = CoroutinesTestRule()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should replace currently focused text block instead of adding a new block after this text block if this text block is empty`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val text = ""
|
||||
|
||||
val block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Text(
|
||||
text = text,
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.values().random()
|
||||
),
|
||||
children = emptyList()
|
||||
)
|
||||
|
||||
val page = listOf(
|
||||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
),
|
||||
children = listOf(block.id)
|
||||
),
|
||||
block
|
||||
)
|
||||
|
||||
val newStyle = Block.Content.Text.Style.values().random()
|
||||
|
||||
val params = ReplaceBlock.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
prototype = Block.Prototype.Text(
|
||||
style = newStyle
|
||||
)
|
||||
)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubOpenDocument(document = page)
|
||||
stubReplaceBlock(params)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(id = block.id, hasFocus = true)
|
||||
onAddBlockToolbarClicked()
|
||||
}
|
||||
|
||||
// User is now selecting a new text block
|
||||
|
||||
vm.onAddTextBlockClicked(style = newStyle)
|
||||
|
||||
verifyZeroInteractions(createBlock)
|
||||
|
||||
verifyBlocking(replaceBlock, times(1)) { invoke(params) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should add new text block after currently focused text block if this focused text block is not empty`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val text = MockDataFactory.randomString()
|
||||
|
||||
val block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Text(
|
||||
text = text,
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.values().random()
|
||||
),
|
||||
children = emptyList()
|
||||
)
|
||||
|
||||
val page = listOf(
|
||||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
),
|
||||
children = listOf(block.id)
|
||||
),
|
||||
block
|
||||
)
|
||||
|
||||
val newStyle = Block.Content.Text.Style.values().random()
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
prototype = Block.Prototype.Text(
|
||||
style = newStyle
|
||||
),
|
||||
position = Position.BOTTOM
|
||||
)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubOpenDocument(document = page)
|
||||
stubCreateBlock(params)
|
||||
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(id = block.id, hasFocus = true)
|
||||
onAddBlockToolbarClicked()
|
||||
}
|
||||
|
||||
// User is now selecting a new text block
|
||||
|
||||
vm.onAddTextBlockClicked(style = newStyle)
|
||||
|
||||
verifyZeroInteractions(replaceBlock)
|
||||
verifyBlocking(createBlock, times(1)) { invoke(params) }
|
||||
}
|
||||
|
||||
private fun stubReplaceBlock(
|
||||
params: ReplaceBlock.Params
|
||||
) {
|
||||
replaceBlock.stub {
|
||||
onBlocking { invoke(params) } doReturn Either.Right(
|
||||
Pair(
|
||||
MockDataFactory.randomUuid(),
|
||||
Payload(
|
||||
context = root,
|
||||
emptyList()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stubCreateBlock(
|
||||
params: CreateBlock.Params
|
||||
) {
|
||||
createBlock.stub {
|
||||
onBlocking { invoke(params) } doReturn Either.Right(
|
||||
Pair(
|
||||
MockDataFactory.randomUuid(),
|
||||
Payload(
|
||||
context = root,
|
||||
emptyList()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue