diff --git a/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt b/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt index 9a2601f49f..4e9169c320 100644 --- a/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt +++ b/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt @@ -41,7 +41,8 @@ class PageModule { interceptEvents: InterceptEvents, updateCheckbox: UpdateCheckbox, unlinkBlocks: UnlinkBlocks, - duplicateBlock: DuplicateBlock + duplicateBlock: DuplicateBlock, + updateTextStyle: UpdateTextStyle ): PageViewModelFactory = PageViewModelFactory( openPage = openPage, closePage = closePage, @@ -50,7 +51,8 @@ class PageModule { interceptEvents = interceptEvents, updateCheckbox = updateCheckbox, unlinkBlocks = unlinkBlocks, - duplicateBlock = duplicateBlock + duplicateBlock = duplicateBlock, + updateTextStyle = updateTextStyle ) @Provides @@ -125,4 +127,12 @@ class PageModule { ): DuplicateBlock = DuplicateBlock( repo = repo ) + + @Provides + @PerScreen + fun provideUpdateTextStyleUseCase( + repo: BlockRepository + ): UpdateTextStyle = UpdateTextStyle( + repo = repo + ) } \ No newline at end of file diff --git a/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt b/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt index e0d97549e3..2e5aa33992 100644 --- a/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt +++ b/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt @@ -20,6 +20,7 @@ import com.agileburo.anytype.core_ui.tools.OutsideClickDetector import com.agileburo.anytype.core_ui.widgets.toolbar.ActionToolbarWidget.ActionConfig.ACTION_DELETE import com.agileburo.anytype.core_ui.widgets.toolbar.ActionToolbarWidget.ActionConfig.ACTION_DUPLICATE import com.agileburo.anytype.core_ui.widgets.toolbar.ColorToolbarWidget +import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.Option import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_LIST_BULLETED_LIST import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionConfig.OPTION_LIST_CHECKBOX @@ -113,6 +114,11 @@ class PageFragment : NavigationFragment(R.layout.fragment_page) { .onEach { vm.onHideKeyboardClicked() } .launchIn(lifecycleScope) + toolbar + .turnIntoClicks() + .onEach { vm.onTurnIntoToolbarToggleClicked() } + .launchIn(lifecycleScope) + toolbar .addButtonClicks() .onEach { vm.onAddBlockToolbarClicked() } @@ -151,27 +157,10 @@ class PageFragment : NavigationFragment(R.layout.fragment_page) { optionToolbar .optionClicks() .onEach { option -> - when (option) { - is Option.Text -> { - when (option.type) { - OPTION_TEXT_TEXT -> vm.onAddTextBlockClicked(Text.Style.P) - OPTION_TEXT_HEADER_ONE -> vm.onAddTextBlockClicked(Text.Style.H1) - OPTION_TEXT_HEADER_TWO -> vm.onAddTextBlockClicked(Text.Style.H2) - OPTION_TEXT_HEADER_THREE -> vm.onAddTextBlockClicked(Text.Style.H3) - OPTION_TEXT_HIGHLIGHTED -> vm.onAddTextBlockClicked(Text.Style.QUOTE) - OPTION_LIST_BULLETED_LIST -> vm.onAddTextBlockClicked(Text.Style.BULLET) - OPTION_LIST_CHECKBOX -> vm.onAddTextBlockClicked(Text.Style.CHECKBOX) - else -> toast(NOT_IMPLEMENTED_MESSAGE) - } - } - is Option.List -> { - when (option.type) { - OPTION_LIST_BULLETED_LIST -> vm.onAddTextBlockClicked(Text.Style.BULLET) - OPTION_LIST_CHECKBOX -> vm.onAddTextBlockClicked(Text.Style.CHECKBOX) - else -> toast(NOT_IMPLEMENTED_MESSAGE) - } - } - } + if (optionToolbar.state == OptionToolbarWidget.State.ADD_BLOCK) + handleAddBlockToolbarOptionClicked(option) + else if (optionToolbar.state == OptionToolbarWidget.State.TURN_INTO) + handleTurnIntoToolbarOptionClicked(option) } .launchIn(lifecycleScope) @@ -189,6 +178,50 @@ class PageFragment : NavigationFragment(R.layout.fragment_page) { fab.clicks().onEach { toast(NOT_IMPLEMENTED_MESSAGE) }.launchIn(lifecycleScope) } + private fun handleAddBlockToolbarOptionClicked(option: Option) { + when (option) { + is Option.Text -> { + when (option.type) { + OPTION_TEXT_TEXT -> vm.onAddTextBlockClicked(Text.Style.P) + OPTION_TEXT_HEADER_ONE -> vm.onAddTextBlockClicked(Text.Style.H1) + OPTION_TEXT_HEADER_TWO -> vm.onAddTextBlockClicked(Text.Style.H2) + OPTION_TEXT_HEADER_THREE -> vm.onAddTextBlockClicked(Text.Style.H3) + OPTION_TEXT_HIGHLIGHTED -> vm.onAddTextBlockClicked(Text.Style.QUOTE) + else -> toast(NOT_IMPLEMENTED_MESSAGE) + } + } + is Option.List -> { + when (option.type) { + OPTION_LIST_BULLETED_LIST -> vm.onAddTextBlockClicked(Text.Style.BULLET) + OPTION_LIST_CHECKBOX -> vm.onAddTextBlockClicked(Text.Style.CHECKBOX) + else -> toast(NOT_IMPLEMENTED_MESSAGE) + } + } + } + } + + private fun handleTurnIntoToolbarOptionClicked(option: Option) { + when (option) { + is Option.Text -> { + when (option.type) { + OPTION_TEXT_TEXT -> vm.onTurnIntoStyleClicked(Text.Style.P) + OPTION_TEXT_HEADER_ONE -> vm.onTurnIntoStyleClicked(Text.Style.H1) + OPTION_TEXT_HEADER_TWO -> vm.onTurnIntoStyleClicked(Text.Style.H2) + OPTION_TEXT_HEADER_THREE -> vm.onTurnIntoStyleClicked(Text.Style.H3) + OPTION_TEXT_HIGHLIGHTED -> vm.onTurnIntoStyleClicked(Text.Style.QUOTE) + else -> toast(NOT_IMPLEMENTED_MESSAGE) + } + } + is Option.List -> { + when (option.type) { + OPTION_LIST_BULLETED_LIST -> vm.onTurnIntoStyleClicked(Text.Style.BULLET) + OPTION_LIST_CHECKBOX -> vm.onTurnIntoStyleClicked(Text.Style.CHECKBOX) + else -> toast(NOT_IMPLEMENTED_MESSAGE) + } + } + } + } + private fun showColorToolbar() { colorToolbar.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) colorToolbar.visible() @@ -240,6 +273,7 @@ class PageFragment : NavigationFragment(R.layout.fragment_page) { Timber.d("Rendering new control panel state:\n$state") markupToolbar.setState(state.markupToolbar) toolbar.setState(state.blockToolbar) + state.focus?.let { toolbar.setTurnIntoTarget(it.type) } with(state.colorToolbar) { if (isVisible) { hideKeyboard() @@ -250,15 +284,31 @@ class PageFragment : NavigationFragment(R.layout.fragment_page) { } else hideColorToolbar() } - with(state.addBlockToolbar) { + state.addBlockToolbar.apply { if (isVisible) { + optionToolbar.state = OptionToolbarWidget.State.ADD_BLOCK hideKeyboard() lifecycleScope.launch { delay(300) showOptionToolbar() } - } else - hideOptionToolbar() + } else { + if (!state.turnIntoToolbar.isVisible) + hideOptionToolbar() + } + } + state.turnIntoToolbar.apply { + if (isVisible) { + optionToolbar.state = OptionToolbarWidget.State.TURN_INTO + hideKeyboard() + lifecycleScope.launch { + delay(300) + optionToolbar.show() + } + } else { + if (!state.addBlockToolbar.isVisible) + hideOptionToolbar() + } } with(state.actionToolbar) { if (isVisible) { diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt index b03eb9c0f8..7e73de1e1b 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt @@ -362,13 +362,13 @@ class BlockAdapter( if (holder is BlockViewHolder.TextHolder) { holder.enableEnterKeyDetector( onEndLineEnterClicked = { - onEndLineEnterClicked(blocks[position].id) + onEndLineEnterClicked(blocks[holder.adapterPosition].id) }, onSplitLineEnterClicked = { - onSplitLineEnterClicked(blocks[position].id) + onSplitLineEnterClicked(blocks[holder.adapterPosition].id) } ) - holder.enableBackspaceDetector { onEmptyBlockBackspaceClicked(blocks[position].id) } + holder.enableBackspaceDetector { onEmptyBlockBackspaceClicked(blocks[holder.adapterPosition].id) } } } @@ -382,6 +382,7 @@ class BlockAdapter( } } + @Deprecated( level = DeprecationLevel.WARNING, message = "Consider RecyclerView's AsyncListDiffer instead. Or implement it with Kotlin coroutines." diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt index 328003db79..f5b51a2e3b 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt @@ -76,6 +76,7 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { BackspaceKeyDetector { if (content.text.toString().isEmpty()) { content.clearTextWatchers() + content.setOnKeyListener(null) onEmptyBlockBackspaceClicked() } } diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/state/ControlPanelState.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/state/ControlPanelState.kt index 3c02b62167..a860701d73 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/state/ControlPanelState.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/state/ControlPanelState.kt @@ -9,10 +9,12 @@ package com.agileburo.anytype.core_ui.state * @property colorToolbar color toolbar state * @property addBlockToolbar add-block toolbar state * @property actionToolbar action-toolbar state + * @property turnIntoToolbar turn-into tolbar state */ data class ControlPanelState( val focus: Focus? = null, val blockToolbar: Toolbar.Block, + val turnIntoToolbar: Toolbar.TurnInto, val markupToolbar: Toolbar.Markup, val colorToolbar: Toolbar.Color, val addBlockToolbar: Toolbar.AddBlock, @@ -79,18 +81,25 @@ data class ControlPanelState( ) : Toolbar() /** - * TODO + * Turn-into (converting one block into another) toolbar state. */ data class TurnInto( - val isVisible: Boolean - ) + override val isVisible: Boolean + ) : Toolbar() } /** * Block currently associated with this panel. * @property id id of the focused block */ - data class Focus(val id: String) + data class Focus( + val id: String, + val type: Type + ) { + enum class Type { + P, H1, H2, H3, H4, TITLE, QUOTE, CODE_SNIPPET, BULLET, NUMBERED, TOGGLE, CHECKBOX + } + } companion object { @@ -106,6 +115,9 @@ data class ControlPanelState( isVisible = false, selectedAction = null ), + turnIntoToolbar = Toolbar.TurnInto( + isVisible = false + ), colorToolbar = Toolbar.Color( isVisible = false ), diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/BlockToolbarWidget.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/BlockToolbarWidget.kt index 06f9ef556e..ef5b3dd6c1 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/BlockToolbarWidget.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/BlockToolbarWidget.kt @@ -5,12 +5,13 @@ import android.util.AttributeSet import android.view.LayoutInflater import androidx.constraintlayout.widget.ConstraintLayout import com.agileburo.anytype.core_ui.R +import com.agileburo.anytype.core_ui.extensions.color import com.agileburo.anytype.core_ui.extensions.invisible import com.agileburo.anytype.core_ui.extensions.visible import com.agileburo.anytype.core_ui.reactive.clicks import com.agileburo.anytype.core_ui.state.ControlPanelState -import com.agileburo.anytype.core_ui.state.ControlPanelState.Toolbar.Block.Action.ADD -import com.agileburo.anytype.core_ui.state.ControlPanelState.Toolbar.Block.Action.BLOCK_ACTION +import com.agileburo.anytype.core_ui.state.ControlPanelState.Focus +import com.agileburo.anytype.core_ui.state.ControlPanelState.Toolbar.Block.Action.* import kotlinx.android.synthetic.main.widget_block_toolbar.view.* class BlockToolbarWidget : ConstraintLayout { @@ -39,10 +40,48 @@ class BlockToolbarWidget : ConstraintLayout { fun keyboardClicks() = keyboard.clicks() fun addButtonClicks() = add.clicks() fun actionClicks() = actions.clicks() + fun turnIntoClicks() = turnIntoToggle.clicks() fun setState(state: ControlPanelState.Toolbar.Block) { if (state.isVisible) visible() else invisible() add.isSelected = state.selectedAction == ADD actions.isSelected = state.selectedAction == BLOCK_ACTION + turnIntoToggle.isSelected = state.selectedAction == TURN_INTO + arrow.isSelected = state.selectedAction == TURN_INTO + } + + fun setTurnIntoTarget( + target: Focus.Type + ) { + when (target) { + Focus.Type.P -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_paragraph) + } + Focus.Type.H1 -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_header_one) + } + Focus.Type.H2 -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_header_two) + } + Focus.Type.H3 -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_header_three) + } + Focus.Type.CHECKBOX -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_checkbox) + } + Focus.Type.BULLET -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_bulleted) + } + Focus.Type.QUOTE -> { + turnIntoToggle.setImageResource(R.drawable.ic_block_toolbar_turn_into_highlight) + } + } + + val color = if (turnIntoToggle.isSelected) + context.color(R.color.toolbar_block_turn_into_toggle_selected) + else + context.color(R.color.toolbar_block_turn_into_toggle_default) + + turnIntoToggle.setColorFilter(color) } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/OptionToolbarWidget.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/OptionToolbarWidget.kt index cf069a0187..bcc8c95dcd 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/OptionToolbarWidget.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/widgets/toolbar/OptionToolbarWidget.kt @@ -13,6 +13,8 @@ import com.agileburo.anytype.core_ui.R import com.agileburo.anytype.core_ui.common.ViewType import com.agileburo.anytype.core_ui.extensions.color import com.agileburo.anytype.core_ui.extensions.drawable +import com.agileburo.anytype.core_ui.extensions.invisible +import com.agileburo.anytype.core_ui.extensions.visible import com.agileburo.anytype.core_ui.layout.SpacingItemDecoration import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.* import com.agileburo.anytype.core_ui.widgets.toolbar.OptionToolbarWidget.OptionAdapter.Companion.LIST_TYPE @@ -51,6 +53,7 @@ import kotlinx.android.synthetic.main.widget_bottom_detail_toolbar.view.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.sendBlocking import kotlinx.coroutines.flow.consumeAsFlow +import kotlin.properties.Delegates /** * This toolbar widget provides user with different types of options @@ -63,6 +66,21 @@ import kotlinx.coroutines.flow.consumeAsFlow */ class OptionToolbarWidget : LinearLayout { + var state: State by Delegates.observable(State.INIT) { _, old, new -> + if (new != old) + when (new) { + State.TURN_INTO -> { + header.setText(R.string.option_toolbar_header_turn_into) + } + State.ADD_BLOCK -> { + header.setText(R.string.option_toolbar_header_add_block) + } + State.INIT -> { + header.text = "" + } + } + } + private val channel = Channel