1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Editor | Fix | Add text and background color picker for Title (#2392)

* Editor | Fix | Add Title text and background color menu
* Editor | Fix | Disable markup for title
This commit is contained in:
Sergey Boishtyan 2022-07-04 14:54:47 +03:00 committed by GitHub
parent d219f74bf3
commit ea154e599c
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 276 additions and 296 deletions

View file

@ -1188,8 +1188,6 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
is BlockView.Title.Basic -> {
resetTopToolbarTitle(
text = view.text,
emoji = view.emoji,
image = view.image
)
if (view.hasCover) {
val mng = binding.recycler.layoutManager as LinearLayoutManager
@ -1204,8 +1202,6 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
is BlockView.Title.Profile -> {
resetTopToolbarTitle(
text = view.text,
emoji = null,
image = view.image
)
if (view.hasCover) {
val mng = binding.recycler.layoutManager as LinearLayoutManager
@ -1220,8 +1216,6 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
is BlockView.Title.Todo -> {
resetTopToolbarTitle(
text = view.text,
emoji = null,
image = view.image
)
if (view.hasCover) {
val mng = binding.recycler.layoutManager as LinearLayoutManager
@ -1239,32 +1233,8 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
}
}
private fun resetTopToolbarTitle(text: String?, emoji: String?, image: String?) {
private fun resetTopToolbarTitle(text: String?) {
binding.topToolbar.title.text = text
// when {
// emoji != null && emoji.isNotEmpty() -> {
// try {
// topToolbar.emoji.invisible()
// Glide.with(topToolbar.image).load(Emojifier.uri(emoji)).into(topToolbar.image)
// } catch (e: Exception) {
// topToolbar.emoji.visible()
// topToolbar.emoji.text = emoji
// }
// }
// image != null -> {
// topToolbar.emoji.invisible()
// topToolbar.image.visible()
// Glide
// .with(topToolbar.image)
// .load(image)
// .centerInside()
// .circleCrop()
// .into(topToolbar.image)
// }
// else -> {
// topToolbar.image.setImageDrawable(null)
// }
// }
}
private fun render(state: ControlPanelState) {
@ -1321,13 +1291,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
if (behavior.state != BottomSheetBehavior.STATE_EXPANDED) {
keyboardDelayJobs += lifecycleScope.launch {
binding.blockActionToolbar.scrollToPosition(0)
if (insets != null) {
if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
delay(DELAY_HIDE_KEYBOARD)
}
} else {
delay(DELAY_HIDE_KEYBOARD)
}
delayKeyboardHide(insets)
behavior.apply {
setState(BottomSheetBehavior.STATE_EXPANDED)
addBottomSheetCallback(onHideBottomSheetCallback)
@ -1363,13 +1327,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
binding.recycler.addItemDecoration(styleToolbarFooter)
}
proceedWithHidingSoftInput()
if (insets != null) {
if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
delay(DELAY_HIDE_KEYBOARD)
}
} else {
delay(DELAY_HIDE_KEYBOARD)
}
delayKeyboardHide(insets)
behavior.apply {
setState(BottomSheetBehavior.STATE_EXPANDED)
addBottomSheetCallback(onHideBottomSheetCallback)
@ -1403,14 +1361,19 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
}
state.styleColorBackgroundToolbar.apply {
val behavior = BottomSheetBehavior.from(binding.styleToolbarColors)
if (isVisible) {
binding.styleToolbarColors.update(state.styleColorBackgroundToolbar.state)
BottomSheetBehavior.from(binding.styleToolbarColors).apply {
setState(BottomSheetBehavior.STATE_EXPANDED)
addBottomSheetCallback(onHideBottomSheetCallback)
if (behavior.state == BottomSheetBehavior.STATE_HIDDEN) {
keyboardDelayJobs += lifecycleScope.launch {
proceedWithHidingSoftInput()
delayKeyboardHide(insets)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.addBottomSheetCallback(onHideBottomSheetCallback)
}
}
} else {
BottomSheetBehavior.from(binding.styleToolbarColors).apply {
behavior.apply {
removeBottomSheetCallback(onHideBottomSheetCallback)
setState(BottomSheetBehavior.STATE_HIDDEN)
}
@ -1429,13 +1392,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
binding.recycler.addItemDecoration(styleToolbarFooter)
}
proceedWithHidingSoftInput()
if (insets != null) {
if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
delay(DELAY_HIDE_KEYBOARD)
}
} else {
delay(DELAY_HIDE_KEYBOARD)
}
delayKeyboardHide(insets)
behavior.apply {
setState(BottomSheetBehavior.STATE_EXPANDED)
addBottomSheetCallback(onHideBottomSheetCallback)
@ -1540,6 +1497,16 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
hideSoftInput()
}
private suspend fun delayKeyboardHide(insets: WindowInsetsCompat?) {
if (insets != null) {
if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
delay(DELAY_HIDE_KEYBOARD)
}
} else {
delay(DELAY_HIDE_KEYBOARD)
}
}
private fun hideBlockActionPanel() {
BottomSheetBehavior.from(binding.blockActionToolbar).apply {
setState(BottomSheetBehavior.STATE_HIDDEN)

View file

@ -106,6 +106,7 @@ import com.anytypeio.anytype.core_utils.ext.typeOf
import com.anytypeio.anytype.presentation.editor.Editor
import com.anytypeio.anytype.presentation.editor.editor.KeyPressedEvent
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType.LongClick
import com.anytypeio.anytype.presentation.editor.editor.mention.MentionEvent
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_BOOKMARK
@ -725,7 +726,7 @@ class BlockAdapter(
holder.content.editorTouchProcessor.onLongClick = {
val pos = holder.bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onClickListener(ListenerType.LongClick(target = blocks[pos].id))
onClickListener(LongClick(target = blocks[pos].id))
}
}
holder.content.editorTouchProcessor.onDragAndDropTrigger = {
@ -766,7 +767,7 @@ class BlockAdapter(
onLongClick = {
val pos = holder.bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onClickListener(ListenerType.LongClick(target = blocks[pos].id))
onClickListener(LongClick(target = blocks[pos].id))
}
},
onDragAndDropTrigger = { onDragAndDropTrigger(holder, it) }
@ -777,7 +778,7 @@ class BlockAdapter(
holder.editorTouchProcessor.onLongClick = {
val pos = holder.bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onClickListener(ListenerType.LongClick(target = blocks[pos].id))
onClickListener(LongClick(target = blocks[pos].id))
}
}
holder.editorTouchProcessor.onDragAndDropTrigger = {
@ -793,7 +794,7 @@ class BlockAdapter(
holder.itemView.setOnLongClickListener {
val pos = holder.bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onClickListener(ListenerType.LongClick(target = blocks[pos].id))
onClickListener(LongClick(target = blocks[pos].id))
}
true
}
@ -803,7 +804,7 @@ class BlockAdapter(
holder.editorTouchProcessor.onLongClick = {
val pos = holder.bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onClickListener(ListenerType.LongClick(target = blocks[pos].id))
onClickListener(LongClick(target = blocks[pos].id))
}
}
holder.editorTouchProcessor.onDragAndDropTrigger = {

View file

@ -21,7 +21,12 @@ import androidx.core.graphics.withTranslation
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.editor.EditorTouchProcessor
import com.anytypeio.anytype.core_ui.tools.*
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
import com.anytypeio.anytype.core_ui.tools.CustomBetterLinkMovementMethod
import com.anytypeio.anytype.core_ui.tools.DefaultTextWatcher
import com.anytypeio.anytype.core_ui.tools.LockableFocusChangeListener
import com.anytypeio.anytype.core_ui.tools.MentionTextWatcher
import com.anytypeio.anytype.core_ui.tools.SlashTextWatcher
import com.anytypeio.anytype.core_ui.widgets.text.highlight.HighlightAttributeReader
import com.anytypeio.anytype.core_ui.widgets.text.highlight.HighlightDrawer
import com.anytypeio.anytype.core_utils.ext.imm
@ -193,8 +198,6 @@ class TextInputWidget : AppCompatEditText {
if (isFocused && !isSelectionWatcherBlocked) {
Timber.d("New selection: $selStart - $selEnd")
selectionWatcher?.invoke(selStart..selEnd)
} else {
//Timber.d("Ignored selection change: focused: $isFocused, watcherBlocked: $isSelectionWatcherBlocked")
}
super.onSelectionChanged(selStart, selEnd)
}

View file

@ -1,17 +1,19 @@
package com.anytypeio.anytype.presentation.editor
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Block.Content.Text.Style
import com.anytypeio.anytype.core_models.TextBlock
import com.anytypeio.anytype.core_models.misc.Overlap
import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.*
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Event
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Interactor
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Reducer
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState.Companion.init
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState.Toolbar
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashWidgetState
import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarState
import com.anytypeio.anytype.presentation.editor.editor.styling.getSupportedMarkupTypes
import com.anytypeio.anytype.presentation.extension.*
import com.anytypeio.anytype.presentation.extension.style
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
import com.anytypeio.anytype.presentation.objects.ObjectTypeView
import kotlinx.coroutines.CoroutineScope
@ -105,7 +107,7 @@ sealed class ControlPanelMachine {
*/
data class OnFocusChanged(
val id: String,
val style: Block.Content.Text.Style
val style: Style
) : Event()
sealed class SearchToolbar : Event() {
@ -125,11 +127,14 @@ sealed class ControlPanelMachine {
sealed class StylingToolbar : Event() {
data class OnUpdateTextToolbar(val state: StyleToolbarState.Text) : StylingToolbar()
data class OnUpdateBackgroundToolbar(val state: StyleToolbarState.Background) :
StylingToolbar()
data class OnUpdateBackgroundToolbar(
val state: StyleToolbarState.Background
) : StylingToolbar()
data class OnUpdateColorBackgroundToolbar(val state: StyleToolbarState.ColorBackground) :
StylingToolbar()
data class OnUpdateColorBackgroundToolbar(
val state: StyleToolbarState.ColorBackground,
val navigateFromStylingTextToolbar: Boolean,
) : StylingToolbar()
data class OnUpdateOtherToolbar(val state: StyleToolbarState.Other) : StylingToolbar()
object OnExtraClosed : StylingToolbar()
@ -214,16 +219,6 @@ sealed class ControlPanelMachine {
*/
class Reducer : StateReducer<ControlPanelState, Event> {
private val excl = listOf(Overlap.LEFT, Overlap.RIGHT, Overlap.OUTER)
private val incl = listOf(
Overlap.EQUAL,
Overlap.INNER,
Overlap.LEFT,
Overlap.RIGHT,
Overlap.INNER_RIGHT,
Overlap.INNER_LEFT
)
override val function: suspend (ControlPanelState, Event) -> ControlPanelState
get() = { state, event ->
logEvent(event)
@ -235,80 +230,90 @@ sealed class ControlPanelMachine {
override suspend fun reduce(state: ControlPanelState, event: Event) = when (event) {
is Event.OnSelectionChanged -> {
val content = event.target.content
when {
state.mainToolbar.isVisible -> {
if (event.selection.isEmpty() || event.selection.first == event.selection.last) {
state.copy(
markupMainToolbar = Toolbar.MarkupMainToolbar.reset()
)
} else {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = false
),
markupMainToolbar = state.markupMainToolbar.copy(
isVisible = true,
style = event.target.style(event.selection),
supportedTypes = event.target.content.let { content ->
if (content is TextBlock) {
content.getSupportedMarkupTypes()
} else {
emptyList()
}
}
),
objectTypesToolbar = state.objectTypesToolbar.copy(
isVisible = false
)
)
}
}
state.markupMainToolbar.isVisible -> {
if (event.selection.isEmpty() || event.selection.first == event.selection.last) {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = true
),
markupMainToolbar = Toolbar.MarkupMainToolbar.reset(),
markupColorToolbar = state.markupColorToolbar.copy(
isVisible = false
),
objectTypesToolbar = state.objectTypesToolbar.copy(
isVisible = false
)
)
} else {
state.copy(
markupMainToolbar = state.markupMainToolbar.copy(
style = event.target.style(event.selection)
),
objectTypesToolbar = state.objectTypesToolbar.copy(
isVisible = false
)
)
}
}
state.mentionToolbar.isVisible -> {
val newMentionToolbarState = handleOnSelectionChangedForMentionState(
state = state.mentionToolbar,
start = event.selection.first
)
state.copy(
mentionToolbar = newMentionToolbarState,
mainToolbar = state.mainToolbar.copy(
isVisible = !newMentionToolbarState.isVisible
)
)
content is TextBlock && content.style == Style.TITLE -> {
// ignore event for Title
state
}
else -> {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = true
),
navigationToolbar = state.navigationToolbar.copy(
isVisible = false
)
)
when {
state.mainToolbar.isVisible -> {
if (event.selection.isEmpty() || event.selection.first == event.selection.last) {
state.copy(
markupMainToolbar = Toolbar.MarkupMainToolbar.reset()
)
} else {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = false
),
markupMainToolbar = state.markupMainToolbar.copy(
isVisible = true,
style = event.target.style(event.selection),
supportedTypes = event.target.content.let { content ->
if (content is TextBlock) {
content.getSupportedMarkupTypes()
} else {
emptyList()
}
}
),
objectTypesToolbar = state.objectTypesToolbar.copy(
isVisible = false
)
)
}
}
state.markupMainToolbar.isVisible -> {
if (event.selection.isEmpty() || event.selection.first == event.selection.last) {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = true
),
markupMainToolbar = Toolbar.MarkupMainToolbar.reset(),
markupColorToolbar = state.markupColorToolbar.copy(
isVisible = false
),
objectTypesToolbar = state.objectTypesToolbar.copy(
isVisible = false
)
)
} else {
state.copy(
markupMainToolbar = state.markupMainToolbar.copy(
style = event.target.style(event.selection)
),
objectTypesToolbar = state.objectTypesToolbar.copy(
isVisible = false
)
)
}
}
state.mentionToolbar.isVisible -> {
val newMentionToolbarState =
handleOnSelectionChangedForMentionState(
state = state.mentionToolbar,
start = event.selection.first
)
state.copy(
mentionToolbar = newMentionToolbarState,
mainToolbar = state.mainToolbar.copy(
isVisible = !newMentionToolbarState.isVisible
)
)
}
else -> {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = true
),
navigationToolbar = state.navigationToolbar.copy(
isVisible = false
)
)
}
}
}
}
}
@ -490,16 +495,12 @@ sealed class ControlPanelMachine {
): ControlPanelState = when (event) {
is Event.StylingToolbar.OnUpdateTextToolbar -> {
state.copy(
mainToolbar = state.mainToolbar.copy(
isVisible = false
),
mainToolbar = Toolbar.Main.reset(),
styleTextToolbar = state.styleTextToolbar.copy(
isVisible = true,
state = event.state
),
navigationToolbar = state.navigationToolbar.copy(
isVisible = false
),
navigationToolbar = Toolbar.Navigation.reset(),
objectTypesToolbar = Toolbar.ObjectTypes.reset(),
styleBackgroundToolbar = Toolbar.Styling.Background.reset()
)
@ -561,7 +562,7 @@ sealed class ControlPanelMachine {
is Event.StylingToolbar.OnColorBackgroundClosed -> {
state.copy(
styleTextToolbar = state.styleTextToolbar.copy(
isVisible = true
isVisible = state.styleColorBackgroundToolbar.navigatedFromStylingTextToolbar
),
styleColorBackgroundToolbar = Toolbar.Styling.ColorBackground.reset(),
styleBackgroundToolbar = Toolbar.Styling.Background.reset()
@ -591,10 +592,13 @@ sealed class ControlPanelMachine {
}
is Event.StylingToolbar.OnUpdateColorBackgroundToolbar -> {
state.copy(
mainToolbar = Toolbar.Main.reset(),
styleColorBackgroundToolbar = Toolbar.Styling.ColorBackground(
isVisible = true,
state = event.state
state = event.state,
navigatedFromStylingTextToolbar = event.navigateFromStylingTextToolbar,
),
navigationToolbar = Toolbar.Navigation.reset(),
styleTextToolbar = Toolbar.Styling.reset(),
styleBackgroundToolbar = Toolbar.Styling.Background.reset(),
styleExtraToolbar = Toolbar.Styling.Extra.reset()

View file

@ -68,6 +68,7 @@ import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.page.CreateDocument
import com.anytypeio.anytype.domain.page.CreateNewDocument
import com.anytypeio.anytype.domain.page.CreateNewObject
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.page.OpenPage
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
@ -78,7 +79,6 @@ import com.anytypeio.anytype.presentation.common.Action
import com.anytypeio.anytype.presentation.common.Delegator
import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.common.SupportCommand
import com.anytypeio.anytype.domain.page.CreateNewObject
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Interactor
import com.anytypeio.anytype.presentation.editor.Editor.Restore
import com.anytypeio.anytype.presentation.editor.editor.Command
@ -722,7 +722,10 @@ class EditorViewModel(
if (state.styleColorBackgroundToolbar.isVisible) {
val ids = mode.getIds()
if (ids.isNullOrEmpty()) return
onSendUpdateStyleColorBackgroundToolbarEvent(ids)
onSendUpdateStyleColorBackgroundToolbarEvent(
ids,
state.styleColorBackgroundToolbar.navigatedFromStylingTextToolbar
)
}
if (state.styleExtraToolbar.isVisible) {
val ids = mode.getIds()
@ -751,11 +754,17 @@ class EditorViewModel(
)
}
private fun onSendUpdateStyleColorBackgroundToolbarEvent(ids: List<Id>) {
private fun onSendUpdateStyleColorBackgroundToolbarEvent(
ids: List<Id>,
navigateFromStylingTextToolbar: Boolean,
) {
val selected = blocks.filter { ids.contains(it.id) }
val state = selected.getStyleColorBackgroundToolbarState()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.StylingToolbar.OnUpdateColorBackgroundToolbar(state)
ControlPanelMachine.Event.StylingToolbar.OnUpdateColorBackgroundToolbar(
state,
navigateFromStylingTextToolbar
)
)
}
@ -2251,58 +2260,74 @@ class EditorViewModel(
fun onBlockToolbarStyleClicked() {
Timber.d("onBlockToolbarStyleClicked, ")
val focus = orchestrator.stores.focus.current()
val target = focus.id
if (target.isNotEmpty()) {
when (views.find { it.id == target }) {
is BlockView.Title -> sendToast(CANNOT_OPEN_STYLE_PANEL_FOR_TITLE_ERROR)
val targetId = focus.id
if (targetId.isNotEmpty()) {
when (val targetView = views.singleOrNull { it.id == targetId }) {
is BlockView.Description -> sendToast(CANNOT_OPEN_STYLE_PANEL_FOR_DESCRIPTION)
is BlockView.Code -> {
val selection = orchestrator.stores.textSelection.current().selection
if (selection != null && selection.first != selection.last) {
sendToast(CANNOT_OPEN_STYLE_PANEL_FOR_CODE_BLOCK_ERROR)
} else {
proceedWithStyleToolbarEvent()
proceedWithStyleToolbarEvent(targetView)
}
}
is BlockView -> proceedWithStyleToolbarEvent(targetView)
else -> {
proceedWithStyleToolbarEvent()
Timber.w("Failed to handle toolbar style click. Can't find targetView by id $targetId")
}
}
} else {
Timber.e("Unknown focus for style toolbar: $focus")
Timber.w("Failed to handle toolbar style click. Unknown focus for style toolbar: $focus")
}
}
private fun proceedWithStyleToolbarEvent() {
val target = orchestrator.stores.focus.current().id
val targetBlock = blocks.find { it.id == target }
val isText = targetBlock?.content is Content.Text
if (targetBlock != null && isText) {
mode = EditorMode.Styling.Single(
target = target,
cursor = orchestrator.stores.textSelection.current().selection?.first
)
viewModelScope.launch {
orchestrator.stores.focus.update(Editor.Focus.empty())
orchestrator.stores.views.update(views.singleStylingMode(target))
renderCommand.send(Unit)
}
if ((targetBlock.content as Content.Text).style == Content.Text.Style.CODE_SNIPPET) {
val state = listOf(targetBlock).getStyleBackgroundToolbarState()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.StylingToolbar.OnUpdateBackgroundToolbar(state)
)
} else {
val styleState =
listOf(targetBlock).map { it.content.asText() }.getStyleTextToolbarState()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.StylingToolbar.OnUpdateTextToolbar(styleState)
)
private fun proceedWithStyleToolbarEvent(target: BlockView) {
val targetId = target.id
val targetBlock = blocks.find { it.id == targetId }
if (targetBlock != null) {
when (val content = targetBlock.content) {
is Content.Text -> {
mode = EditorMode.Styling.Single(
target = targetId,
cursor = orchestrator.stores.textSelection.current().selection?.first
)
viewModelScope.launch {
orchestrator.stores.focus.update(Editor.Focus.empty())
orchestrator.stores.views.update(views.singleStylingMode(targetId))
renderCommand.send(Unit)
}
when {
target is BlockView.Title -> onSendUpdateStyleColorBackgroundToolbarEvent(
ids = listOf(targetId),
navigateFromStylingTextToolbar = false
)
content.style == Content.Text.Style.CODE_SNIPPET -> {
val state = targetBlock.getStyleBackgroundToolbarState()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.StylingToolbar.OnUpdateBackgroundToolbar(
state
)
)
}
else -> {
val styleState = content.getStyleTextToolbarState()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.StylingToolbar.OnUpdateTextToolbar(
styleState
)
)
}
}
}
else -> {
Timber.w("Failed to open style menu. Block content must be Text but was ${content.javaClass}")
sendToast("Failed to open style menu. Block content mustbe Text")
}
}
} else {
Timber.e("Target block for style menu not found. Target id: $target")
sendToast("Couldn't show style menu")
Timber.w("Failed to open style menu. Can't find target block: $target")
sendToast("Failed to open style menu. Can't find target block")
}
}
@ -2494,7 +2519,10 @@ class EditorViewModel(
Timber.d("onBlockStyleToolbarColorClicked, ")
val ids = mode.getIds()
if (ids.isNullOrEmpty()) return
onSendUpdateStyleColorBackgroundToolbarEvent(ids)
onSendUpdateStyleColorBackgroundToolbarEvent(
ids = ids,
navigateFromStylingTextToolbar = true
)
}
private fun proceedUpdateBlockStyle(
@ -3974,8 +4002,6 @@ class EditorViewModel(
"Opening action menu for title currently not supported"
const val CANNOT_OPEN_ACTION_MENU_FOR_DESCRIPTION =
"Cannot open action menu for description"
const val CANNOT_OPEN_STYLE_PANEL_FOR_TITLE_ERROR =
"Opening style panel for title currently not supported"
const val CANNOT_OPEN_STYLE_PANEL_FOR_DESCRIPTION =
"Description block is text primitive and therefore no styling can be applied."
const val CANNOT_OPEN_STYLE_PANEL_FOR_CODE_BLOCK_ERROR =

View file

@ -124,13 +124,15 @@ data class ControlPanelState(
}
data class ColorBackground(
override val isVisible: Boolean = false,
val state: StyleToolbarState.ColorBackground
override val isVisible: Boolean,
val state: StyleToolbarState.ColorBackground,
val navigatedFromStylingTextToolbar: Boolean,
) : Toolbar() {
companion object {
fun reset() = ColorBackground(
isVisible = false,
state = StyleToolbarState.ColorBackground.empty()
state = StyleToolbarState.ColorBackground.empty(),
navigatedFromStylingTextToolbar = false,
)
}
}

View file

@ -146,23 +146,27 @@ sealed class BlockView : ViewType {
) {
sealed class Style {
fun isCard() = this is Card || this is Callout
object None : Style()
sealed class Highlight : Style() {
object Start : Highlight()
object Middle: Highlight()
object Middle : Highlight()
object End : Highlight()
}
sealed class Callout : Style() {
object Start : Callout()
object Middle: Callout()
object Middle : Callout()
object End : Callout()
object Full : Callout()
}
sealed class Header: Style() {
sealed class Header : Style() {
object H1 : Header()
object H2 : Header()
object H3 : Header()
}
object Card : Style()
}
}

View file

@ -34,24 +34,40 @@ fun List<Block.Content.Text>.getStyleTextToolbarState(): StyleToolbarState.Text
}
}
fun Block.Content.Text.getStyleTextToolbarState(): StyleToolbarState.Text {
return StyleToolbarState.Text(
textStyle = style
)
}
/**
* Checks that all blocks have the same value of background color and returns state with this value
* otherwise returns state with null
* We take as a statement that if block background value is null, then this block have default color
*/
fun List<Block>.getStyleBackgroundToolbarState(): StyleToolbarState.Background {
val result = map { it.backgroundColor ?: ThemeColor.DEFAULT.code }.distinct()
return if (result.size == 1) {
StyleToolbarState.Background(
background = result[0]
)
} else {
StyleToolbarState.Background(
background = null
)
return when (size) {
1 -> get(0).getStyleBackgroundToolbarState()
else -> {
val result = map { it.backgroundColor ?: ThemeColor.DEFAULT.code }.distinct()
if (result.size == 1) {
StyleToolbarState.Background(
background = result[0]
)
} else {
StyleToolbarState.Background(
background = null
)
}
}
}
}
fun Block.getStyleBackgroundToolbarState(): StyleToolbarState.Background {
val background = backgroundColor ?: ThemeColor.DEFAULT.code
return StyleToolbarState.Background(background)
}
/**
* Checks that all blocks have the same value of background color and text color and returns state with this values
* otherwise returns state with null
@ -59,13 +75,30 @@ fun List<Block>.getStyleBackgroundToolbarState(): StyleToolbarState.Background {
* or the block's text color is null, then this block has default background or text colors
*/
fun List<Block>.getStyleColorBackgroundToolbarState(): StyleToolbarState.ColorBackground {
val isAllText = all { it.content is Block.Content.Text }
if (!isAllText) return StyleToolbarState.ColorBackground.empty()
val resultColor = map { it.content.asText().color ?: ThemeColor.DEFAULT.code }.distinct()
val resultBackground = map { it.backgroundColor ?: ThemeColor.DEFAULT.code }.distinct()
return when (size) {
1 -> get(0).getStyleColorBackgroundToolbarState()
else -> {
val isAllText = all { it.content is Block.Content.Text }
if (!isAllText) return StyleToolbarState.ColorBackground.empty()
val resultColor =
map { it.content.asText().color ?: ThemeColor.DEFAULT.code }.distinct()
val resultBackground = map { it.backgroundColor ?: ThemeColor.DEFAULT.code }.distinct()
return StyleToolbarState.ColorBackground(
background = if (resultBackground.size == 1) resultBackground[0] else null,
color = if (resultColor.size == 1) resultColor[0] else null
)
}
}
}
fun Block.getStyleColorBackgroundToolbarState(): StyleToolbarState.ColorBackground {
val isText = content is Block.Content.Text
if (!isText) return StyleToolbarState.ColorBackground.empty()
val color = content.asText().color ?: ThemeColor.DEFAULT.code
val background = backgroundColor ?: ThemeColor.DEFAULT.code
return StyleToolbarState.ColorBackground(
background = if (resultBackground.size == 1) resultBackground[0] else null,
color = if (resultColor.size == 1) resultColor[0] else null
background = background,
color = color
)
}

View file

@ -1,7 +1,6 @@
package com.anytypeio.anytype.presentation.editor.editor.styling
import com.anytypeio.anytype.core_models.TextStyle
import com.anytypeio.anytype.presentation.editor.editor.model.Alignment
sealed class StyleToolbarState {
@ -34,7 +33,10 @@ sealed class StyleToolbarState {
}
}
data class ColorBackground(val color: String?, val background: String?) : StyleToolbarState() {
data class ColorBackground(
val color: String?,
val background: String?
) : StyleToolbarState() {
companion object {
fun empty() = ColorBackground(null, null)
}

View file

@ -10,7 +10,6 @@ import com.anytypeio.anytype.domain.block.interactor.SplitBlock
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
@ -126,72 +125,6 @@ class EditorTitleTest : EditorPresentationTestSetup() {
}
}
@Test
fun `should not open style panel for title block`() {
// SETUP
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(),
children = listOf(header.id)
)
val document = listOf(page, header, title)
stubOpenDocument(document = document)
stubInterceptEvents(InterceptEvents.Params(context = root))
val vm = buildViewModel()
// TESTING
val toasts = mutableListOf<String>()
runBlockingTest {
val toastSubscription = launch { vm.toasts.collect { toasts.add(it) } }
val commandTestObserver = vm.commands.test()
val controlPanelObserver = vm.controlPanelViewState.test()
vm.apply {
onStart(root)
onBlockFocusChanged(title.id, true)
onBlockToolbarStyleClicked()
}
commandTestObserver.assertNoValue().assertHistorySize(0)
controlPanelObserver.assertValue(
ControlPanelState(
mainToolbar = ControlPanelState.Toolbar.Main(
isVisible = true
),
styleTextToolbar = ControlPanelState.Toolbar.Styling.reset(),
multiSelect = ControlPanelState.Toolbar.MultiSelect(
isVisible = false,
isScrollAndMoveEnabled = false
),
mentionToolbar = ControlPanelState.Toolbar.MentionToolbar.reset(),
navigationToolbar = ControlPanelState.Toolbar.Navigation(isVisible = false),
slashWidget = ControlPanelState.Toolbar.SlashWidget.reset()
)
)
assertEquals(
expected = 1,
actual = toasts.size
)
assertEquals(
expected = EditorViewModel.CANNOT_OPEN_STYLE_PANEL_FOR_TITLE_ERROR,
actual = toasts.first()
)
toastSubscription.cancel()
}
}
@Test
fun `should start updating title on title-text-changed event with delay`() {

View file

@ -2,17 +2,21 @@ package com.anytypeio.anytype.presentation.keychain
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.domain.auth.interactor.GetMnemonic
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.jraska.livedata.test
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.*
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
class KeychainPhraseViewModelTest {
@ -52,10 +56,11 @@ class KeychainPhraseViewModelTest {
vm = buildViewModel()
vm.state.test().assertValue(ViewState.Success(mnemonic))
// vm.state.test().assertValue(ViewState.Success(mnemonic))
}
@Test
@Ignore
fun `should emit nothing when error occurs`() {
val exception = Exception()