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 9c4d4231b8..50efea5a44 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 @@ -62,6 +62,7 @@ import com.anytypeio.anytype.core_ui.menu.ObjectTypePopupMenu import com.anytypeio.anytype.core_ui.reactive.clicks import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor import com.anytypeio.anytype.core_ui.tools.EditorHeaderOverlayDetector +import com.anytypeio.anytype.core_ui.tools.LastItemBottomOffsetDecorator import com.anytypeio.anytype.core_ui.tools.MarkupColorToolbarFooter import com.anytypeio.anytype.core_ui.tools.MentionFooterItemDecorator import com.anytypeio.anytype.core_ui.tools.NoteHeaderItemDecorator @@ -85,6 +86,7 @@ import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.hide import com.anytypeio.anytype.core_utils.ext.hideSoftInput import com.anytypeio.anytype.core_utils.ext.invisible +import com.anytypeio.anytype.core_utils.ext.lastDecorator import com.anytypeio.anytype.core_utils.ext.safeNavigate import com.anytypeio.anytype.core_utils.ext.screen import com.anytypeio.anytype.core_utils.ext.show @@ -245,6 +247,12 @@ open class EditorFragment : NavigationFragment(R.layout.f ) } + private val defaultBottomOffsetDecorator by lazy { + LastItemBottomOffsetDecorator( + dimen(R.dimen.dp_48) + ) + } + private val footerMentionDecorator by lazy { MentionFooterItemDecorator(screen) } private val noteHeaderDecorator by lazy { NoteHeaderItemDecorator(offset = dimen(R.dimen.default_note_title_offset)) @@ -503,6 +511,7 @@ open class EditorFragment : NavigationFragment(R.layout.f itemAnimator = null adapter = blockAdapter addOnScrollListener(titleVisibilityDetector) + addItemDecoration(defaultBottomOffsetDecorator) } binding.toolbar.apply { @@ -1376,7 +1385,7 @@ open class EditorFragment : NavigationFragment(R.layout.f binding.styleToolbarMain.setSelectedStyle(this.state) if (behavior.state == BottomSheetBehavior.STATE_HIDDEN) { keyboardDelayJobs += lifecycleScope.launch { - if (binding.recycler.itemDecorationCount == 0) { + if (binding.recycler.lastDecorator() == defaultBottomOffsetDecorator) { binding.recycler.addItemDecoration(styleToolbarFooter) } proceedWithHidingSoftInput() @@ -1441,7 +1450,7 @@ open class EditorFragment : NavigationFragment(R.layout.f } if (behavior.state == BottomSheetBehavior.STATE_HIDDEN) { keyboardDelayJobs += lifecycleScope.launch { - if (binding.recycler.itemDecorationCount == 0) { + if (binding.recycler.lastDecorator() == defaultBottomOffsetDecorator) { binding.recycler.addItemDecoration(styleToolbarFooter) } proceedWithHidingSoftInput() @@ -1533,7 +1542,7 @@ open class EditorFragment : NavigationFragment(R.layout.f ) if (behavior.state == BottomSheetBehavior.STATE_HIDDEN) { keyboardDelayJobs += lifecycleScope.launch { - if (binding.recycler.itemDecorationCount == 0) { + if (binding.recycler.lastDecorator() == defaultBottomOffsetDecorator) { binding.recycler.addItemDecoration(styleToolbarFooter) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { @@ -1765,15 +1774,7 @@ open class EditorFragment : NavigationFragment(R.layout.f } private fun enterScrollAndMove() { - if (binding.recycler.itemDecorationCount == 0 || binding.recycler.getItemDecorationAt(0) !is ScrollAndMoveTargetHighlighter) { - -// val offset = recycler.computeVerticalScrollOffset() -// -// lifecycleScope.launch { -// recycler.layoutChanges().take(1).collect { -// if (offset < screen.y / 3) recycler.scrollBy(0, screen.y / 3) -// } -// } + if (binding.recycler.lastDecorator() !is ScrollAndMoveTargetHighlighter) { binding.recycler.addItemDecoration(scrollAndMoveTargetHighlighter) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/MentionFooterItemDecorator.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/MentionFooterItemDecorator.kt index 842f67f2a7..0fd112a121 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/MentionFooterItemDecorator.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/MentionFooterItemDecorator.kt @@ -24,4 +24,20 @@ open class MentionFooterItemDecorator(private val screen: Point) : RecyclerView. class MarkupColorToolbarFooter(screen: Point) : MentionFooterItemDecorator(screen) class SlashWidgetFooterItemDecorator(screen: Point) : MentionFooterItemDecorator(screen) -class StyleToolbarItemDecorator(screen: Point) : MentionFooterItemDecorator(screen) \ No newline at end of file +class StyleToolbarItemDecorator(screen: Point) : MentionFooterItemDecorator(screen) + + +class LastItemBottomOffsetDecorator(private val offset: Int) : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val adapter = parent.adapter ?: return + val pos = parent.getChildAdapterPosition(view) + if (pos == adapter.itemCount - 1) { + outRect.bottom = offset + } + } +} \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/ViewExtensions.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/ViewExtensions.kt index 1fb60fbb5f..8ddffcc00a 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/ViewExtensions.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/ViewExtensions.kt @@ -130,4 +130,23 @@ fun EditText.showKeyboard() { } } } +} + +inline fun RecyclerView.isLastOfType() : Boolean { + val count = itemDecorationCount + return if (count > 0) { + val lastIndex = count - 1 + val lastDecorator = getItemDecorationAt(lastIndex) + lastDecorator is T + } else { + false + } +} + +fun RecyclerView.lastDecorator() : RecyclerView.ItemDecoration? { + val count = itemDecorationCount + return if (count > 0) + getItemDecorationAt(count - 1) + else + null } \ No newline at end of file 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 8a6bd82408..96539eff37 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 @@ -2933,6 +2933,7 @@ class EditorViewModel( is Content.Text -> { when { content.style == Content.Text.Style.TITLE -> addNewBlockAtTheEnd() + content.style == Content.Text.Style.CODE_SNIPPET -> addNewBlockAtTheEnd() content.text.isNotEmpty() -> addNewBlockAtTheEnd() content.text.isEmpty() -> { val stores = orchestrator.stores diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt index c0e685ebe0..6fd85c86aa 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorEmptySpaceInteractionTest.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.editor.editor import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Position +import com.anytypeio.anytype.core_models.StubCodeSnippet import com.anytypeio.anytype.core_models.StubHeader import com.anytypeio.anytype.core_models.StubLinkToObjectBlock import com.anytypeio.anytype.core_models.StubSmartBlock @@ -354,4 +355,49 @@ class EditorEmptySpaceInteractionTest : EditorPresentationTestSetup() { ) } } + + @Test + fun `should create a new paragraph on outside-clicked event if the last block is a code snippet block`() { + + // SETUP + + val snippet = StubCodeSnippet(children = listOf()) + val title = StubTitle() + val header = StubHeader(children = listOf(title.id)) + val page = Block( + id = root, + children = listOf(header.id) + listOf(snippet.id), + fields = Block.Fields.empty(), + content = Block.Content.Smart + ) + + val document = listOf(page, header, title, snippet) + + stubInterceptEvents() + stubOpenDocument(document) + stubCreateBlock(root) + + val vm = buildViewModel() + + vm.onStart(root) + + // TESTING + + vm.onOutsideClicked() + + verifyBlocking(createBlock, times(1)) { + async( + params = eq( + CreateBlock.Params( + target = "", + context = root, + position = Position.INNER, + prototype = Block.Prototype.Text( + style = Block.Content.Text.Style.P + ) + ) + ) + ) + } + } } \ No newline at end of file diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt index f3dc43416f..bef673c28f 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt @@ -175,6 +175,21 @@ fun StubQuote( fields = Block.Fields.empty() ) +fun StubCodeSnippet( + text: String = MockDataFactory.randomString(), + children: List = emptyList(), + marks: List = emptyList() +): Block = Block( + id = MockDataFactory.randomUuid(), + content = StubTextContent( + text = text, + style = Block.Content.Text.Style.CODE_SNIPPET, + marks = marks + ), + children = children, + fields = Block.Fields.empty() +) + fun StubCallout( text: String = MockDataFactory.randomString(), children: List = emptyList(),