diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt index d6f00ea538..7564ecda68 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt @@ -18,7 +18,6 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.clipboard.Clipboard import com.anytypeio.anytype.domain.clipboard.Copy import com.anytypeio.anytype.domain.clipboard.Paste -import com.anytypeio.anytype.domain.config.FlavourConfigProvider import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.cover.RemoveDocCover @@ -34,7 +33,6 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.page.* import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark -import com.anytypeio.anytype.domain.page.navigation.GetListPages import com.anytypeio.anytype.domain.sets.FindObjectSetForType import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.status.ThreadStatusChannel diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt index feb68e84c1..5dfb4b8781 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/LinkToObjectOrWebDi.kt @@ -7,8 +7,6 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.linking.LinkToObjectOrWebViewModelFactory -import com.anytypeio.anytype.presentation.linking.LinkToObjectViewModelFactory -import com.anytypeio.anytype.ui.linking.LinkToObjectFragment import com.anytypeio.anytype.ui.linking.LinkToObjectOrWebPagesFragment import dagger.Module import dagger.Provides diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index e0fd2bbfc7..de3a7c07a8 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -95,7 +95,9 @@ import com.anytypeio.anytype.ui.editor.modals.actions.BlockActionToolbarFactory import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment.DocumentMenuActionReceiver import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuFragment import com.anytypeio.anytype.ui.linking.LinkToObjectFragment +import com.anytypeio.anytype.ui.linking.LinkToObjectOrWebPagesFragment.Companion.LINK_TO_OBJ_OR_WEB_NAME_KEY import com.anytypeio.anytype.ui.linking.LinkToObjectOrWebPagesFragment.Companion.LINK_TO_OBJ_OR_WEB_REQUEST_KEY +import com.anytypeio.anytype.ui.linking.LinkToObjectOrWebPagesFragment.Companion.LINK_TO_OBJ_OR_WEB_ID_KEY import com.anytypeio.anytype.ui.linking.LinkToObjectOrWebPagesFragment.Companion.LINK_TO_OBJ_OR_WEB_URL_KEY import com.anytypeio.anytype.ui.linking.OnLinkToAction import com.anytypeio.anytype.ui.moving.MoveToFragment @@ -428,7 +430,12 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor), } setFragmentResultListener(LINK_TO_OBJ_OR_WEB_REQUEST_KEY) {_, bundle -> val url = bundle.getString(LINK_TO_OBJ_OR_WEB_URL_KEY) - vm.onUriSelectedForTextSelection(url) + if (url != null) vm.proceedToAddUriToTextAsLink(url) + val name = bundle.getString(LINK_TO_OBJ_OR_WEB_NAME_KEY) + if (name != null) vm.proceedToCreateObjectAndAddToTextAsLink(name) + val id = bundle.getString(LINK_TO_OBJ_OR_WEB_ID_KEY) + if (id != null) vm.proceedToAddObjectToTextAsLink(id) + Timber.d("FragmentResultListener, request:[$LINK_TO_OBJ_OR_WEB_REQUEST_KEY], url:[$url], name:[$name], id:[$id]") } pickiT = PickiT(requireContext(), this, requireActivity()) setupOnBackPressedDispatcher() @@ -576,11 +583,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor), .onEach { type -> vm.onStyleToolbarMarkupAction(type, null) } .launchIn(lifecycleScope) - setMarkupUrlToolbar - .onApply() - .onEach { vm.onUriSelectedForTextSelection(it) } - .launchIn(lifecycleScope) - blockActionToolbar.actionListener = { action -> vm.onMultiSelectAction(action) } markupColorToolbar.onColorClickedListener = { color -> @@ -597,10 +599,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor), } } - blocker.clicks() - .onEach { vm.onBlockerClicked() } - .launchIn(lifecycleScope) - // topToolbar.back.clicks().onEach { // hideSoftInput() // vm.onBackButtonPressed() @@ -1057,6 +1055,9 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor), R.id.action_pageScreen_to_linkToObjectOrWebPagesFragment ) } + Command.ShowKeyboard -> { + recycler.findFocus()?.focusAndShowKeyboard() + } } } } @@ -1224,18 +1225,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor), setMainMarkupToolbarState(state) - state.markupUrlToolbar.apply { - if (isVisible) { - setMarkupUrlToolbar.visible() - setMarkupUrlToolbar.takeFocus() - setMarkupUrlToolbar.bind(state.markupMainToolbar.style?.markupUrl) - blocker.visible() - } else { - setMarkupUrlToolbar.invisible() - blocker.invisible() - } - } - state.multiSelect.apply { val behavior = BottomSheetBehavior.from(blockActionToolbar) if (isVisible) { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/linking/LinkToObjectOrWebPagesFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/linking/LinkToObjectOrWebPagesFragment.kt index afa3175f03..75de47b977 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/linking/LinkToObjectOrWebPagesFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/linking/LinkToObjectOrWebPagesFragment.kt @@ -26,6 +26,7 @@ import com.anytypeio.anytype.ui.search.ObjectSearchFragment import com.google.android.material.bottomsheet.BottomSheetBehavior import kotlinx.android.synthetic.main.fragment_link_to_object_or_web.* import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject class LinkToObjectOrWebPagesFragment() : BaseBottomSheetFragment() { @@ -99,6 +100,7 @@ class LinkToObjectOrWebPagesFragment() : BaseBottomSheetFragment() { } private fun execute(command: LinkToObjectOrWebViewModel.Command) { + Timber.d("execute, command:[$command]") when (command) { LinkToObjectOrWebViewModel.Command.Exit -> { hideSoftInput() @@ -110,6 +112,18 @@ class LinkToObjectOrWebPagesFragment() : BaseBottomSheetFragment() { hideSoftInput() dismiss() } + is LinkToObjectOrWebViewModel.Command.SetObjectLink -> { + val bundle = bundleOf(LINK_TO_OBJ_OR_WEB_ID_KEY to command.target) + setFragmentResult(LINK_TO_OBJ_OR_WEB_REQUEST_KEY, bundle) + hideSoftInput() + dismiss() + } + is LinkToObjectOrWebViewModel.Command.CreateObject -> { + val bundle = bundleOf(LINK_TO_OBJ_OR_WEB_NAME_KEY to command.name) + setFragmentResult(LINK_TO_OBJ_OR_WEB_REQUEST_KEY, bundle) + hideSoftInput() + dismiss() + } } } @@ -150,5 +164,7 @@ class LinkToObjectOrWebPagesFragment() : BaseBottomSheetFragment() { companion object { const val LINK_TO_OBJ_OR_WEB_REQUEST_KEY = "link-to-object-or-web.request" const val LINK_TO_OBJ_OR_WEB_URL_KEY = "link-to-object-or-web.url.key" + const val LINK_TO_OBJ_OR_WEB_ID_KEY = "link-to-object-or-web.id.key" + const val LINK_TO_OBJ_OR_WEB_NAME_KEY = "link-to-object-or-web.name.key" } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml index bd3266a6b8..0449cfba9f 100644 --- a/app/src/main/res/layout/fragment_editor.xml +++ b/app/src/main/res/layout/fragment_editor.xml @@ -89,15 +89,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> - - - - () + this.forEach { mark -> + if (!mark.isClickableMark()) { + result.add(mark) + } else { + if (mark.rangeIntersection(newMark.range) == NO_RANGE_INTERSECTION) { + result.add(mark) + } + } + } + result.add(newMark) + return result.toList() +} + fun Marks.sortByType(): Marks { return this.sortedBy { it.type.ordinal } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt index 240376f292..6bcdde1e88 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt @@ -146,8 +146,6 @@ sealed class ControlPanelMachine { object OnMarkupColorToggleClicked : MarkupToolbar() object OnMarkupHighlightToggleClicked : MarkupToolbar() object OnMarkupToolbarUrlClicked : MarkupToolbar() - object OnMarkupUrlSet : MarkupToolbar() - object OnBlockerClicked : MarkupToolbar() } /** @@ -378,24 +376,7 @@ sealed class ControlPanelMachine { markupColorToolbar = state.markupColorToolbar.copy( isVisible = false ), - markupMainToolbar = state.markupMainToolbar.copy(), - markupUrlToolbar = state.markupUrlToolbar.copy( - isVisible = true - ) - ) - } - is Event.MarkupToolbar.OnMarkupUrlSet -> { - state.copy( - markupUrlToolbar = state.markupUrlToolbar.copy( - isVisible = false - ) - ) - } - is Event.MarkupToolbar.OnBlockerClicked -> { - state.copy( - markupUrlToolbar = state.markupUrlToolbar.copy( - isVisible = false - ) + markupMainToolbar = state.markupMainToolbar.copy() ) } is Event.OnMarkupTextColorSelected -> state.copy() 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 db0a2184b8..883fc6af63 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 @@ -345,22 +345,10 @@ class EditorViewModel( .onEach { (action, textSelection) -> val range = textSelection.selection if (textSelection.isNotEmpty && range != null && range.first != range.last) { - if (action.type == Markup.Type.LINK) { - val block = blocks.first { it.id == textSelection.id } - stateData.value = ViewState.OpenLinkScreen( - pageId = context, - block = block, - range = IntRange( - start = range.first, - endInclusive = range.last.dec() - ) - ) - } else { - applyMarkup( - selection = Pair(textSelection.id, range), - action = action - ) - } + applyMarkup( + selection = Pair(textSelection.id, range), + action = action + ) } } .launchIn(viewModelScope) @@ -4477,29 +4465,8 @@ class EditorViewModel( //region MARKUP TOOLBAR fun onMarkupUrlClicked() { - Timber.d("onMarkupUrlClicked, ") - val target = orchestrator.stores.focus.current().id - val selection = orchestrator.stores.textSelection.current().selection!! - - pending.add( - Restore.Selection( - target = target, - range = selection - ) - ) - - val update = views.map { view -> - if (view.id == target) { - view.setGhostEditorSelection(selection) - } else { - view - } - } - - viewModelScope.launch { orchestrator.stores.views.update(update) } - viewModelScope.launch { renderCommand.send(Unit) } dispatch( Command.OpenLinkToObjectOrWebScreen( target = target @@ -4507,55 +4474,6 @@ class EditorViewModel( ) } - fun onUriSelectedForTextSelection(uri: String?) { - Timber.d("onSetLink, url:[$uri]") - if (uri == null) { - Timber.e("Can't set nullable url link") - return - } - val range = orchestrator.stores.textSelection.current().selection - if (range != null) { - val target = orchestrator.stores.focus.current().id - restore.add(pending.poll()) - if (uri.isNotEmpty()) - applyLinkMarkup( - blockId = target, - link = uri, - range = range.first..range.last.dec() - ) - else - onUnlinkPressed( - blockId = target, - range = range.first..range.last.dec() - ) - controlPanelInteractor.onEvent( - event = ControlPanelMachine.Event.MarkupToolbar.OnMarkupUrlSet - ) - } - } - - fun onBlockerClicked() { - Timber.d("onBlockerClicked, ") - val target = orchestrator.stores.focus.current().id - val update = views.map { view -> - if (view.id == target) { - view.setGhostEditorSelection(null).apply { - if (this is Focusable) { - isFocused = true - } - } - } else { - view - } - } - restore.add(pending.poll()) - viewModelScope.launch { orchestrator.stores.views.update(update) } - viewModelScope.launch { renderCommand.send(Unit) } - controlPanelInteractor.onEvent( - event = ControlPanelMachine.Event.MarkupToolbar.OnBlockerClicked - ) - } - fun onUnlinkPressed(blockId: String, range: IntRange) { Timber.d("onUnlinkPressed, blockId:[$blockId] range:[$range]") @@ -5378,4 +5296,66 @@ class EditorViewModel( ) } } + + //region ADD URI OR OBJECT ID TO SELECTED TEXT + fun proceedToCreateObjectAndAddToTextAsLink(name: String) { + Timber.d("proceedToCreateObjectAndAddToTextAsLink, name:[$name]") + viewModelScope.launch { + getDefaultEditorType.invoke(Unit).proceed( + failure = { Timber.e(it, "Error while getting default object type") }, + success = { response -> + createObjectAddProceedToAddToTextAsLink( + name = name, + type = response.type + ) + } + ) + } + } + + private suspend fun createObjectAddProceedToAddToTextAsLink(name: String, type: String?) { + val params = CreateNewDocument.Params(name, type) + createNewDocument.invoke(params).process( + failure = { Timber.e(it, "Error while creating new page with params: $params") }, + success = { result -> proceedToAddObjectToTextAsLink(id = result.id) } + ) + } + + fun proceedToAddObjectToTextAsLink(id: Id) { + Timber.d("proceedToAddObjectToTextAsLink, target:[$id]") + val range = orchestrator.stores.textSelection.current().selection + if (range != null) { + dispatch(Command.ShowKeyboard) + viewModelScope.launch { + markupActionPipeline.send( + MarkupAction( + type = Markup.Type.OBJECT, + param = id + ) + ) + } + } + } + + fun proceedToAddUriToTextAsLink(uri: String) { + Timber.d("proceedToAddUriToTextAsLink, uri:[$uri]") + val range = orchestrator.stores.textSelection.current().selection + if (range != null) { + val target = orchestrator.stores.focus.current().id + if (uri.isNotEmpty()) + applyLinkMarkup( + blockId = target, + link = uri, + range = range.first..range.last.dec() + ) + else + onUnlinkPressed( + blockId = target, + range = range.first..range.last.dec() + ) + } else { + Timber.e("Can't add uri to text, range is null") + } + } + //endregion } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt index 8dc3fb474c..18cb41c37d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Command.kt @@ -50,6 +50,7 @@ sealed class Command { object PopBackStack : Command() + object ShowKeyboard : Command() object CloseKeyboard : Command() object ClearSearchInput : Command() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Transformation.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Transformation.kt index d51f351994..9df33d9703 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Transformation.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Transformation.kt @@ -2,8 +2,10 @@ package com.anytypeio.anytype.presentation.editor.editor import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Block.Content.Text.Mark +import com.anytypeio.anytype.core_models.ext.addClickableMark import com.anytypeio.anytype.core_models.ext.addMark import com.anytypeio.anytype.core_models.ext.content +import com.anytypeio.anytype.core_models.ext.sortByType import com.anytypeio.anytype.presentation.editor.model.TextUpdate fun Block.updateText(update: TextUpdate): Block { @@ -41,7 +43,13 @@ fun Block.markup( param = param ) - val marks = content.marks.addMark(new) + return copy(content = content.addMarkToContent(new)) +} - return copy(content = content.copy(marks = marks)) +fun Block.Content.Text.addMarkToContent(mark: Mark): Block.Content.Text { + return if (mark.isClickableMark()) { + this.copy(marks = marks.addClickableMark(mark).sortByType()) + } else { + this.copy(marks = marks.addMark(mark)) + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt index 144ca90324..14fb73f6db 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt @@ -23,7 +23,6 @@ data class ControlPanelState( val styleExtraToolbar: Toolbar.Styling.Other = Toolbar.Styling.Other(), val styleColorToolbar: Toolbar.Styling.Color = Toolbar.Styling.Color(), val markupMainToolbar: Toolbar.MarkupMainToolbar = Toolbar.MarkupMainToolbar.reset(), - val markupUrlToolbar: Toolbar.MarkupUrlToolbar = Toolbar.MarkupUrlToolbar(), val markupColorToolbar: Toolbar.MarkupColorToolbar = Toolbar.MarkupColorToolbar(), val multiSelect: Toolbar.MultiSelect, val mentionToolbar: Toolbar.MentionToolbar, @@ -86,19 +85,6 @@ data class ControlPanelState( override val isVisible: Boolean = false ) : Toolbar() - /** - * TODO Markup toolbar allowing user-interface for markup operations. - * @property isVisible defines whether the toolbar is visible or not - */ - data class MarkupUrlToolbar( - override val isVisible: Boolean = false, - val url: String? = null - ) : Toolbar() { - companion object { - fun reset() = MarkupUrlToolbar() - } - } - /** * Basic color toolbar state. * @property isVisible defines whether the toolbar is visible or not @@ -283,7 +269,6 @@ data class ControlPanelState( markupMainToolbar = Toolbar.MarkupMainToolbar( isVisible = false ), - markupUrlToolbar = Toolbar.MarkupUrlToolbar(), markupColorToolbar = Toolbar.MarkupColorToolbar(), multiSelect = Toolbar.MultiSelect( isVisible = false, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt index 963dfe2a88..fe4ff00520 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/linking/LinkToObjectOrWebViewModel.kt @@ -53,15 +53,19 @@ class LinkToObjectOrWebViewModel( } fun onClicked(item: LinkToItemView) { + Timber.d("onClicked, item:[$item] ") viewModelScope.launch { when (item) { - is LinkToItemView.CreateObject -> TODO() - is LinkToItemView.Object -> TODO() - LinkToItemView.Subheading.Objects -> TODO() - LinkToItemView.Subheading.Web -> TODO() + is LinkToItemView.CreateObject -> { + commands.emit(Command.CreateObject(item.title)) + } + is LinkToItemView.Object -> { + commands.emit(Command.SetObjectLink(item.id)) + } is LinkToItemView.WebItem -> { commands.emit(Command.SetWebLink(item.url)) } + else -> Unit } } } @@ -95,7 +99,7 @@ class LinkToObjectOrWebViewModel( } } - fun setObjects(data: List) { + private fun setObjects(data: List) { objects.value = data.filter { SupportedLayouts.layouts.contains(it.layout) } @@ -105,10 +109,6 @@ class LinkToObjectOrWebViewModel( userInput.value = searchText } - fun onObjectClicked(target: Id, layout: ObjectType.Layout?) { - //todo will be fixed in next PR - } - private fun proceedWithNewFilter(filter: String) { if (filter.isEmpty()) { onEmptyFilterState() @@ -172,6 +172,8 @@ class LinkToObjectOrWebViewModel( sealed class Command { object Exit : Command() data class SetWebLink(val url: String) : Command() + data class SetObjectLink(val target: Id) : Command() + data class CreateObject(val name: String) : Command() } sealed class ViewState { diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMarkupObjectTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMarkupObjectTest.kt new file mode 100644 index 0000000000..ee4631a087 --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMarkupObjectTest.kt @@ -0,0 +1,472 @@ +package com.anytypeio.anytype.presentation.editor.editor + +import MockDataFactory +import android.util.Log +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.ObjectType.Companion.PAGE_URL +import com.anytypeio.anytype.core_models.SmartBlockType +import com.anytypeio.anytype.core_models.ext.content +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.util.CoroutinesTestRule +import com.anytypeio.anytype.presentation.util.TXT +import com.jraska.livedata.test +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 + +class EditorMarkupObjectTest : EditorPresentationTestSetup() { + + @get:Rule + val rule = InstantTaskExecutorRule() + + @get:Rule + val coroutineTestRule = CoroutinesTestRule() + + @get:Rule + val timberTestRule: TimberTestRule = TimberTestRule.builder() + .minPriority(Log.DEBUG) + .showThread(true) + .showTimestamp(false) + .onlyLogWhenTestFails(true) + .build() + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + } + + @After + fun after() { + coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION) + } + + @Test + fun `should add object markup to text`() { + val title = MockTypicalDocumentFactory.title + val header = MockTypicalDocumentFactory.header + val block = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "Start Foobar End", + marks = emptyList(), + style = Block.Content.Text.Style.P + ), + children = emptyList() + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id, block.id) + ) + + val doc = listOf(page, header, title, block) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubOpenDocument( + document = doc, + details = Block.Details(), + relations = listOf() + ) + stubUpdateText() + + val vm = buildViewModel() + val linkObject = MockDataFactory.randomString() + + //TESTING + vm.apply { + onStart(root) + onBlockFocusChanged( + id = block.id, + hasFocus = true + ) + onSelectionChanged( + id = block.id, + selection = IntRange(6, 12) + ) + proceedToAddObjectToTextAsLink(id = linkObject) + } + + vm.state.test().apply { + assertValue( + ViewState.Success( + blocks = listOf( + BlockView.Title.Basic( + id = title.id, + isFocused = false, + text = title.content().text, + mode = BlockView.Mode.EDIT + ), + BlockView.Text.Paragraph( + id = block.id, + cursor = null, + isSelected = false, + isFocused = true, + marks = listOf( + Markup.Mark( + from = 6, + to = 12, + type = Markup.Type.OBJECT, + param = linkObject + ) + ), + backgroundColor = null, + color = null, + indent = 0, + text = "Start Foobar End", + mode = BlockView.Mode.EDIT + ) + ) + ) + ) + } + } + + @Test + fun `should add object markup to text end remove all clicked marks in range`() { + val title = MockTypicalDocumentFactory.title + val header = MockTypicalDocumentFactory.header + val linkObject = MockDataFactory.randomUuid() + val linkWeb = MockDataFactory.randomString() + val linkMention = MockDataFactory.randomUuid() + + val block = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "Start Link Object Mention End", + marks = listOf( + Block.Content.Text.Mark( + range = IntRange( + start = 0, + endInclusive = 5 + ), + type = Block.Content.Text.Mark.Type.BOLD + ), + Block.Content.Text.Mark( + range = IntRange( + start = 6, + endInclusive = 10 + ), + type = Block.Content.Text.Mark.Type.LINK, + param = linkWeb + ), + Block.Content.Text.Mark( + range = IntRange( + start = 6, + endInclusive = 10 + ), + type = Block.Content.Text.Mark.Type.BOLD + ), + Block.Content.Text.Mark( + range = IntRange( + start = 11, + endInclusive = 17 + ), + type = Block.Content.Text.Mark.Type.OBJECT, + param = linkObject + ), + Block.Content.Text.Mark( + range = IntRange( + start = 18, + endInclusive = 25 + ), + type = Block.Content.Text.Mark.Type.MENTION, + param = linkMention + ), + Block.Content.Text.Mark( + range = IntRange( + start = 18, + endInclusive = 25 + ), + type = Block.Content.Text.Mark.Type.TEXT_COLOR, + param = "#000" + ), + Block.Content.Text.Mark( + range = IntRange( + start = 26, + endInclusive = 29 + ), + type = Block.Content.Text.Mark.Type.ITALIC + ) + ), + style = Block.Content.Text.Style.P + ), + children = emptyList() + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id, block.id) + ) + + val doc = listOf(page, header, title, block) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubOpenDocument( + document = doc, + details = Block.Details(), + relations = listOf() + ) + stubUpdateText() + + val vm = buildViewModel() + val linkNew = MockDataFactory.randomString() + + //TESTING + vm.apply { + onStart(root) + onBlockFocusChanged( + id = block.id, + hasFocus = true + ) + onSelectionChanged( + id = block.id, + selection = IntRange(6, 25) + ) + proceedToAddObjectToTextAsLink(id = linkNew) + } + + val expected = ViewState.Success( + blocks = listOf( + BlockView.Title.Basic( + id = title.id, + isFocused = false, + text = title.content().text, + mode = BlockView.Mode.EDIT + ), + BlockView.Text.Paragraph( + id = block.id, + cursor = null, + isSelected = false, + isFocused = true, + marks = listOf( + Markup.Mark( + from = 26, + to = 29, + type = Markup.Type.ITALIC + ), + Markup.Mark( + from = 0, + to = 5, + type = Markup.Type.BOLD + ), + Markup.Mark( + from = 6, + to = 10, + type = Markup.Type.BOLD + ), + Markup.Mark( + from = 18, + to = 25, + type = Markup.Type.TEXT_COLOR, + param = "#000" + ), + Markup.Mark( + from = 6, + to = 25, + type = Markup.Type.OBJECT, + param = linkNew + ), + + ), + backgroundColor = null, + color = null, + indent = 0, + text = "Start Link Object Mention End", + mode = BlockView.Mode.EDIT + ) + ) + ) + + val actual = vm.state.value + assertEquals(expected, actual) + } + + @Test + fun `should create object and add markup to text end remove all clicked marks in range`() { + val title = MockTypicalDocumentFactory.title + val header = MockTypicalDocumentFactory.header + val linkObject = MockDataFactory.randomUuid() + val linkWeb = MockDataFactory.randomString() + val linkMention = MockDataFactory.randomUuid() + + val block = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "Start Link Object Mention End", + marks = listOf( + Block.Content.Text.Mark( + range = IntRange( + start = 0, + endInclusive = 5 + ), + type = Block.Content.Text.Mark.Type.BOLD + ), + Block.Content.Text.Mark( + range = IntRange( + start = 6, + endInclusive = 10 + ), + type = Block.Content.Text.Mark.Type.LINK, + param = linkWeb + ), + Block.Content.Text.Mark( + range = IntRange( + start = 6, + endInclusive = 10 + ), + type = Block.Content.Text.Mark.Type.BOLD + ), + Block.Content.Text.Mark( + range = IntRange( + start = 11, + endInclusive = 17 + ), + type = Block.Content.Text.Mark.Type.OBJECT, + param = linkObject + ), + Block.Content.Text.Mark( + range = IntRange( + start = 18, + endInclusive = 25 + ), + type = Block.Content.Text.Mark.Type.MENTION, + param = linkMention + ), + Block.Content.Text.Mark( + range = IntRange( + start = 18, + endInclusive = 25 + ), + type = Block.Content.Text.Mark.Type.TEXT_COLOR, + param = "#000" + ), + Block.Content.Text.Mark( + range = IntRange( + start = 26, + endInclusive = 29 + ), + type = Block.Content.Text.Mark.Type.ITALIC + ) + ), + style = Block.Content.Text.Style.P + ), + children = emptyList() + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id, block.id) + ) + + val doc = listOf(page, header, title, block) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubOpenDocument( + document = doc, + details = Block.Details(), + relations = listOf() + ) + stubUpdateText() + val vm = buildViewModel() + val linkNew = MockDataFactory.randomString() + + //TESTING + val newObjectType = PAGE_URL + val newObjectId = MockDataFactory.randomString() + val newObjectName = MockDataFactory.randomString() + stubCreateNewDocument( + name = newObjectName, + type = newObjectType, + id = newObjectId + ) + stubGetDefaultObjectType(type = newObjectType) + vm.apply { + onStart(root) + onBlockFocusChanged( + id = block.id, + hasFocus = true + ) + onSelectionChanged( + id = block.id, + selection = IntRange(6, 25) + ) + proceedToCreateObjectAndAddToTextAsLink(name = newObjectName) + } + + val expected = ViewState.Success( + blocks = listOf( + BlockView.Title.Basic( + id = title.id, + isFocused = false, + text = title.content().text, + mode = BlockView.Mode.EDIT + ), + BlockView.Text.Paragraph( + id = block.id, + cursor = null, + isSelected = false, + isFocused = true, + marks = listOf( + Markup.Mark( + from = 26, + to = 29, + type = Markup.Type.ITALIC + ), + Markup.Mark( + from = 0, + to = 5, + type = Markup.Type.BOLD + ), + Markup.Mark( + from = 6, + to = 10, + type = Markup.Type.BOLD + ), + Markup.Mark( + from = 18, + to = 25, + type = Markup.Type.TEXT_COLOR, + param = "#000" + ), + Markup.Mark( + from = 6, + to = 25, + type = Markup.Type.OBJECT, + param = newObjectId + ), + ), + backgroundColor = null, + color = null, + indent = 0, + text = "Start Link Object Mention End", + mode = BlockView.Mode.EDIT + ) + ) + ) + + val actual = vm.state.value + assertEquals(expected, actual) + } +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt index f9bfa989dd..1b3ff0ade1 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt @@ -535,4 +535,12 @@ open class EditorPresentationTestSetup { onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type, name)) } } + + fun stubCreateNewDocument(name: String, type: String, id: String) { + val params = CreateNewDocument.Params(name, type) + val result = CreateNewDocument.Result(id, name, null) + createNewDocument.stub { + onBlocking { invoke(params) } doReturn Either.Right(result) + } + } } \ No newline at end of file