mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
Editor | Feature | Simple Tables (#2427)
* Editor | Feature | Simple tables, prototype (#2325) * core model * table view model * view holders * table view holders layouts * table adapters * add table block to block adapter * map core table model to view * stub table block * fixes * fixed row hight * update middleware * fix * add createTable command to data and middleware modules * add createTable use case * add table row & table column layouts to block model * update create table use case * add table create to orchestrator * add create table item to slash menu * delete stubbing * update create table usecase * update proto * add create table use case to tests * fix use case * add stubs for table blocks * set default row, column size * create table use case di * set id to stub blocks * table view * map table block to view + test * layout types: table row, table column * table block model + mapping * remove legacy * rename table to table view holder * table holders + row adapter * tableviewholder setup * add block test to table view * table block layout * add table item to slash menu * test fix * api update * add restriction duplicate * render table block with empty and text cells * row view use diff cells * tests on mapping table block to views * table text cells + empty cells * table row adapter diff update * use paragraph block in table cells * fix tests * table rows mapping * table row, cell holders * table cell, row adapters * layouts update * code style * add fill table row use case * text + click listeners * listener type, table row empty cell * merge fixes * add fill table row intent to editor * set focus to cell text * remember focus on emty cell clicked * fix problem when horizontal layout has limited width * prevent crash on text cell focus * legacy * add table view library * table block listener * table block adapter * table block holders * table block layouts * integrate table blocl * fix tests * fix table view dep * use list of cells instead rows * update tests * code style * code style * clear text in cells when cell is null * legacy * legacy * revert local lib * fix lib version * pr fixes * pr fixes * pr fixes * pr fix * pr fixes Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple tables, part 2 (#2400) * move text input widget into parent layout * table cells adapter diff util + tests * cells diffutil should return payload model * set column header to 0 * table block update test * create diff util in table block adapter * do not show corner view * update listeners in holders * text cell layout update * update cell container to frame layout * on update empty to text cell, check mode * clear empty cells on bind * update diffUtil + tests * added column header item * set column header items * create and bind column header items * use recyler with grid layout * update table cell mapping + model * remove tableview lib from core-ui * add row_id + colum_id to cells * fix tests * import * update table cells diff util * update cell adapter + holders * update mw * add height, row height + width to cell model * fix test * delete test * table block design * set support touch helper for cell holder * update cells payload logic * table holder fixes * fix test * pr fixes Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple tables, cells as text views (#2411) * move text input widget into parent layout * table cells adapter diff util + tests * cells diffutil should return payload model * set column header to 0 * table block update test * create diff util in table block adapter * do not show corner view * update listeners in holders * text cell layout update * update cell container to frame layout * on update empty to text cell, check mode * clear empty cells on bind * update diffUtil + tests * added column header item * set column header items * create and bind column header items * use recyler with grid layout * update table cell mapping + model * remove tableview lib from core-ui * add row_id + colum_id to cells * fix tests * import * update table cells diff util * update cell adapter + holders * update mw * add height, row height + width to cell model * fix test * delete test * table block design * set support touch helper for cell holder * update cells payload logic * table holder fixes * fix test * pr fixes * text cell design * use grid as layout manager and add payloads for table block * added background and selection for table block * table block selection logic * update table cells diff util * click listeners in table cells + save table id * table cells listeners * refactoring table cell as text view * fix tests * fix merge conflict * pr fixes Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple tables, create from slash filter (#2415) * DROID-126 slash item simple table design * DROID-126 update slash item model * DROID-126 added max row and column number * DROID-126 create table with entered rows and columns * DROID-126 update table item with rows and columns * DROID-126 tests * DROID-126 pr fix Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple tables, edit cell text value (#2413) * DROID-172 set text value di * DROID-172 set block text fragment + view model * DROID-172 added input action logic for text blockls * DROID-172 input action listener fix * DROID-172 clicks + commands on set block text value screen * DROID-172 code style fix * DROID-172 pr fix * DROID-172 pr fix * DROID-172 fix * DROID-172 input action fixes Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple tables, cell/column/row menu, part 1 (#2417) * DROID-131 icons * DROID-131 simple table widget * DROID-131 simple table widget adapters * DROID-131 simpla table widget models + delegate * DROID-131 include widget in editor view model * DROID-131 add widget to editor fragment + view model logic * DROID-131 fix tests * DROID-131 pr fixes Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple tables, empty cell clicked (#2420) * DROID-114 empty cell clicked * DROID-114 doc * DROID-114 open cell value modal after fill table row command * DROID-114 pr fixes Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple Tables, cells selection state (#2425) * DROID-131 dismiss listener * DROID-131 add cell borders to diff util * DROID-131 apply borders to selected cell * DROID-131 update cell settings model + render * DROID-131 update cell listeners * DROID-131 apply and dismiss cells borders * DROID-131 apply and dismiss cell borders in view model * DROID-131 clicks * DROID-131 update tests * DROID-131 click params update * DROID-131 table click in mode select * DROID-131 naming * DROID-131 pr fix Co-authored-by: konstantiniiv <ki@anytype.io> * Editor | Feature | Simple Table, design (#2429) * DROID-180 fixes * DROID-180 table vertical divider * DROID-180 table horizontal divider * DROID-180 update table block holder * DROID-180 refactoring * DROID-180 divider fixes * DROID-180 add space cell * DROID-180 update table cell diff util * DROID-180 table block adapter + holders * DROID-180 table block design fix * DROID-180 add space item to table * DROID-180 fix * DROID-180 add space cell to tests * DROID-180 add offset to horizontal item divider * DROID-180 vertical divider update * DROID-180 add isHeader to table row * DROID-180 delete legacy * DROID-180 update cell background + isheader logic * DROID-180 fix * DROID-180 design fix * DROID-180 pr fix * DROID-180 back to list adapter Co-authored-by: konstantiniiv <ki@anytype.io> * ci * fix tests * ci off Co-authored-by: konstantiniiv <ki@anytype.io>
This commit is contained in:
parent
234de6d854
commit
743bb29730
91 changed files with 3964 additions and 45 deletions
|
@ -72,6 +72,8 @@ import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
|||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
|
||||
import com.anytypeio.anytype.domain.table.CreateTable
|
||||
import com.anytypeio.anytype.domain.table.FillTableRow
|
||||
import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage
|
||||
|
@ -86,6 +88,7 @@ import com.anytypeio.anytype.presentation.editor.editor.InternalDetailModificati
|
|||
import com.anytypeio.anytype.presentation.editor.editor.Orchestrator
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Proxy
|
||||
import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMatcher
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.editor.template.DefaultEditorTemplateDelegate
|
||||
|
@ -228,6 +231,15 @@ open class EditorTestSetup {
|
|||
@Mock
|
||||
lateinit var objectTypesProvider: ObjectTypesProvider
|
||||
|
||||
@Mock
|
||||
lateinit var createTable: CreateTable
|
||||
|
||||
@Mock
|
||||
lateinit var fillTableRow: FillTableRow
|
||||
|
||||
@Mock
|
||||
lateinit var simpleTableDelegate: SimpleTableDelegate
|
||||
|
||||
val root: String = "rootId123"
|
||||
|
||||
private val urlBuilder by lazy {
|
||||
|
@ -368,7 +380,9 @@ open class EditorTestSetup {
|
|||
turnIntoStyle = turnIntoStyle,
|
||||
updateBlocksMark = updateBlocksMark,
|
||||
setObjectType = setObjectType,
|
||||
createBookmarkBlock = createBookmarkBlock
|
||||
createBookmarkBlock = createBookmarkBlock,
|
||||
createTable = createTable,
|
||||
fillTableRow = fillTableRow
|
||||
),
|
||||
createNewDocument = createNewDocument,
|
||||
interceptThreadStatus = interceptThreadStatus,
|
||||
|
@ -388,7 +402,8 @@ open class EditorTestSetup {
|
|||
setDocCoverImage = setDocCoverImage,
|
||||
setDocImageIcon = setDocImageIcon,
|
||||
editorTemplateDelegate = editorTemplateDelegate,
|
||||
createNewObject = createNewObject
|
||||
createNewObject = createNewObject,
|
||||
simpleTablesDelegate = simpleTableDelegate
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -294,6 +294,13 @@ class ComponentManager(
|
|||
.build()
|
||||
}
|
||||
|
||||
val setTextBlockValueComponent = DependentComponentMap { ctx ->
|
||||
editorComponent
|
||||
.get(ctx)
|
||||
.setBlockTextValueComponent()
|
||||
.build()
|
||||
}
|
||||
|
||||
val createBookmarkSubComponent = Component {
|
||||
main
|
||||
.createBookmarkBuilder()
|
||||
|
|
|
@ -75,6 +75,8 @@ import com.anytypeio.anytype.domain.relations.AddFileToObject
|
|||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
|
||||
import com.anytypeio.anytype.domain.table.CreateTable
|
||||
import com.anytypeio.anytype.domain.table.FillTableRow
|
||||
import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage
|
||||
|
@ -90,6 +92,8 @@ import com.anytypeio.anytype.presentation.editor.editor.Interactor
|
|||
import com.anytypeio.anytype.presentation.editor.editor.InternalDetailModificationManager
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Orchestrator
|
||||
import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMatcher
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.DefaultSimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.editor.template.DefaultEditorTemplateDelegate
|
||||
|
@ -147,6 +151,8 @@ interface EditorSubComponent {
|
|||
fun objectAppearancePreviewLayoutComponent() : ObjectAppearancePreviewLayoutSubComponent.Builder
|
||||
fun objectAppearanceCoverComponent() : ObjectAppearanceCoverSubComponent.Builder
|
||||
fun objectAppearanceChooseDescription() : ObjectAppearanceChooseDescriptionSubComponent.Builder
|
||||
|
||||
fun setBlockTextValueComponent(): SetBlockTextValueSubComponent.Builder
|
||||
}
|
||||
|
||||
|
||||
|
@ -205,7 +211,8 @@ object EditorSessionModule {
|
|||
setDocCoverImage: SetDocCoverImage,
|
||||
setDocImageIcon: SetDocumentImageIcon,
|
||||
editorTemplateDelegate: EditorTemplateDelegate,
|
||||
createNewObject: CreateNewObject
|
||||
createNewObject: CreateNewObject,
|
||||
simpleTableDelegate: SimpleTableDelegate
|
||||
): EditorViewModelFactory = EditorViewModelFactory(
|
||||
openPage = openPage,
|
||||
closeObject = closePage,
|
||||
|
@ -237,7 +244,8 @@ object EditorSessionModule {
|
|||
setDocCoverImage = setDocCoverImage,
|
||||
setDocImageIcon = setDocImageIcon,
|
||||
editorTemplateDelegate = editorTemplateDelegate,
|
||||
createNewObject = createNewObject
|
||||
createNewObject = createNewObject,
|
||||
simpleTablesDelegate = simpleTableDelegate
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
@ -264,6 +272,12 @@ object EditorSessionModule {
|
|||
applyTemplate = applyTemplate
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideSimpleTableDelegate(
|
||||
) : SimpleTableDelegate = DefaultSimpleTableDelegate()
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun provideDefaultBlockViewRenderer(
|
||||
|
@ -326,6 +340,8 @@ object EditorSessionModule {
|
|||
setupBookmark: SetupBookmark,
|
||||
createBookmarkBlock: CreateBookmarkBlock,
|
||||
turnIntoDocument: TurnIntoDocument,
|
||||
createTable: CreateTable,
|
||||
fillTableRow: FillTableRow,
|
||||
setObjectType: SetObjectType,
|
||||
matcher: DefaultPatternMatcher,
|
||||
move: Move,
|
||||
|
@ -373,7 +389,9 @@ object EditorSessionModule {
|
|||
updateFields = updateFields,
|
||||
turnIntoStyle = turnInto,
|
||||
updateBlocksMark = updateBlocksMark,
|
||||
setObjectType = setObjectType
|
||||
setObjectType = setObjectType,
|
||||
createTable = createTable,
|
||||
fillTableRow = fillTableRow
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -751,6 +769,24 @@ object EditorUseCaseModule {
|
|||
repo: BlockRepository
|
||||
): SetLinkAppearance = SetLinkAppearance(repo)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideCreateTableUseCase(
|
||||
repo: BlockRepository
|
||||
): CreateTable = CreateTable(
|
||||
repo = repo
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideTableRowFill(
|
||||
repo: BlockRepository
|
||||
): FillTableRow = FillTableRow(
|
||||
repo = repo
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.anytypeio.anytype.di.feature
|
||||
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
|
||||
import com.anytypeio.anytype.domain.block.interactor.UpdateText
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.objects.block.SetBlockTextValueViewModel
|
||||
import com.anytypeio.anytype.ui.editor.modals.SetBlockTextValueFragment
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent(modules = [SetBlockTextValueModule::class])
|
||||
@PerDialog
|
||||
interface SetBlockTextValueSubComponent {
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
fun module(model: SetBlockTextValueModule): Builder
|
||||
fun build(): SetBlockTextValueSubComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: SetBlockTextValueFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object SetBlockTextValueModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerDialog
|
||||
fun provideViewModelFactory(
|
||||
updateText: UpdateText,
|
||||
storage: Editor.Storage
|
||||
): SetBlockTextValueViewModel.Factory =
|
||||
SetBlockTextValueViewModel.Factory(
|
||||
storage = storage,
|
||||
updateText = updateText
|
||||
)
|
||||
}
|
|
@ -109,6 +109,7 @@ import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelStat
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.editor.sam.ScrollAndMoveTarget
|
||||
import com.anytypeio.anytype.presentation.editor.editor.sam.ScrollAndMoveTargetDescriptor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetViewState
|
||||
import com.anytypeio.anytype.presentation.editor.markup.MarkupColorView
|
||||
import com.anytypeio.anytype.presentation.editor.model.EditorFooter
|
||||
import com.anytypeio.anytype.presentation.editor.template.SelectTemplateViewState
|
||||
|
@ -123,6 +124,7 @@ import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase
|
|||
import com.anytypeio.anytype.ui.editor.modals.SelectProgrammingLanguageFragment
|
||||
import com.anytypeio.anytype.ui.editor.modals.SelectProgrammingLanguageReceiver
|
||||
import com.anytypeio.anytype.ui.editor.modals.SetLinkFragment
|
||||
import com.anytypeio.anytype.ui.editor.modals.SetBlockTextValueFragment
|
||||
import com.anytypeio.anytype.ui.editor.modals.TextBlockIconPickerFragment
|
||||
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment
|
||||
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment.DocumentMenuActionReceiver
|
||||
|
@ -214,6 +216,9 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
|
|||
binding.typeHasTemplateToolbar.id -> {
|
||||
vm.onTypeHasTemplateToolbarHidden()
|
||||
}
|
||||
binding.simpleTableWidget.id -> {
|
||||
vm.onHideSimpleTableWidget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -472,6 +477,20 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
|
|||
}
|
||||
}
|
||||
}
|
||||
jobs += subscribe(vm.simpleTablesViewState) { state ->
|
||||
val behavior = BottomSheetBehavior.from(binding.simpleTableWidget)
|
||||
when (state) {
|
||||
is SimpleTableWidgetViewState.Active -> {
|
||||
binding.simpleTableWidget.onStateChanged(state = state.state)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.addBottomSheetCallback(onHideBottomSheetCallback)
|
||||
}
|
||||
SimpleTableWidgetViewState.Idle -> {
|
||||
behavior.removeBottomSheetCallback(onHideBottomSheetCallback)
|
||||
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vm.onStart(id = extractDocumentId())
|
||||
super.onStart()
|
||||
|
@ -1112,6 +1131,19 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
|
|||
val margin = resources.getDimensionPixelSize(R.dimen.default_editor_item_offset)
|
||||
lm.scrollToPositionWithOffset(command.pos, margin)
|
||||
}
|
||||
is Command.OpenSetBlockTextValueScreen -> {
|
||||
val fr = SetBlockTextValueFragment.new(
|
||||
ctx = command.ctx,
|
||||
block = command.block,
|
||||
table = command.table
|
||||
).apply {
|
||||
onDismissListener = {
|
||||
vm.onSetBlockTextValueScreenDismiss()
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
fr.show(childFragmentManager, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1881,6 +1913,14 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
|
|||
vm.onEnterSearchModeClicked()
|
||||
}
|
||||
|
||||
override fun onSetTextBlockValue() {
|
||||
vm.onSetTextBlockValue()
|
||||
}
|
||||
|
||||
override fun onMentionClicked(target: Id) {
|
||||
vm.onMentionClicked(target = target)
|
||||
}
|
||||
|
||||
override fun onUndoRedoClicked() {
|
||||
vm.onUndoRedoActionClicked()
|
||||
}
|
||||
|
@ -2073,4 +2113,6 @@ interface OnFragmentInteractionListener {
|
|||
fun onSetObjectLink(id: Id)
|
||||
fun onSetWebLink(uri: String)
|
||||
fun onCreateObject(name: String)
|
||||
fun onSetTextBlockValue()
|
||||
fun onMentionClicked(target: Id)
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package com.anytypeio.anytype.ui.editor.modals
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.DragEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.DragAndDropAdapterDelegate
|
||||
import com.anytypeio.anytype.core_ui.features.editor.marks
|
||||
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
|
||||
import com.anytypeio.anytype.core_utils.ext.argString
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.ext.withParent
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetImeOffsetFragment
|
||||
import com.anytypeio.anytype.databinding.FragmentSetBlockTextValueBinding
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.ext.extractMarks
|
||||
import com.anytypeio.anytype.presentation.objects.block.SetBlockTextValueViewModel
|
||||
import com.anytypeio.anytype.ui.editor.OnFragmentInteractionListener
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetBlockTextValueFragment :
|
||||
BaseBottomSheetImeOffsetFragment<FragmentSetBlockTextValueBinding>(), ClipboardInterceptor,
|
||||
View.OnDragListener {
|
||||
|
||||
private val vm: SetBlockTextValueViewModel by viewModels { factory }
|
||||
|
||||
var onDismissListener: (() -> Unit)? = null
|
||||
|
||||
@Inject
|
||||
lateinit var factory: SetBlockTextValueViewModel.Factory
|
||||
|
||||
private val blockAdapter by lazy {
|
||||
BlockAdapter(
|
||||
restore = LinkedList(),
|
||||
initialBlock = mutableListOf(),
|
||||
onTextChanged = { _, _ -> },
|
||||
onTitleBlockTextChanged = { _, _ -> },
|
||||
onSelectionChanged = { _, _ -> },
|
||||
onCheckboxClicked = {},
|
||||
onTitleCheckboxClicked = {},
|
||||
onFocusChanged = { _, _ -> },
|
||||
onSplitLineEnterClicked = { id, editable, _ ->
|
||||
vm.onKeyboardDoneKeyClicked(
|
||||
ctx = ctx,
|
||||
tableId = table,
|
||||
targetId = id,
|
||||
text = editable.toString(),
|
||||
marks = editable.marks(),
|
||||
markup = editable.extractMarks()
|
||||
)
|
||||
},
|
||||
onSplitDescription = { _, _, _ -> },
|
||||
onEmptyBlockBackspaceClicked = {},
|
||||
onNonEmptyBlockBackspaceClicked = { _, _ -> },
|
||||
onTextInputClicked = {},
|
||||
onPageIconClicked = {},
|
||||
onCoverClicked = {},
|
||||
onTogglePlaceholderClicked = {},
|
||||
onToggleClicked = {},
|
||||
onTitleTextInputClicked = {},
|
||||
onTextBlockTextChanged = {},
|
||||
onClickListener = vm::onClickListener,
|
||||
onMentionEvent = {},
|
||||
onSlashEvent = {},
|
||||
onBackPressedCallback = { false },
|
||||
onKeyPressedEvent = {},
|
||||
onDragAndDropTrigger = { _, _ -> false },
|
||||
lifecycle = lifecycle,
|
||||
dragAndDropSelector = DragAndDropAdapterDelegate(),
|
||||
clipboardInterceptor = this,
|
||||
onDragListener = this
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.recycler.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = blockAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
with(lifecycleScope) {
|
||||
jobs += subscribe(vm.state) { render(it) }
|
||||
jobs += subscribe(vm.toasts) { toast(it) }
|
||||
}
|
||||
vm.onStart(tableId = table, blockId = block)
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
vm.onStop()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
onDismissListener?.invoke()
|
||||
}
|
||||
|
||||
private fun render(state: SetBlockTextValueViewModel.ViewState) {
|
||||
when (state) {
|
||||
SetBlockTextValueViewModel.ViewState.Exit -> {
|
||||
withParent<OnFragmentInteractionListener> { onSetTextBlockValue() }
|
||||
dismiss()
|
||||
}
|
||||
SetBlockTextValueViewModel.ViewState.Loading -> {
|
||||
}
|
||||
is SetBlockTextValueViewModel.ViewState.Success -> {
|
||||
blockAdapter.updateWithDiffUtil(state.data)
|
||||
}
|
||||
is SetBlockTextValueViewModel.ViewState.OnMention -> {
|
||||
withParent<OnFragmentInteractionListener> { onMentionClicked(state.targetId) }
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().setTextBlockValueComponent.get(ctx).inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().setTextBlockValueComponent.release(ctx)
|
||||
}
|
||||
|
||||
override fun inflateBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
): FragmentSetBlockTextValueBinding {
|
||||
return FragmentSetBlockTextValueBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onClipboardAction(action: ClipboardInterceptor.Action) {}
|
||||
override fun onUrlPasted(url: Url) {}
|
||||
override fun onDrag(v: View?, event: DragEvent?) = false
|
||||
|
||||
private val ctx: String get() = argString(CTX_KEY)
|
||||
private val block: String get() = argString(BLOCK_ID_KEY)
|
||||
private val table: String get() = argString(TABLE_ID_KEY)
|
||||
|
||||
companion object {
|
||||
const val CTX_KEY = "arg.editor.block.text.value.ctx"
|
||||
const val TABLE_ID_KEY = "arg.editor.block.text.value.table.id"
|
||||
const val BLOCK_ID_KEY = "arg.editor.block.text.value.block.id"
|
||||
const val DEFAULT_IME_ACTION = EditorInfo.IME_ACTION_DONE
|
||||
|
||||
fun new(ctx: Id, table: Id, block: Id) = SetBlockTextValueFragment().apply {
|
||||
arguments = bundleOf(
|
||||
CTX_KEY to ctx,
|
||||
TABLE_ID_KEY to table,
|
||||
BLOCK_ID_KEY to block
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -279,6 +279,18 @@
|
|||
app:cardUseCompatPadding="true"
|
||||
app:layout_behavior="@string/bottom_sheet_behavior"/>
|
||||
|
||||
<com.anytypeio.anytype.core_ui.widgets.toolbar.table.SimpleTableSettingWidget
|
||||
android:id="@+id/simpleTableWidget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:behavior_hideable="true"
|
||||
app:behavior_skipCollapsed="true"
|
||||
app:cardBackgroundColor="@color/background_secondary"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="6dp"
|
||||
app:cardUseCompatPadding="true"
|
||||
app:layout_behavior="@string/bottom_sheet_behavior"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<View
|
||||
|
|
24
app/src/main/res/layout/fragment_set_block_text_value.xml
Normal file
24
app/src/main/res/layout/fragment_set_block_text_value.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sheet_top"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="6dp"
|
||||
android:src="@drawable/sheet_top" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_marginBottom="32dp" />
|
||||
|
||||
</LinearLayout>
|
|
@ -18,6 +18,7 @@ buildscript {
|
|||
ext.test_runner = 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
|
|
@ -343,6 +343,10 @@ data class Block(
|
|||
data class Latex(val latex: String) : Content()
|
||||
object TableOfContents : Content()
|
||||
object Unsupported : Content()
|
||||
|
||||
object Table: Content()
|
||||
data class TableRow(val isHeader: Boolean): Content()
|
||||
object TableColumn: Content()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,6 +45,7 @@ import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationFileBinding
|
|||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationObjectBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationPlaceholderBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockRelationTagBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTextBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTitleProfileBinding
|
||||
|
@ -63,6 +64,7 @@ import com.anytypeio.anytype.core_ui.features.editor.holders.error.PictureError
|
|||
import com.anytypeio.anytype.core_ui.features.editor.holders.error.VideoError
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.ext.setup
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.ext.setupPlaceholder
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.ext.toIMECode
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.media.Bookmark
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.media.File
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.media.Picture
|
||||
|
@ -85,6 +87,7 @@ import com.anytypeio.anytype.core_ui.features.editor.holders.placeholders.Pictur
|
|||
import com.anytypeio.anytype.core_ui.features.editor.holders.placeholders.VideoPlaceholder
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.relations.FeaturedRelationListViewHolder
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.relations.RelationViewHolder
|
||||
import com.anytypeio.anytype.core_ui.features.table.holders.TableBlockHolder
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Bulleted
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Callout
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Checkbox
|
||||
|
@ -151,6 +154,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_PLACEHOLDER
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_STATUS
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_TAGS
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TABLE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TITLE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TOC
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TODO_TITLE
|
||||
|
@ -459,7 +463,7 @@ class BlockAdapter(
|
|||
}
|
||||
}
|
||||
setOnEditorActionListener { v, actionId, _ ->
|
||||
if (actionId == TextInputWidget.TEXT_INPUT_WIDGET_ACTION_GO) {
|
||||
if (actionId == BlockView.InputAction.NewLine.toIMECode()) {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
onSplitDescription(
|
||||
|
@ -709,6 +713,10 @@ class BlockAdapter(
|
|||
HOLDER_UNSUPPORTED -> Unsupported(
|
||||
ItemBlockUnsupportedBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
HOLDER_TABLE -> TableBlockHolder(
|
||||
ItemBlockTableBinding.inflate(inflater, parent, false),
|
||||
clickListener = onClickListener
|
||||
)
|
||||
else -> throw IllegalStateException("Unexpected view type: $viewType")
|
||||
}
|
||||
|
||||
|
@ -1097,6 +1105,12 @@ class BlockAdapter(
|
|||
item = blocks[position] as BlockView.TableOfContents
|
||||
)
|
||||
}
|
||||
is TableBlockHolder -> {
|
||||
holder.processChangePayload(
|
||||
payloads = payloads.typeOf(),
|
||||
item = blocks[position] as BlockView.Table
|
||||
)
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected view holder: $holder")
|
||||
}
|
||||
checkIfDecorationChanged(holder, payloads.typeOf(), position)
|
||||
|
@ -1521,6 +1535,9 @@ class BlockAdapter(
|
|||
is Unsupported -> {
|
||||
holder.bind(item = blocks[position] as BlockView.Unsupported)
|
||||
}
|
||||
is TableBlockHolder -> {
|
||||
holder.bind(item = blocks[position] as BlockView.Table)
|
||||
}
|
||||
}
|
||||
|
||||
if (holder is Text) {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package com.anytypeio.anytype.core_ui.features.editor.holders.ext
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.view.updatePadding
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.editor.EditorTouchProcessor
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.relations.RelationViewHolder
|
||||
import com.anytypeio.anytype.core_ui.features.table.holders.TableBlockHolder
|
||||
import com.anytypeio.anytype.core_utils.ext.dimen
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
|
@ -56,4 +58,9 @@ fun RelationViewHolder.setupPlaceholder(adapter: BlockAdapter): RelationViewHold
|
|||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun BlockView.InputAction.toIMECode(): Int = when (this) {
|
||||
BlockView.InputAction.Done -> EditorInfo.IME_ACTION_DONE
|
||||
BlockView.InputAction.NewLine -> EditorInfo.IME_ACTION_GO
|
||||
}
|
|
@ -30,6 +30,7 @@ abstract class Text(
|
|||
) {
|
||||
indentize(item)
|
||||
select(item)
|
||||
inputAction(item)
|
||||
|
||||
if (item.mode == BlockView.Mode.READ) {
|
||||
enableReadMode()
|
||||
|
@ -143,5 +144,9 @@ abstract class Text(
|
|||
select(item)
|
||||
}
|
||||
|
||||
fun inputAction(item: BlockView.TextBlockProps) {
|
||||
content.setInputAction(item.inputAction)
|
||||
}
|
||||
|
||||
override fun getDefaultTextColor(): Int = defTextColor
|
||||
}
|
|
@ -4,6 +4,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemSlashWidgetStyleBinding
|
||||
import com.anytypeio.anytype.core_utils.ext.gone
|
||||
import com.anytypeio.anytype.core_utils.ext.visible
|
||||
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashItem
|
||||
|
||||
class OtherMenuHolder(
|
||||
|
@ -27,6 +28,23 @@ class OtherMenuHolder(
|
|||
ivIcon.setImageResource(R.drawable.ic_slash_toc)
|
||||
tvSubtitle.gone()
|
||||
}
|
||||
is SlashItem.Other.Table -> {
|
||||
val rowCount = item.rowCount
|
||||
val columnCount = item.columnCount
|
||||
if (rowCount != null && columnCount != null) {
|
||||
tvTitle.text = binding.root.resources.getString(
|
||||
R.string.slash_widgth_other_simple_table_rows_columns_count,
|
||||
rowCount,
|
||||
columnCount
|
||||
)
|
||||
tvSubtitle.visible()
|
||||
tvSubtitle.setText(R.string.slash_widget_other_simple_table_subtitle)
|
||||
} else {
|
||||
tvTitle.setText(R.string.slash_widget_other_simple_table)
|
||||
tvSubtitle.gone()
|
||||
}
|
||||
ivIcon.setImageResource(R.drawable.ic_slash_simple_tables)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package com.anytypeio.anytype.core_ui.features.table
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableRowItemBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableSpaceBinding
|
||||
import com.anytypeio.anytype.core_ui.features.table.holders.TableCellHolder
|
||||
import com.anytypeio.anytype.core_utils.ext.typeOf
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
|
||||
class TableBlockAdapter(
|
||||
differ: TableCellsDiffUtil,
|
||||
private val clickListener: (ListenerType) -> Unit
|
||||
) : ListAdapter<BlockView.Table.Cell, TableCellHolder>(differ) {
|
||||
|
||||
private var tableBlockId = ""
|
||||
|
||||
fun setTableBlockId(id: Id) {
|
||||
tableBlockId = id
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TableCellHolder {
|
||||
when (viewType) {
|
||||
TYPE_CELL -> {
|
||||
val binding = ItemBlockTableRowItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return TableCellHolder.TableTextCellHolder(
|
||||
context = parent.context,
|
||||
binding = binding
|
||||
).apply {
|
||||
textContent.setOnClickListener {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
onCellClicked(getItem(pos))
|
||||
}
|
||||
}
|
||||
editorTouchProcessor.onLongClick = {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
clickListener(ListenerType.LongClick(tableBlockId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TYPE_SPACE -> {
|
||||
val binding = ItemBlockTableSpaceBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return TableCellHolder.TableSpaceHolder(binding)
|
||||
}
|
||||
else -> throw UnsupportedOperationException("wrong viewtype:$viewType")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCellClicked(item: BlockView.Table.Cell) {
|
||||
when (item) {
|
||||
is BlockView.Table.Cell.Empty -> clickListener(
|
||||
ListenerType.TableEmptyCell(
|
||||
cellId = item.getId(),
|
||||
rowId = item.rowId,
|
||||
tableId = tableBlockId
|
||||
)
|
||||
)
|
||||
is BlockView.Table.Cell.Text ->
|
||||
clickListener(
|
||||
ListenerType.TableTextCell(
|
||||
tableId = tableBlockId,
|
||||
cellId = item.block.id
|
||||
)
|
||||
)
|
||||
BlockView.Table.Cell.Space -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TableCellHolder, position: Int) {
|
||||
if (holder is TableCellHolder.TableTextCellHolder) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: TableCellHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
if (payloads.isEmpty()) {
|
||||
onBindViewHolder(holder, position)
|
||||
} else {
|
||||
if (holder is TableCellHolder.TableTextCellHolder) {
|
||||
holder.processChangePayload(
|
||||
payloads = payloads.typeOf<TableCellsDiffUtil.Payload>().first(),
|
||||
cell = getItem(position)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = when (getItem(position)) {
|
||||
is BlockView.Table.Cell.Empty -> TYPE_CELL
|
||||
is BlockView.Table.Cell.Text -> TYPE_CELL
|
||||
BlockView.Table.Cell.Space -> TYPE_SPACE
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE_CELL = 1
|
||||
const val TYPE_SPACE = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package com.anytypeio.anytype.core_ui.features.table
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
|
||||
object TableCellsDiffUtil : DiffUtil.ItemCallback<BlockView.Table.Cell>() {
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: BlockView.Table.Cell,
|
||||
newItem: BlockView.Table.Cell
|
||||
): Boolean {
|
||||
if (oldItem is BlockView.Table.Cell.Empty && newItem is BlockView.Table.Cell.Empty) {
|
||||
return oldItem.rowId == newItem.rowId && oldItem.columnId == newItem.columnId
|
||||
}
|
||||
if (oldItem is BlockView.Table.Cell.Empty && newItem is BlockView.Table.Cell.Text) {
|
||||
return oldItem.rowId == newItem.rowId && oldItem.columnId == newItem.columnId
|
||||
}
|
||||
if (oldItem is BlockView.Table.Cell.Text && newItem is BlockView.Table.Cell.Text) {
|
||||
return oldItem.rowId == newItem.rowId && oldItem.columnId == newItem.columnId
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: BlockView.Table.Cell,
|
||||
newItem: BlockView.Table.Cell
|
||||
): Boolean {
|
||||
if (oldItem is BlockView.Table.Cell.Empty && newItem is BlockView.Table.Cell.Empty) {
|
||||
return oldItem.settings == newItem.settings
|
||||
}
|
||||
if (oldItem is BlockView.Table.Cell.Empty && newItem is BlockView.Table.Cell.Text) {
|
||||
return false
|
||||
}
|
||||
if (oldItem is BlockView.Table.Cell.Text && newItem is BlockView.Table.Cell.Text) {
|
||||
return oldItem.block == newItem.block && oldItem.settings == newItem.settings
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getChangePayload(
|
||||
oldItem: BlockView.Table.Cell,
|
||||
newItem: BlockView.Table.Cell
|
||||
): Any? {
|
||||
val changes = mutableListOf<Int>()
|
||||
var oldSettings: BlockView.Table.CellSettings? = null
|
||||
var newSettings: BlockView.Table.CellSettings? = null
|
||||
|
||||
if (oldItem is BlockView.Table.Cell.Empty && newItem is BlockView.Table.Cell.Empty) {
|
||||
oldSettings = oldItem.settings
|
||||
newSettings = newItem.settings
|
||||
}
|
||||
|
||||
if (oldItem is BlockView.Table.Cell.Empty && newItem is BlockView.Table.Cell.Text) {
|
||||
oldSettings = oldItem.settings
|
||||
newSettings = newItem.settings
|
||||
}
|
||||
|
||||
if (oldItem is BlockView.Table.Cell.Text && newItem is BlockView.Table.Cell.Text) {
|
||||
val oldBlock = oldItem.block
|
||||
val newBlock = newItem.block
|
||||
oldSettings = oldItem.settings
|
||||
newSettings = newItem.settings
|
||||
if (newBlock.text != oldBlock.text) {
|
||||
changes.add(TEXT_CHANGED)
|
||||
}
|
||||
if (newBlock.color != oldBlock.color) {
|
||||
changes.add(TEXT_COLOR_CHANGED)
|
||||
}
|
||||
if (newBlock.backgroundColor != oldBlock.backgroundColor) {
|
||||
changes.add(BACKGROUND_COLOR_CHANGED)
|
||||
}
|
||||
if (newBlock.marks != oldBlock.marks) {
|
||||
changes.add(MARKUP_CHANGED)
|
||||
}
|
||||
if (newBlock.alignment != oldBlock.alignment) {
|
||||
changes.add(ALIGN_CHANGED)
|
||||
}
|
||||
}
|
||||
|
||||
if (oldSettings != null && newSettings != null) {
|
||||
if (oldSettings.width != newSettings.width) {
|
||||
changes.add(SETTING_WIDTH_CHANGED)
|
||||
}
|
||||
if (oldSettings.left != newSettings.left
|
||||
|| oldSettings.top != newSettings.top
|
||||
|| oldSettings.right != newSettings.right
|
||||
|| oldSettings.bottom != newSettings.bottom
|
||||
) {
|
||||
changes.add(SETTING_BORDER_CHANGED)
|
||||
}
|
||||
if (oldSettings.isHeader != newSettings.isHeader) {
|
||||
changes.add(BACKGROUND_COLOR_CHANGED)
|
||||
}
|
||||
}
|
||||
|
||||
return if (changes.isEmpty()) {
|
||||
super.getChangePayload(oldItem, newItem)
|
||||
} else {
|
||||
Payload(changes)
|
||||
}
|
||||
}
|
||||
|
||||
data class Payload(
|
||||
val changes: List<Int>
|
||||
) {
|
||||
val isBordersChanged: Boolean = changes.contains(SETTING_BORDER_CHANGED)
|
||||
val isWidthChanged: Boolean = changes.contains(SETTING_WIDTH_CHANGED)
|
||||
val isTextChanged = changes.contains(TEXT_CHANGED)
|
||||
val isBackgroundChanged = changes.contains(BACKGROUND_COLOR_CHANGED)
|
||||
val isTextColorChanged = changes.contains(TEXT_COLOR_CHANGED)
|
||||
val isMarkupChanged = changes.contains(MARKUP_CHANGED)
|
||||
val isAlignChanged = changes.contains(ALIGN_CHANGED)
|
||||
}
|
||||
|
||||
const val TEXT_CHANGED = 1
|
||||
const val TEXT_COLOR_CHANGED = 2
|
||||
const val BACKGROUND_COLOR_CHANGED = 3
|
||||
const val MARKUP_CHANGED = 4
|
||||
const val ALIGN_CHANGED = 5
|
||||
const val SETTING_WIDTH_CHANGED = 6
|
||||
const val SETTING_BORDER_CHANGED = 7
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.anytypeio.anytype.core_ui.features.table.holders
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableBinding
|
||||
import com.anytypeio.anytype.core_ui.extensions.drawable
|
||||
import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
|
||||
import com.anytypeio.anytype.core_ui.features.table.TableBlockAdapter
|
||||
import com.anytypeio.anytype.core_ui.features.table.TableCellsDiffUtil
|
||||
import com.anytypeio.anytype.core_ui.layout.TableHorizontalItemDivider
|
||||
import com.anytypeio.anytype.core_ui.layout.TableVerticalItemDivider
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
|
||||
class TableBlockHolder(
|
||||
binding: ItemBlockTableBinding,
|
||||
clickListener: (ListenerType) -> Unit
|
||||
) : BlockViewHolder(binding.root) {
|
||||
|
||||
val root: FrameLayout = binding.root
|
||||
val recycler: RecyclerView = binding.recyclerTable
|
||||
private val selected = binding.selected
|
||||
|
||||
private val tableAdapter = TableBlockAdapter(
|
||||
differ = TableCellsDiffUtil,
|
||||
clickListener = clickListener
|
||||
)
|
||||
private val lm = GridLayoutManager(itemView.context, 1, GridLayoutManager.HORIZONTAL, false)
|
||||
|
||||
private val mSpanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
return when (recycler.adapter?.getItemViewType(position)) {
|
||||
TableBlockAdapter.TYPE_CELL -> 1
|
||||
else -> lm.spanCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val drawable = itemView.context.drawable(R.drawable.divider_dv_grid)
|
||||
val verticalDecorator = TableVerticalItemDivider(drawable)
|
||||
val horizontalDecorator = TableHorizontalItemDivider(drawable)
|
||||
|
||||
recycler.apply {
|
||||
layoutManager = lm
|
||||
lm.spanSizeLookup = mSpanSizeLookup
|
||||
adapter = tableAdapter
|
||||
addItemDecoration(verticalDecorator)
|
||||
addItemDecoration(horizontalDecorator)
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(item: BlockView.Table) {
|
||||
selected.isSelected = item.isSelected
|
||||
lm.spanCount = item.rowCount
|
||||
tableAdapter.setTableBlockId(item.id)
|
||||
tableAdapter.submitList(item.cells)
|
||||
}
|
||||
|
||||
fun processChangePayload(
|
||||
payloads: List<BlockViewDiffUtil.Payload>,
|
||||
item: BlockView.Table
|
||||
) {
|
||||
payloads.forEach { payload ->
|
||||
if (payload.changes.contains(BlockViewDiffUtil.SELECTION_CHANGED)) {
|
||||
selected.isSelected = item.isSelected
|
||||
}
|
||||
if (payload.changes.contains(BlockViewDiffUtil.BACKGROUND_COLOR_CHANGED)) {
|
||||
applyBackground(item.backgroundColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyBackground(background: String?) {
|
||||
root.setBlockBackgroundColor(background)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
package com.anytypeio.anytype.core_ui.features.table.holders
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.toSpannable
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableRowItemBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockTableSpaceBinding
|
||||
import com.anytypeio.anytype.core_ui.extensions.resolveThemedTextColor
|
||||
import com.anytypeio.anytype.core_ui.extensions.veryLight
|
||||
import com.anytypeio.anytype.core_ui.features.editor.EditorTouchProcessor
|
||||
import com.anytypeio.anytype.core_ui.features.editor.SupportCustomTouchProcessor
|
||||
import com.anytypeio.anytype.core_ui.features.table.TableCellsDiffUtil
|
||||
import com.anytypeio.anytype.core_utils.ext.invisible
|
||||
import com.anytypeio.anytype.core_utils.ext.visible
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Markup
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.Alignment
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
|
||||
|
||||
sealed class TableCellHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
class TableTextCellHolder(context: Context, binding: ItemBlockTableRowItemBinding) :
|
||||
TableCellHolder(binding.root), SupportCustomTouchProcessor {
|
||||
|
||||
val root: View = binding.root
|
||||
val textContent: AppCompatTextView = binding.textContent
|
||||
val selection: View = binding.selection
|
||||
|
||||
private val defTextColor: Int = itemView.resources.getColor(R.color.text_primary, null)
|
||||
private val mentionIconSize =
|
||||
itemView.resources.getDimensionPixelSize(R.dimen.mention_span_image_size_default)
|
||||
private val mentionIconPadding =
|
||||
itemView.resources.getDimensionPixelSize(R.dimen.mention_span_image_padding_default)
|
||||
private val mentionCheckedIcon =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_task_0_text_16)
|
||||
private val mentionUncheckedIcon =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_task_0_text_16)
|
||||
private val mentionInitialsSize =
|
||||
itemView.resources.getDimension(R.dimen.mention_span_initials_size_default)
|
||||
|
||||
override val editorTouchProcessor = EditorTouchProcessor(
|
||||
fallback = { e -> itemView.onTouchEvent(e) })
|
||||
|
||||
init {
|
||||
textContent.setOnTouchListener { v, e -> editorTouchProcessor.process(v, e) }
|
||||
}
|
||||
|
||||
fun bind(
|
||||
cell: BlockView.Table.Cell
|
||||
) {
|
||||
when (cell) {
|
||||
is BlockView.Table.Cell.Empty -> {
|
||||
setBorders(cell.settings)
|
||||
textContent.text = null
|
||||
setBackground(null, cell.settings)
|
||||
}
|
||||
is BlockView.Table.Cell.Text -> {
|
||||
setBorders(cell.settings)
|
||||
setBlockText(
|
||||
text = cell.block.text,
|
||||
markup = cell.block,
|
||||
color = cell.block.color
|
||||
)
|
||||
setTextColor(cell.block.color)
|
||||
setBackground(cell.block.backgroundColor, cell.settings)
|
||||
setAlignment(cell.block.alignment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun processChangePayload(
|
||||
payloads: TableCellsDiffUtil.Payload,
|
||||
cell: BlockView.Table.Cell
|
||||
) {
|
||||
if (payloads.isBordersChanged) {
|
||||
when (cell) {
|
||||
is BlockView.Table.Cell.Empty -> setBorders(cell.settings)
|
||||
is BlockView.Table.Cell.Text -> setBorders(cell.settings)
|
||||
BlockView.Table.Cell.Space -> {}
|
||||
}
|
||||
}
|
||||
if (cell is BlockView.Table.Cell.Text) {
|
||||
processChangePayload(payloads, cell.block, cell.settings)
|
||||
}
|
||||
}
|
||||
|
||||
fun processChangePayload(
|
||||
payloads: TableCellsDiffUtil.Payload,
|
||||
block: BlockView.Text.Paragraph,
|
||||
settings: BlockView.Table.CellSettings
|
||||
) {
|
||||
if (payloads.isTextChanged) {
|
||||
setBlockText(
|
||||
text = block.text,
|
||||
markup = block,
|
||||
color = block.color
|
||||
)
|
||||
}
|
||||
if (payloads.isTextColorChanged) {
|
||||
setTextColor(block.color)
|
||||
}
|
||||
if (payloads.isBackgroundChanged) {
|
||||
setBackground(block.backgroundColor, settings)
|
||||
}
|
||||
if (payloads.isMarkupChanged) {
|
||||
setBlockSpannableText(block, resolveTextBlockThemedColor(block.color))
|
||||
}
|
||||
if (payloads.isAlignChanged) {
|
||||
setAlignment(block.alignment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setBlockText(
|
||||
text: String,
|
||||
markup: Markup,
|
||||
color: String?
|
||||
) {
|
||||
when (markup.marks.isEmpty()) {
|
||||
true -> textContent.text = text
|
||||
false -> setBlockSpannableText(markup, resolveTextBlockThemedColor(color))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setBlockSpannableText(
|
||||
markup: Markup,
|
||||
color: Int
|
||||
) {
|
||||
when (markup.marks.any { it is Markup.Mark.Mention || it is Markup.Mark.Object }) {
|
||||
true -> setSpannableWithMention(markup, color)
|
||||
false -> setSpannable(markup, color)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSpannable(markup: Markup, textColor: Int) {
|
||||
textContent.setText(
|
||||
markup.toSpannable(
|
||||
textColor = textColor,
|
||||
context = itemView.context
|
||||
),
|
||||
TextView.BufferType.SPANNABLE
|
||||
)
|
||||
}
|
||||
|
||||
private fun setSpannableWithMention(
|
||||
markup: Markup,
|
||||
textColor: Int
|
||||
) {
|
||||
textContent.setText(
|
||||
markup.toSpannable(
|
||||
textColor = textColor,
|
||||
context = itemView.context,
|
||||
mentionImageSize = mentionIconSize,
|
||||
mentionImagePadding = mentionIconPadding,
|
||||
mentionCheckedIcon = mentionCheckedIcon,
|
||||
mentionUncheckedIcon = mentionUncheckedIcon,
|
||||
mentionInitialsSize = mentionInitialsSize
|
||||
),
|
||||
TextView.BufferType.SPANNABLE
|
||||
)
|
||||
}
|
||||
|
||||
private fun setAlignment(alignment: Alignment?) {
|
||||
if (alignment != null) {
|
||||
textContent.gravity = when (alignment) {
|
||||
Alignment.START -> Gravity.START
|
||||
Alignment.CENTER -> Gravity.CENTER
|
||||
Alignment.END -> Gravity.END
|
||||
}
|
||||
} else {
|
||||
textContent.gravity = Gravity.START
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTextColor(color: String?) {
|
||||
textContent.setTextColor(resolveTextBlockThemedColor(color))
|
||||
}
|
||||
|
||||
|
||||
private fun setBackground(background: String?, settings: BlockView.Table.CellSettings) {
|
||||
setTableCellBackgroundColor(background, root, settings.isHeader)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [color] color code, @see [ThemeColor]
|
||||
*/
|
||||
private fun setTableCellBackgroundColor(color: String?, view: View, isHeader: Boolean) {
|
||||
if (!color.isNullOrEmpty()) {
|
||||
val value = ThemeColor.values().find { value -> value.code == color }
|
||||
if (value != null && value != ThemeColor.DEFAULT) {
|
||||
view.setBackgroundColor(view.resources.veryLight(value, 0))
|
||||
} else {
|
||||
setTableCellHeaderOrEmptyBackground(view, isHeader)
|
||||
}
|
||||
} else {
|
||||
setTableCellHeaderOrEmptyBackground(view, isHeader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTableCellHeaderOrEmptyBackground(view: View, isHeader: Boolean) {
|
||||
if (isHeader) {
|
||||
root.setBackgroundColor(
|
||||
itemView.resources.getColor(
|
||||
R.color.table_row_header_background,
|
||||
null
|
||||
)
|
||||
)
|
||||
} else {
|
||||
view.background = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveTextBlockThemedColor(color: String?): Int {
|
||||
return itemView.context.resolveThemedTextColor(color, defTextColor)
|
||||
}
|
||||
|
||||
private fun setBorders(settings: BlockView.Table.CellSettings) {
|
||||
if (settings.isAllBordersApply()) {
|
||||
selection.visible()
|
||||
} else {
|
||||
selection.invisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TableSpaceHolder(binding: ItemBlockTableSpaceBinding) : TableCellHolder(binding.root)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.anytypeio.anytype.core_ui.layout
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class TableHorizontalItemDivider(
|
||||
private val drawable: Drawable
|
||||
) : RecyclerView.ItemDecoration() {
|
||||
|
||||
override fun onDraw(
|
||||
canvas: Canvas,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
canvas.save()
|
||||
val top = 0
|
||||
val bottom = parent.height
|
||||
val childCount = parent.childCount
|
||||
val itemCount = parent.adapter?.itemCount ?: 0
|
||||
val rect = Rect()
|
||||
for (i in 0 until childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
val position = parent.getChildLayoutPosition(child)
|
||||
parent.layoutManager?.getDecoratedBoundsWithMargins(child, rect)
|
||||
var right = rect.right + child.translationX.roundToInt()
|
||||
var left = right - drawable.intrinsicWidth
|
||||
|
||||
if (position < itemCount - 1) {
|
||||
drawable.setBounds(left, top, right, bottom)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
|
||||
if (position == 0) {
|
||||
right = child.left
|
||||
left = right - drawable.intrinsicWidth
|
||||
|
||||
drawable.setBounds(left, top, right, bottom)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
val position = parent.getChildLayoutPosition(view)
|
||||
outRect.right = drawable.intrinsicWidth
|
||||
if (position == 0) {
|
||||
outRect.left = drawable.intrinsicWidth
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.anytypeio.anytype.core_ui.layout
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class TableVerticalItemDivider(
|
||||
private val drawable: Drawable
|
||||
) : RecyclerView.ItemDecoration() {
|
||||
|
||||
override fun onDraw(
|
||||
canvas: Canvas,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
canvas.save()
|
||||
val spanCount = (parent.layoutManager as GridLayoutManager).spanCount
|
||||
val childCount = parent.childCount
|
||||
val itemCount = parent.adapter?.itemCount ?: 0
|
||||
val rect = Rect()
|
||||
for (i in 0 until childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
val position = parent.getChildLayoutPosition(child)
|
||||
parent.getDecoratedBoundsWithMargins(child, rect)
|
||||
var bottom = rect.bottom + child.translationY.roundToInt()
|
||||
var top = bottom - drawable.intrinsicHeight
|
||||
if (position < itemCount - 1) {
|
||||
drawable.setBounds(rect.left, top, rect.right, bottom)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
|
||||
if (position.rem(spanCount) == 0 && position < itemCount - 1) {
|
||||
bottom = child.top
|
||||
top = bottom - drawable.intrinsicHeight
|
||||
drawable.setBounds(rect.left, top, rect.right, bottom)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
val spanCount = (parent.layoutManager as GridLayoutManager).spanCount
|
||||
val itemCount = parent.adapter?.itemCount ?: 0
|
||||
val position = parent.getChildLayoutPosition(view)
|
||||
outRect.bottom = drawable.intrinsicHeight
|
||||
if (position.rem(spanCount) == 0 && position < itemCount - 1) {
|
||||
outRect.top = drawable.intrinsicHeight
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ 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.features.editor.holders.ext.toIMECode
|
||||
import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor
|
||||
import com.anytypeio.anytype.core_ui.tools.CustomBetterLinkMovementMethod
|
||||
import com.anytypeio.anytype.core_ui.tools.DefaultTextWatcher
|
||||
|
@ -31,6 +32,7 @@ import com.anytypeio.anytype.core_ui.widgets.text.highlight.HighlightAttributeRe
|
|||
import com.anytypeio.anytype.core_ui.widgets.text.highlight.HighlightDrawer
|
||||
import com.anytypeio.anytype.core_utils.ext.imm
|
||||
import com.anytypeio.anytype.core_utils.ext.multilineIme
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import timber.log.Timber
|
||||
|
||||
class TextInputWidget : AppCompatEditText {
|
||||
|
@ -42,7 +44,8 @@ class TextInputWidget : AppCompatEditText {
|
|||
setOnLongClickListener { view -> view != null && !view.hasFocus() }
|
||||
context.obtainStyledAttributes(attrs, R.styleable.TextInputWidget).apply {
|
||||
ignoreDragAndDrop = getBoolean(R.styleable.TextInputWidget_ignoreDragAndDrop, false)
|
||||
pasteAsPlainTextOnly = getBoolean(R.styleable.TextInputWidget_onlyPasteAsPlaneText, false)
|
||||
pasteAsPlainTextOnly =
|
||||
getBoolean(R.styleable.TextInputWidget_onlyPasteAsPlaneText, false)
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
@ -85,12 +88,20 @@ class TextInputWidget : AppCompatEditText {
|
|||
|
||||
private var isSelectionWatcherBlocked = false
|
||||
|
||||
private var inputAction: BlockView.InputAction = DEFAULT_INPUT_WIDGET_ACTION
|
||||
|
||||
fun setInputAction(action: BlockView.InputAction) {
|
||||
if (inputAction != action) {
|
||||
inputAction = action
|
||||
}
|
||||
}
|
||||
|
||||
private fun setup() {
|
||||
enableEditMode()
|
||||
}
|
||||
|
||||
fun enableEditMode() {
|
||||
multilineIme(action = TEXT_INPUT_WIDGET_ACTION_GO)
|
||||
multilineIme(action = inputAction.toIMECode())
|
||||
setTextIsSelectable(true)
|
||||
}
|
||||
|
||||
|
@ -307,7 +318,7 @@ class TextInputWidget : AppCompatEditText {
|
|||
onEnterClicked: (IntRange) -> Unit
|
||||
) {
|
||||
setOnEditorActionListener { v, actionId, _ ->
|
||||
if (actionId == TEXT_INPUT_WIDGET_ACTION_GO) {
|
||||
if (actionId == inputAction.toIMECode()) {
|
||||
onEnterClicked.invoke(v.selectionStart..v.selectionEnd)
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
|
@ -336,6 +347,6 @@ class TextInputWidget : AppCompatEditText {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val TEXT_INPUT_WIDGET_ACTION_GO = EditorInfo.IME_ACTION_GO
|
||||
val DEFAULT_INPUT_WIDGET_ACTION = BlockView.InputAction.NewLine
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package com.anytypeio.anytype.core_ui.widgets.toolbar.table
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemSimpleTableWidgetRecentBinding
|
||||
|
||||
class SimpleTableSettingAdapter(
|
||||
private val cellAdapter: SimpleTableWidgetAdapter,
|
||||
private val columnAdapter: SimpleTableWidgetAdapter,
|
||||
private val rowAdapter: SimpleTableWidgetAdapter
|
||||
) : RecyclerView.Adapter<SimpleTableSettingAdapter.VH>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = ItemSimpleTableWidgetRecentBinding.inflate(inflater, parent, false)
|
||||
return when (viewType) {
|
||||
TYPE_CELL -> {
|
||||
VH.Cell(binding).apply {
|
||||
binding.recyclerCell.apply {
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
setHasFixedSize(true)
|
||||
adapter = cellAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
TYPE_COLUMN -> {
|
||||
VH.Column(binding).apply {
|
||||
binding.recyclerCell.apply {
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
setHasFixedSize(true)
|
||||
adapter = columnAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
TYPE_ROW -> {
|
||||
VH.Row(binding).apply {
|
||||
binding.recyclerCell.apply {
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
setHasFixedSize(true)
|
||||
adapter = rowAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected view type: $viewType")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {}
|
||||
override fun getItemCount(): Int = DEFAULT_TABS_COUNT
|
||||
|
||||
override fun getItemViewType(position: Int): Int = when (position) {
|
||||
0 -> TYPE_CELL
|
||||
1 -> TYPE_COLUMN
|
||||
2 -> TYPE_ROW
|
||||
else -> throw IllegalStateException("Unexpected position: $position")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_TABS_COUNT = 3
|
||||
const val TYPE_CELL = 1
|
||||
const val TYPE_COLUMN = 2
|
||||
const val TYPE_ROW = 3
|
||||
}
|
||||
|
||||
sealed class VH(view: View) : RecyclerView.ViewHolder(view) {
|
||||
class Cell(val binding: ItemSimpleTableWidgetRecentBinding) : VH(binding.root)
|
||||
class Column(val binding: ItemSimpleTableWidgetRecentBinding) : VH(binding.root)
|
||||
class Row(val binding: ItemSimpleTableWidgetRecentBinding) : VH(binding.root)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.anytypeio.anytype.core_ui.widgets.toolbar.table
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.cardview.widget.CardView
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.databinding.WidgetSimpleTableBinding
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetState
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
|
||||
class SimpleTableSettingWidget @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : CardView(context, attrs, defStyleAttr) {
|
||||
|
||||
val binding = WidgetSimpleTableBinding.inflate(
|
||||
LayoutInflater.from(context), this, true
|
||||
)
|
||||
|
||||
private val cellAdapter = SimpleTableWidgetAdapter(items = listOf(),
|
||||
onClick = { item -> })
|
||||
private val columnAdapter = SimpleTableWidgetAdapter(items = listOf(),
|
||||
onClick = { item -> })
|
||||
private val rowAdapter = SimpleTableWidgetAdapter(items = listOf(),
|
||||
onClick = { item -> })
|
||||
|
||||
private val pagerAdapter = SimpleTableSettingAdapter(
|
||||
cellAdapter = cellAdapter,
|
||||
columnAdapter = columnAdapter,
|
||||
rowAdapter = rowAdapter
|
||||
)
|
||||
|
||||
fun onStateChanged(state: SimpleTableWidgetState) {
|
||||
when (state) {
|
||||
is SimpleTableWidgetState.UpdateItems -> {
|
||||
cellAdapter.update(state.cellItems)
|
||||
columnAdapter.update(state.columnItems)
|
||||
rowAdapter.update(state.rowItems)
|
||||
}
|
||||
SimpleTableWidgetState.Idle -> {}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
binding.viewpager.adapter = pagerAdapter
|
||||
binding.viewpager.isUserInputEnabled = false
|
||||
TabLayoutMediator(binding.tabsLayout, binding.viewpager) { tab, position ->
|
||||
tab.text = when (position) {
|
||||
0 -> context.getString(R.string.simple_tables_widget_tab_cell)
|
||||
1 -> context.getString(R.string.simple_tables_widget_tab_column)
|
||||
2 -> context.getString(R.string.simple_tables_widget_tab_row)
|
||||
else -> throw IllegalStateException("Unexpected position: $position")
|
||||
}
|
||||
}.attach()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package com.anytypeio.anytype.core_ui.widgets.toolbar.table
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemSimpleTableActionBinding
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem
|
||||
|
||||
class SimpleTableWidgetAdapter(
|
||||
private var items: List<SimpleTableWidgetItem>,
|
||||
private val onClick: (SimpleTableWidgetItem) -> Unit
|
||||
) :
|
||||
RecyclerView.Adapter<SimpleTableWidgetAdapter.VH>() {
|
||||
|
||||
fun update(items: List<SimpleTableWidgetItem>) {
|
||||
this.items = items
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val holder = VH(
|
||||
binding = ItemSimpleTableActionBinding.inflate(inflater, parent, false)
|
||||
).apply {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION)
|
||||
onClick(items[pos])
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
class VH(binding: ItemSimpleTableActionBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
val icon = binding.icon
|
||||
val title = binding.title
|
||||
|
||||
fun bind(item: SimpleTableWidgetItem) {
|
||||
when (item) {
|
||||
SimpleTableWidgetItem.Cell.ClearContents,
|
||||
SimpleTableWidgetItem.Row.ClearContents,
|
||||
SimpleTableWidgetItem.Column.ClearContents -> {
|
||||
title.setText(R.string.simple_tables_widget_item_clear_contents)
|
||||
icon.setImageResource(R.drawable.ic_slash_actions_clean_style)
|
||||
}
|
||||
SimpleTableWidgetItem.Cell.ClearStyle -> {
|
||||
title.setText(R.string.simple_tables_widget_item_clear_style)
|
||||
icon.setImageResource(R.drawable.ic_slash_actions_clean_style)
|
||||
}
|
||||
SimpleTableWidgetItem.Cell.Color,
|
||||
SimpleTableWidgetItem.Column.Color,
|
||||
SimpleTableWidgetItem.Row.Color -> {
|
||||
title.setText(R.string.simple_tables_widget_item_clear_color)
|
||||
icon.setImageResource(R.drawable.ic_style_toolbar_color)
|
||||
}
|
||||
SimpleTableWidgetItem.Cell.Style,
|
||||
SimpleTableWidgetItem.Row.Style,
|
||||
SimpleTableWidgetItem.Column.Style -> {
|
||||
title.setText(R.string.simple_tables_widget_item_clear_style)
|
||||
icon.setImageResource(R.drawable.ic_block_toolbar_block_style)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.Delete,
|
||||
SimpleTableWidgetItem.Row.Delete -> {
|
||||
title.setText(R.string.toolbar_action_delete)
|
||||
icon.setImageResource(R.drawable.ic_block_action_delete)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.Duplicate,
|
||||
SimpleTableWidgetItem.Row.Duplicate -> {
|
||||
title.setText(R.string.toolbar_action_duplicate)
|
||||
icon.setImageResource(R.drawable.ic_block_action_duplicate)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.InsertLeft -> {
|
||||
title.setText(R.string.simple_tables_widget_item_insert_left)
|
||||
icon.setImageResource(R.drawable.ic_column_insert_left)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.InsertRight -> {
|
||||
title.setText(R.string.simple_tables_widget_item_insert_right)
|
||||
icon.setImageResource(R.drawable.ic_column_insert_right)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.MoveLeft -> {
|
||||
title.setText(R.string.simple_tables_widget_item_move_left)
|
||||
icon.setImageResource(R.drawable.ic_move_column_left)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.MoveRight -> {
|
||||
title.setText(R.string.simple_tables_widget_item_move_right)
|
||||
icon.setImageResource(R.drawable.ic_move_column_right)
|
||||
}
|
||||
SimpleTableWidgetItem.Column.Sort,
|
||||
SimpleTableWidgetItem.Row.Sort -> {
|
||||
title.setText(R.string.sort)
|
||||
icon.setImageResource(R.drawable.ic_action_sort)
|
||||
}
|
||||
SimpleTableWidgetItem.Row.InsertAbove -> {
|
||||
title.setText(R.string.simple_tables_widget_item_insert_above)
|
||||
icon.setImageResource(R.drawable.ic_add_row_above)
|
||||
}
|
||||
SimpleTableWidgetItem.Row.InsertBelow -> {
|
||||
title.setText(R.string.simple_tables_widget_item_insert_below)
|
||||
icon.setImageResource(R.drawable.ic_add_row_below)
|
||||
}
|
||||
SimpleTableWidgetItem.Row.MoveDown -> {
|
||||
title.setText(R.string.simple_tables_widget_item_move_down)
|
||||
icon.setImageResource(R.drawable.ic_move_row_down)
|
||||
}
|
||||
SimpleTableWidgetItem.Row.MoveUp -> {
|
||||
title.setText(R.string.simple_tables_widget_item_move_up)
|
||||
icon.setImageResource(R.drawable.ic_move_row_up)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
core-ui/src/main/res/drawable/bg_editor_table_cell.xml
Normal file
4
core-ui/src/main/res/drawable/bg_editor_table_cell.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
</shape>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="@dimen/dp_2"
|
||||
android:color="@color/amber_80" />
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_editor_table_cell_selected" android:state_selected="true" />
|
||||
<item android:drawable="@drawable/bg_editor_table_cell" android:state_selected="false" />
|
||||
</selector>
|
10
core-ui/src/main/res/drawable/bg_table_cell_board_all.xml
Normal file
10
core-ui/src/main/res/drawable/bg_table_cell_board_all.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/amber_80" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
12
core-ui/src/main/res/drawable/ic_action_sort.xml
Normal file
12
core-ui/src/main/res/drawable/ic_action_sort.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M4.72,11.22C4.428,11.512 4.428,11.987 4.72,12.28C5.013,12.573 5.488,12.573 5.781,12.28L9.251,8.811V24.75H10.751V8.811L14.22,12.28C14.513,12.573 14.988,12.573 15.281,12.28C15.574,11.987 15.574,11.512 15.281,11.22L10.001,5.939L4.72,11.22Z"
|
||||
android:fillColor="@color/glyph_active"/>
|
||||
<path
|
||||
android:pathData="M21.251,23.189V7.25H22.751V23.188L26.219,19.72C26.512,19.427 26.987,19.427 27.28,19.72C27.573,20.013 27.573,20.487 27.28,20.78L22,26.06L16.721,20.78C16.428,20.487 16.428,20.013 16.721,19.72C17.013,19.427 17.488,19.427 17.781,19.72L21.251,23.189Z"
|
||||
android:fillColor="@color/glyph_active"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_add_row_above.xml
Normal file
10
core-ui/src/main/res/drawable/ic_add_row_above.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M16.75,27.25C16.75,27.664 16.414,28 16,28C15.586,28 15.25,27.664 15.25,27.25L15.25,24.25L12.25,24.25C11.836,24.25 11.5,23.914 11.5,23.5C11.5,23.086 11.836,22.75 12.25,22.75L15.25,22.75L15.25,19.75C15.25,19.336 15.586,19 16,19C16.414,19 16.75,19.336 16.75,19.75L16.75,22.75L19.75,22.75C20.164,22.75 20.5,23.086 20.5,23.5C20.5,23.914 20.164,24.25 19.75,24.25L16.75,24.25L16.75,27.25ZM24,15.5L8,15.5C7.172,15.5 6.5,14.828 6.5,14L6.5,8C6.5,7.172 7.172,6.5 8,6.5L24,6.5C24.828,6.5 25.5,7.172 25.5,8L25.5,14C25.5,14.828 24.828,15.5 24,15.5ZM27,14C27,15.657 25.657,17 24,17L8,17C6.343,17 5,15.657 5,14L5,8C5,6.343 6.343,5 8,5L24,5C25.657,5 27,6.343 27,8L27,14Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_add_row_below.xml
Normal file
10
core-ui/src/main/res/drawable/ic_add_row_below.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M24,25.5L8,25.5C7.172,25.5 6.5,24.828 6.5,24L6.5,18C6.5,17.172 7.172,16.5 8,16.5L24,16.5C24.828,16.5 25.5,17.172 25.5,18L25.5,24C25.5,24.828 24.828,25.5 24,25.5ZM27,24C27,25.657 25.657,27 24,27L8,27C6.343,27 5,25.657 5,24L5,18C5,16.343 6.343,15 8,15L24,15C25.657,15 27,16.343 27,18L27,24ZM16.75,12.25C16.75,12.664 16.414,13 16,13C15.586,13 15.25,12.664 15.25,12.25L15.25,9.25L12.25,9.25C11.836,9.25 11.5,8.914 11.5,8.5C11.5,8.086 11.836,7.75 12.25,7.75L15.25,7.75L15.25,4.75C15.25,4.336 15.586,4 16,4C16.414,4 16.75,4.336 16.75,4.75L16.75,7.75L19.75,7.75C20.164,7.75 20.5,8.086 20.5,8.5C20.5,8.914 20.164,9.25 19.75,9.25L16.75,9.25L16.75,12.25Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
15
core-ui/src/main/res/drawable/ic_cell_menu.xml
Normal file
15
core-ui/src/main/res/drawable/ic_cell_menu.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="15dp"
|
||||
android:height="17dp"
|
||||
android:viewportWidth="15"
|
||||
android:viewportHeight="17">
|
||||
<path
|
||||
android:pathData="M7.5,2.5L7.5,2.5A1,1 0,0 1,8.5 3.5L8.5,3.5A1,1 0,0 1,7.5 4.5L7.5,4.5A1,1 0,0 1,6.5 3.5L6.5,3.5A1,1 0,0 1,7.5 2.5z"
|
||||
android:fillColor="@color/gray"/>
|
||||
<path
|
||||
android:pathData="M7.5,7.5L7.5,7.5A1,1 0,0 1,8.5 8.5L8.5,8.5A1,1 0,0 1,7.5 9.5L7.5,9.5A1,1 0,0 1,6.5 8.5L6.5,8.5A1,1 0,0 1,7.5 7.5z"
|
||||
android:fillColor="@color/gray"/>
|
||||
<path
|
||||
android:pathData="M7.5,12.5L7.5,12.5A1,1 0,0 1,8.5 13.5L8.5,13.5A1,1 0,0 1,7.5 14.5L7.5,14.5A1,1 0,0 1,6.5 13.5L6.5,13.5A1,1 0,0 1,7.5 12.5z"
|
||||
android:fillColor="@color/gray"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_column_insert_left.xml
Normal file
10
core-ui/src/main/res/drawable/ic_column_insert_left.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M6.5,24L6.5,8C6.5,7.172 7.172,6.5 8,6.5L14,6.5C14.828,6.5 15.5,7.172 15.5,8L15.5,24C15.5,24.828 14.828,25.5 14,25.5L8,25.5C7.172,25.5 6.5,24.828 6.5,24ZM8,27C6.343,27 5,25.657 5,24L5,8C5,6.343 6.343,5 8,5L14,5C15.657,5 17,6.343 17,8L17,24C17,25.657 15.657,27 14,27L8,27ZM19.75,16.75C19.336,16.75 19,16.414 19,16C19,15.586 19.336,15.25 19.75,15.25L22.75,15.25L22.75,12.25C22.75,11.836 23.086,11.5 23.5,11.5C23.914,11.5 24.25,11.836 24.25,12.25L24.25,15.25L27.25,15.25C27.664,15.25 28,15.586 28,16C28,16.414 27.664,16.75 27.25,16.75L24.25,16.75L24.25,19.75C24.25,20.164 23.914,20.5 23.5,20.5C23.086,20.5 22.75,20.164 22.75,19.75L22.75,16.75L19.75,16.75Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_column_insert_right.xml
Normal file
10
core-ui/src/main/res/drawable/ic_column_insert_right.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M3.75,16.75C3.336,16.75 3,16.414 3,16C3,15.586 3.336,15.25 3.75,15.25L6.75,15.25L6.75,12.25C6.75,11.836 7.086,11.5 7.5,11.5C7.914,11.5 8.25,11.836 8.25,12.25L8.25,15.25L11.25,15.25C11.664,15.25 12,15.586 12,16C12,16.414 11.664,16.75 11.25,16.75L8.25,16.75L8.25,19.75C8.25,20.164 7.914,20.5 7.5,20.5C7.086,20.5 6.75,20.164 6.75,19.75L6.75,16.75L3.75,16.75ZM17,25.5C16.172,25.5 15.5,24.828 15.5,24L15.5,8C15.5,7.172 16.172,6.5 17,6.5L23,6.5C23.828,6.5 24.5,7.172 24.5,8L24.5,24C24.5,24.828 23.828,25.5 23,25.5L17,25.5ZM17,27C15.343,27 14,25.657 14,24L14,8C14,6.343 15.343,5 17,5L23,5C24.657,5 26,6.343 26,8L26,24C26,25.657 24.657,27 23,27L17,27Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_move_column_left.xml
Normal file
10
core-ui/src/main/res/drawable/ic_move_column_left.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M8.53,12.53C8.823,12.237 8.823,11.763 8.53,11.47C8.237,11.177 7.763,11.177 7.47,11.47L3.47,15.47L2.939,16L3.47,16.53L7.47,20.53C7.763,20.823 8.237,20.823 8.53,20.53C8.823,20.237 8.823,19.763 8.53,19.47L5.811,16.75L13,16.75L13,15.25L5.811,15.25L8.53,12.53ZM16.5,24L16.5,8C16.5,7.172 17.172,6.5 18,6.5L24,6.5C24.828,6.5 25.5,7.172 25.5,8L25.5,24C25.5,24.828 24.828,25.5 24,25.5L18,25.5C17.172,25.5 16.5,24.828 16.5,24ZM18,27C16.343,27 15,25.657 15,24L15,8C15,6.343 16.343,5 18,5L24,5C25.657,5 27,6.343 27,8L27,24C27,25.657 25.657,27 24,27L18,27Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_move_column_right.xml
Normal file
10
core-ui/src/main/res/drawable/ic_move_column_right.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M6.5,24L6.5,8C6.5,7.172 7.172,6.5 8,6.5L14,6.5C14.828,6.5 15.5,7.172 15.5,8L15.5,24C15.5,24.828 14.828,25.5 14,25.5L8,25.5C7.172,25.5 6.5,24.828 6.5,24ZM8,27C6.343,27 5,25.657 5,24L5,8C5,6.343 6.343,5 8,5L14,5C15.657,5 17,6.343 17,8L17,24C17,25.657 15.657,27 14,27L8,27ZM23.47,19.47C23.177,19.763 23.177,20.237 23.47,20.53C23.763,20.823 24.237,20.823 24.53,20.53L28.53,16.53L29.061,16L28.53,15.47L24.53,11.47C24.237,11.177 23.763,11.177 23.47,11.47C23.177,11.763 23.177,12.237 23.47,12.53L26.189,15.25L19,15.25L19,16.75L26.189,16.75L23.47,19.47Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_move_row_down.xml
Normal file
10
core-ui/src/main/res/drawable/ic_move_row_down.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M12.53,22.97C12.237,22.677 11.763,22.677 11.47,22.97C11.177,23.262 11.177,23.737 11.47,24.03L15.47,28.03L16,28.56L16.53,28.03L20.53,24.03C20.823,23.737 20.823,23.262 20.53,22.97C20.237,22.677 19.763,22.677 19.47,22.97L16.75,25.689L16.75,19L15.25,19L15.25,25.689L12.53,22.97ZM24,15.5L8,15.5C7.172,15.5 6.5,14.828 6.5,14L6.5,8C6.5,7.171 7.172,6.5 8,6.5L24,6.5C24.828,6.5 25.5,7.171 25.5,8L25.5,14C25.5,14.828 24.828,15.5 24,15.5ZM27,14C27,15.657 25.657,17 24,17L8,17C6.343,17 5,15.657 5,14L5,8C5,6.343 6.343,5 8,5L24,5C25.657,5 27,6.343 27,8L27,14Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_move_row_up.xml
Normal file
10
core-ui/src/main/res/drawable/ic_move_row_up.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M19.47,9.091C19.763,9.384 20.237,9.384 20.53,9.091C20.823,8.798 20.823,8.323 20.53,8.03L16.53,4.03L16,3.5L15.47,4.03L11.47,8.03C11.177,8.323 11.177,8.798 11.47,9.091C11.763,9.384 12.237,9.384 12.53,9.091L15.25,6.371V13.061H16.75V6.371L19.47,9.091ZM8,16.561H24C24.828,16.561 25.5,17.232 25.5,18.061V24.061C25.5,24.889 24.828,25.561 24,25.561H8C7.172,25.561 6.5,24.889 6.5,24.061V18.061C6.5,17.232 7.172,16.561 8,16.561ZM5,18.061C5,16.404 6.343,15.061 8,15.061H24C25.657,15.061 27,16.404 27,18.061V24.061C27,25.718 25.657,27.061 24,27.061H8C6.343,27.061 5,25.718 5,24.061V18.061Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
10
core-ui/src/main/res/drawable/ic_slash_simple_tables.xml
Normal file
10
core-ui/src/main/res/drawable/ic_slash_simple_tables.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M2,3.5C1.448,3.5 1,3.948 1,4.5V8.5H8V3.5H2ZM9,8.5H15V3.5H9V8.5ZM23,8.5H16V3.5H22C22.552,3.5 23,3.948 23,4.5V8.5ZM24,9V8.5V4.5C24,3.395 23.104,2.5 22,2.5H16H15.5H15H9H8.5H8H2C0.895,2.5 0,3.395 0,4.5V8.5V9V9.5V14.5V15V15.5V19.5C0,20.604 0.895,21.5 2,21.5H8H8.5H9H15H15.5H16H22C23.104,21.5 24,20.604 24,19.5V15.5V15V14.5V9.5V9ZM9,20.5H15V15.5H9V20.5ZM9,14.5H15V9.5H9V14.5ZM8,20.5V15.5H1V19.5C1,20.052 1.448,20.5 2,20.5H8ZM1,14.5H8V9.5H1V14.5ZM16,15.5H23V19.5C23,20.052 22.552,20.5 22,20.5H16V15.5ZM23,9.5V14.5H16V9.5H23Z"
|
||||
android:fillColor="@color/glyph_active"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
29
core-ui/src/main/res/layout/item_block_table.xml
Normal file
29
core-ui/src/main/res/layout/item_block_table.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
style="@style/DefaultTableBlockRootStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/container"
|
||||
style="@style/DefaultTableBlockContainerStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerTable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<View
|
||||
android:id="@+id/selected"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/default_document_item_padding_end"
|
||||
android:background="@drawable/item_block_multi_select_mode_selector"
|
||||
tools:background="@drawable/item_block_multi_select_selected" />
|
||||
</FrameLayout>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/shape_primary"/>
|
25
core-ui/src/main/res/layout/item_block_table_row_item.xml
Normal file
25
core-ui/src/main/res/layout/item_block_table_row_item.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="@dimen/item_block_table_cell_width"
|
||||
android:layout_height="@dimen/item_block_table_cell_height">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/textContent"
|
||||
style="@style/BlockCellTextContentStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
tools:text="@string/default_text_placeholder" />
|
||||
|
||||
<View
|
||||
android:id="@+id/selection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_table_cell_board_all"
|
||||
android:visibility="invisible" />
|
||||
</FrameLayout>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="match_parent"
|
||||
android:minHeight="42dp" />
|
5
core-ui/src/main/res/layout/item_block_table_space.xml
Normal file
5
core-ui/src/main/res/layout/item_block_table_space.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/item_block_table_space_end_width"
|
||||
android:layout_height="match_parent">
|
||||
</View>
|
34
core-ui/src/main/res/layout/item_simple_table_action.xml
Normal file
34
core-ui/src/main/res/layout/item_simple_table_action.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dp_10"
|
||||
android:layout_marginEnd="@dimen/dp_10"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/rect_block_action_button_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="11sp"
|
||||
tools:text="Delete" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp"
|
||||
android:overScrollMode="never"
|
||||
android:id="@+id/recyclerCell"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
40
core-ui/src/main/res/layout/widget_simple_table.xml
Normal file
40
core-ui/src/main/res/layout/widget_simple_table.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:context="com.anytypeio.anytype.core_ui.widgets.toolbar.table.SimpleTableSettingWidget">
|
||||
|
||||
<View
|
||||
android:id="@+id/dragger"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/dragger" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabsLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabGravity="center"
|
||||
app:tabBackground="@null"
|
||||
app:tabMode="fixed"
|
||||
app:tabIndicator="@null"
|
||||
app:tabSelectedTextColor="@color/text_primary"
|
||||
app:tabTextAppearance="@style/BlockTableWidgetTabsStyle"
|
||||
app:tabTextColor="@color/text_tertiary" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingTop="@dimen/dp_20"
|
||||
android:overScrollMode="never" />
|
||||
|
||||
</LinearLayout>
|
|
@ -179,5 +179,7 @@
|
|||
<color name="palette_system_red">#F55522</color>
|
||||
|
||||
<color name="dashboard_tab_color">#CC0066C3</color>
|
||||
<color name="amber_80">#FFC532</color>
|
||||
<color name="table_row_header_background">#1A50491C</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -288,4 +288,9 @@
|
|||
<dimen name="selection_left_right_offset">12dp</dimen>
|
||||
<dimen name="divider_extra_space_bottom">2dp</dimen>
|
||||
|
||||
<dimen name="item_block_table_cell_width">140dp</dimen>
|
||||
<dimen name="item_block_table_cell_height">63dp</dimen>
|
||||
<dimen name="item_block_table_cell_padding_start">12dp</dimen>
|
||||
<dimen name="item_block_table_cell_padding_top">9dp</dimen>
|
||||
<dimen name="item_block_table_space_end_width">20dp</dimen>
|
||||
</resources>
|
|
@ -384,6 +384,9 @@
|
|||
<string name="slash_widget_other_line">Line divider</string>
|
||||
<string name="slash_widget_other_dots">Dots divider</string>
|
||||
<string name="slash_widget_other_toc">Table of contents</string>
|
||||
<string name="slash_widget_other_simple_table">Simple table</string>
|
||||
<string name="slash_widgth_other_simple_table_rows_columns_count">Simple table %1$dx%2$d</string>
|
||||
<string name="slash_widget_other_simple_table_subtitle">Create a simple table</string>
|
||||
|
||||
<string name="slash_widget_actions_delete">Delete</string>
|
||||
<string name="slash_widget_actions_duplicate">Duplicate</string>
|
||||
|
@ -525,4 +528,20 @@
|
|||
<string name="item_relation_create_from_scratch_title">Type</string>
|
||||
<string name="btn_restore">Restore</string>
|
||||
|
||||
<string name="simple_tables_widget_item_clear_contents">Clear contents</string>
|
||||
<string name="simple_tables_widget_item_clear_color">Color</string>
|
||||
<string name="simple_tables_widget_item_clear_style">Style</string>
|
||||
<string name="simple_tables_widget_item_clear_clear_style">Clear style</string>
|
||||
<string name="simple_tables_widget_item_insert_left">Insert left</string>
|
||||
<string name="simple_tables_widget_item_insert_right">Insert right</string>
|
||||
<string name="simple_tables_widget_item_move_left">Move left</string>
|
||||
<string name="simple_tables_widget_item_move_right">Move right</string>
|
||||
<string name="simple_tables_widget_item_insert_above">Insert above</string>
|
||||
<string name="simple_tables_widget_item_insert_below">Insert below</string>
|
||||
<string name="simple_tables_widget_item_move_up">Move up</string>
|
||||
<string name="simple_tables_widget_item_move_down">Move down</string>
|
||||
<string name="simple_tables_widget_tab_cell">Cell</string>
|
||||
<string name="simple_tables_widget_tab_row">Row</string>
|
||||
<string name="simple_tables_widget_tab_column">Column</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -983,4 +983,30 @@
|
|||
<item name="android:textSize">15sp</item>
|
||||
</style>
|
||||
|
||||
<!-- Editor, table block style, {root {container {recycler}} selected}-->
|
||||
<style name="DefaultTableBlockRootStyle">
|
||||
<item name="android:layout_marginTop">10dp</item>
|
||||
<item name="android:layout_marginBottom">10dp</item>
|
||||
<item name="android:paddingStart">@dimen/default_document_item_padding_start</item>
|
||||
</style>
|
||||
|
||||
<style name="DefaultTableBlockContainerStyle">
|
||||
<item name="android:paddingStart">@dimen/default_graphic_text_container_padding_start</item>
|
||||
</style>
|
||||
|
||||
<style name="BlockCellTextContentStyle" parent="DefaultEditorTextStyle">
|
||||
<item name="android:fontFamily">@font/inter_regular</item>
|
||||
<item name="android:paddingTop">9dp</item>
|
||||
<item name="android:paddingBottom">9dp</item>
|
||||
<item name="android:paddingStart">@dimen/default_document_content_padding_start</item>
|
||||
<item name="android:paddingEnd">@dimen/default_document_content_padding_end</item>
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
</style>
|
||||
|
||||
<style name="BlockTableWidgetTabsStyle">
|
||||
<item name="android:fontFamily">@font/inter_bold</item>
|
||||
<item name="android:textSize">17sp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,225 @@
|
|||
package com.anytypeio.anytype.core_ui.features.editor.table
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.StubParagraph
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.uitests.givenAdapter
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
import com.anytypeio.anytype.test_utils.TestFragment
|
||||
import com.anytypeio.anytype.test_utils.utils.checkHasChildViewCount
|
||||
import com.anytypeio.anytype.test_utils.utils.checkHasChildViewWithText
|
||||
import com.anytypeio.anytype.test_utils.utils.checkIsDisplayed
|
||||
import com.anytypeio.anytype.test_utils.utils.checkIsRecyclerSize
|
||||
import com.anytypeio.anytype.test_utils.utils.onItemView
|
||||
import com.anytypeio.anytype.test_utils.utils.rVMatcher
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(
|
||||
manifest = Config.NONE,
|
||||
sdk = [Build.VERSION_CODES.P],
|
||||
instrumentedPackages = ["androidx.loader.content"]
|
||||
)
|
||||
class TableBlockTest {
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
lateinit var scenario: FragmentScenario<TestFragment>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context.setTheme(R.style.Theme_MaterialComponents)
|
||||
scenario = launchFragmentInContainer()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should render table block and update text in cell`() {
|
||||
|
||||
val rowId1 = "rowId1"
|
||||
val columnId1 = "columnId1"
|
||||
val columnId2 = "columnId2"
|
||||
val columnId3 = "columnId3"
|
||||
val columnId4 = "columnId4"
|
||||
|
||||
val oldText = "oldText"
|
||||
val newText = "NewText"
|
||||
|
||||
val row1Block1 =
|
||||
StubParagraph(id = "$rowId1-$columnId2", text = "a1")
|
||||
val row1Block2 = StubParagraph(id = "$rowId1-$columnId3", text = oldText)
|
||||
|
||||
val cells = listOf(
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId1,
|
||||
columnId = columnId1
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row1Block1.id,
|
||||
text = row1Block1.content.asText().text
|
||||
),
|
||||
rowId = rowId1,
|
||||
columnId = columnId2
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row1Block2.id,
|
||||
text = row1Block2.content.asText().text
|
||||
),
|
||||
rowId = rowId1,
|
||||
columnId = columnId3
|
||||
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId1,
|
||||
columnId = columnId4
|
||||
)
|
||||
)
|
||||
|
||||
val cellsNew = listOf(
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId1,
|
||||
columnId = columnId1,
|
||||
settings = BlockView.Table.CellSettings(
|
||||
width = 140
|
||||
)
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row1Block1.id,
|
||||
text = row1Block1.content.asText().text
|
||||
),
|
||||
settings = BlockView.Table.CellSettings(
|
||||
width = 140
|
||||
),
|
||||
rowId = rowId1,
|
||||
columnId = columnId2
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row1Block2.id,
|
||||
text = newText
|
||||
),
|
||||
settings = BlockView.Table.CellSettings(
|
||||
width = 140
|
||||
),
|
||||
rowId = rowId1,
|
||||
columnId = columnId3
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId1,
|
||||
columnId = columnId4,
|
||||
settings = BlockView.Table.CellSettings(
|
||||
width = 140
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val columns = listOf(
|
||||
BlockView.Table.Column(id = columnId1, backgroundColor = null),
|
||||
BlockView.Table.Column(id = columnId2, backgroundColor = null),
|
||||
BlockView.Table.Column(id = columnId3, backgroundColor = null),
|
||||
BlockView.Table.Column(id = columnId4, backgroundColor = null)
|
||||
)
|
||||
|
||||
scenario.onFragment {
|
||||
it.view?.updateLayoutParams {
|
||||
width = 1200
|
||||
}
|
||||
val recycler = givenRecycler(it)
|
||||
|
||||
val tableId = MockDataFactory.randomUuid()
|
||||
val views = listOf<BlockView>(
|
||||
BlockView.Table(
|
||||
id = tableId,
|
||||
cells = cells,
|
||||
columns = columns,
|
||||
rowCount = 1,
|
||||
isSelected = false
|
||||
)
|
||||
)
|
||||
val adapter = givenAdapter(views)
|
||||
recycler.adapter = adapter
|
||||
|
||||
com.anytypeio.anytype.test_utils.R.id.recycler.rVMatcher().apply {
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkIsDisplayed()
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewCount(4)
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewWithText(
|
||||
0,
|
||||
"",
|
||||
R.id.textContent
|
||||
).checkIsDisplayed()
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewWithText(
|
||||
1,
|
||||
row1Block1.content.asText().text,
|
||||
R.id.textContent
|
||||
).checkIsDisplayed()
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewWithText(
|
||||
2,
|
||||
row1Block2.content.asText().text,
|
||||
R.id.textContent
|
||||
).checkIsDisplayed()
|
||||
}
|
||||
|
||||
val viewsUpdated = listOf<BlockView>(
|
||||
BlockView.Table(
|
||||
id = tableId,
|
||||
cells = cellsNew,
|
||||
columns = columns,
|
||||
rowCount = 1,
|
||||
isSelected = false
|
||||
)
|
||||
)
|
||||
|
||||
adapter.updateWithDiffUtil(viewsUpdated)
|
||||
|
||||
com.anytypeio.anytype.test_utils.R.id.recycler.rVMatcher().apply {
|
||||
checkIsRecyclerSize(1)
|
||||
onItemView(0, R.id.recyclerTable).checkIsDisplayed()
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewCount(4)
|
||||
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewWithText(
|
||||
0,
|
||||
"",
|
||||
R.id.textContent
|
||||
).checkIsDisplayed()
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewWithText(
|
||||
1,
|
||||
row1Block1.content.asText().text,
|
||||
R.id.textContent
|
||||
).checkIsDisplayed()
|
||||
|
||||
onItemView(0, R.id.recyclerTable).checkHasChildViewWithText(
|
||||
2,
|
||||
newText,
|
||||
R.id.textContent
|
||||
).checkIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun givenRecycler(it: Fragment): RecyclerView =
|
||||
it.view!!.findViewById<RecyclerView>(com.anytypeio.anytype.test_utils.R.id.recycler).apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ object SlashConst {
|
|||
const val SLASH_OTHER_LINE = "Line divider"
|
||||
const val SLASH_OTHER_TOC = "Table of contents"
|
||||
const val SLASH_OTHER_TOC_ABBREVIATION = "toc"
|
||||
const val SLASH_OTHER_SIMPLE_TABLE = "Simple table"
|
||||
|
||||
const val SLASH_ALIGN_LEFT = "Left"
|
||||
const val SLASH_ALIGN_CENTER = "Center"
|
||||
|
|
|
@ -75,4 +75,6 @@ fun String.isEndLineClick(range: IntRange): Boolean = range.first == length && r
|
|||
inline fun <reified T> Fragment.withParent(action: T.() -> Unit) {
|
||||
check(parentFragment is T) { "Parent is not ${T::class.java}. Please specify correct type" }
|
||||
(parentFragment as T).action()
|
||||
}
|
||||
}
|
||||
|
||||
fun MatchResult?.parseMatchedInt(index: Int): Int? = this?.groups?.get(index)?.value?.toIntOrNull()
|
|
@ -597,4 +597,21 @@ class BlockDataRepository(
|
|||
override suspend fun duplicateObject(id: Id): Id {
|
||||
return remote.duplicateObject(id)
|
||||
}
|
||||
|
||||
override suspend fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rowCount: Int,
|
||||
columnCount: Int
|
||||
): Payload = remote.createTable(
|
||||
ctx = ctx,
|
||||
target = target,
|
||||
position = position,
|
||||
rows = rowCount,
|
||||
columns = columnCount
|
||||
)
|
||||
|
||||
override suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload =
|
||||
remote.fillTableRow(ctx, targetIds)
|
||||
}
|
|
@ -241,4 +241,14 @@ interface BlockDataStore {
|
|||
suspend fun duplicateObject(id: Id): Id
|
||||
|
||||
suspend fun applyTemplate(ctx: Id, template: Id)
|
||||
|
||||
suspend fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rows: Int,
|
||||
columns: Int
|
||||
): Payload
|
||||
|
||||
suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload
|
||||
}
|
|
@ -240,4 +240,14 @@ interface BlockRemote {
|
|||
suspend fun duplicateObject(id: Id): Id
|
||||
|
||||
suspend fun applyTemplate(ctx: Id, template: Id)
|
||||
|
||||
suspend fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rows: Int,
|
||||
columns: Int
|
||||
): Payload
|
||||
|
||||
suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload
|
||||
}
|
|
@ -513,4 +513,21 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
|
|||
ctx = ctx,
|
||||
template = template
|
||||
)
|
||||
|
||||
override suspend fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rowCount: Int,
|
||||
columCount: Int
|
||||
): Payload = remote.createTable(
|
||||
ctx = ctx,
|
||||
target = target,
|
||||
position = position,
|
||||
rows = rowCount,
|
||||
columns = columCount
|
||||
)
|
||||
|
||||
override suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload =
|
||||
remote.fillTableRow(ctx, targetIds)
|
||||
}
|
|
@ -121,7 +121,7 @@ ext {
|
|||
retrofit: "com.squareup.retrofit2:converter-gson:$retrofit_version",
|
||||
okhttpLoggingInterceptor: "com.squareup.okhttp3:logging-interceptor:$okhttp_logging_interceptor_version",
|
||||
timber: "com.jakewharton.timber:timber:$timber_version",
|
||||
tableView: "com.evrencoskun.library:tableview:$table_view_version",
|
||||
tableView: "com.github.evrencoskun:TableView:$table_view_version",
|
||||
exoPlayerCore: "com.google.android.exoplayer:exoplayer-core:$exoplayer_version",
|
||||
exoPlayerUi: "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version",
|
||||
pickT: "com.github.HBiSoft:PickiT:$pickt_version",
|
||||
|
|
|
@ -301,4 +301,14 @@ interface BlockRepository {
|
|||
suspend fun duplicateObject(id: Id): Id
|
||||
|
||||
suspend fun applyTemplate(ctx: Id, template: Id)
|
||||
|
||||
suspend fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rowCount: Int,
|
||||
columnCount: Int
|
||||
): Payload
|
||||
|
||||
suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.anytypeio.anytype.domain.table
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Position
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
||||
class CreateTable(
|
||||
private val repo: BlockRepository
|
||||
) : BaseUseCase<Payload, CreateTable.Params>() {
|
||||
|
||||
override suspend fun run(params: Params): Either<Throwable, Payload> = safe {
|
||||
repo.createTable(
|
||||
ctx = params.ctx,
|
||||
target = params.target,
|
||||
position = params.position,
|
||||
rowCount = params.rowCount ?: DEFAULT_ROW_COUNT,
|
||||
columnCount = params.columnCount ?: DEFAULT_COLUMN_COUNT
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val ctx: Id,
|
||||
val target: Id,
|
||||
val position: Position,
|
||||
val rowCount: Int?,
|
||||
val columnCount: Int?
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_ROW_COUNT = 3
|
||||
const val DEFAULT_COLUMN_COUNT = 3
|
||||
|
||||
const val DEFAULT_MAX_ROW_COUNT = 25
|
||||
const val DEFAULT_MAX_COLUMN_COUNT = 25
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.anytypeio.anytype.domain.table
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
||||
class FillTableRow(
|
||||
private val repo: BlockRepository
|
||||
) : BaseUseCase<Payload, FillTableRow.Params>() {
|
||||
|
||||
override suspend fun run(params: Params): Either<Throwable, Payload> = safe {
|
||||
repo.fillTableRow(
|
||||
ctx = params.ctx,
|
||||
targetIds = params.targetIds
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @property [targetIds] the list of id rows that need to be filled in
|
||||
*/
|
||||
data class Params(
|
||||
val ctx: Id,
|
||||
val targetIds: List<Id>
|
||||
)
|
||||
}
|
|
@ -549,4 +549,21 @@ class BlockMiddleware(
|
|||
ctx = ctx,
|
||||
template = template
|
||||
)
|
||||
|
||||
override suspend fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rowCount: Int,
|
||||
columnCount: Int
|
||||
): Payload = middleware.createTable(
|
||||
ctx = ctx,
|
||||
target = target,
|
||||
position = position,
|
||||
rowCount = rowCount,
|
||||
columnCount = columnCount
|
||||
)
|
||||
|
||||
override suspend fun fillTableRow(ctx: String, targetIds: List<String>): Payload =
|
||||
middleware.fillTableRow(ctx, targetIds)
|
||||
}
|
|
@ -1646,6 +1646,39 @@ class Middleware(
|
|||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun createTable(
|
||||
ctx: String,
|
||||
target: String,
|
||||
position: Position,
|
||||
rowCount: Int,
|
||||
columnCount: Int
|
||||
): Payload {
|
||||
val request = Rpc.BlockTable.Create.Request(
|
||||
contextId = ctx,
|
||||
targetId = target,
|
||||
position = position.toMiddlewareModel(),
|
||||
rows = rowCount,
|
||||
columns = columnCount
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.createTable(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun fillTableRow(ctx: String, targetIds: List<String>): Payload {
|
||||
val request = Rpc.BlockTable.RowListFill.Request(
|
||||
contextId = ctx,
|
||||
blockIds = targetIds
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.blockTableRowListFill(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
private fun logRequest(any: Any) {
|
||||
val message = "===> " + any::class.java.canonicalName + ":" + "\n" + any.toString()
|
||||
Timber.d(message)
|
||||
|
|
|
@ -145,6 +145,33 @@ fun List<MBlock>.toCoreModels(
|
|||
backgroundColor = block.backgroundColor.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
block.table != null -> {
|
||||
Block(
|
||||
id = block.id,
|
||||
fields = block.toCoreModelsFields(),
|
||||
children = block.childrenIds,
|
||||
content = Block.Content.Table,
|
||||
backgroundColor = block.backgroundColor
|
||||
)
|
||||
}
|
||||
block.tableColumn != null -> {
|
||||
Block(
|
||||
id = block.id,
|
||||
fields = block.toCoreModelsFields(),
|
||||
children = block.childrenIds,
|
||||
content = Block.Content.TableColumn,
|
||||
backgroundColor = block.backgroundColor
|
||||
)
|
||||
}
|
||||
block.tableRow != null -> {
|
||||
Block(
|
||||
id = block.id,
|
||||
fields = block.toCoreModelsFields(),
|
||||
children = block.childrenIds,
|
||||
content = block.toCoreModelsTableRowBlock(),
|
||||
backgroundColor = block.backgroundColor
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Block(
|
||||
id = block.id,
|
||||
|
@ -278,6 +305,13 @@ fun MBlock.toCoreModelsRelationBlock(): Block.Content.RelationBlock {
|
|||
)
|
||||
}
|
||||
|
||||
fun MBlock.toCoreModelsTableRowBlock(): Block.Content.TableRow {
|
||||
val content = checkNotNull(tableRow)
|
||||
return Block.Content.TableRow(
|
||||
isHeader = content.isHeader
|
||||
)
|
||||
}
|
||||
|
||||
fun MBFileState.toCoreModels(): Block.Content.File.State = when (this) {
|
||||
MBFileState.Empty -> Block.Content.File.State.EMPTY
|
||||
MBFileState.Uploading -> Block.Content.File.State.UPLOADING
|
||||
|
|
|
@ -299,6 +299,16 @@ interface MiddlewareService {
|
|||
|
||||
//endregion
|
||||
|
||||
//region SIMPLE TABLE commands
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun createTable(request: Rpc.BlockTable.Create.Request) : Rpc.BlockTable.Create.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun blockTableRowListFill(request: Rpc.BlockTable.RowListFill.Request): Rpc.BlockTable.RowListFill.Response
|
||||
|
||||
//endregion
|
||||
|
||||
//region DEBUG commands
|
||||
|
||||
@Throws(Exception::class)
|
||||
|
|
|
@ -1046,4 +1046,28 @@ class MiddlewareServiceImplementation : MiddlewareService {
|
|||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun createTable(request: Rpc.BlockTable.Create.Request): Rpc.BlockTable.Create.Response {
|
||||
val encoded =
|
||||
Service.blockTableCreate(Rpc.BlockTable.Create.Request.ADAPTER.encode(request))
|
||||
val response = Rpc.BlockTable.Create.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.BlockTable.Create.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun blockTableRowListFill(request: Rpc.BlockTable.RowListFill.Request): Rpc.BlockTable.RowListFill.Response {
|
||||
val encoded =
|
||||
Service.blockTableRowListFill(Rpc.BlockTable.RowListFill.Request.ADAPTER.encode(request))
|
||||
val response = Rpc.BlockTable.RowListFill.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.BlockTable.RowListFill.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
|
@ -108,6 +108,8 @@ import com.anytypeio.anytype.presentation.editor.editor.ext.toReadMode
|
|||
import com.anytypeio.anytype.presentation.editor.editor.ext.update
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ext.updateCursorAndEditMode
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ext.updateSelection
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ext.applyBordersToSelectedCells
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ext.removeBordersFromCells
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ext.updateTableOfContentsViews
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.markup
|
||||
|
@ -140,6 +142,10 @@ import com.anytypeio.anytype.presentation.editor.editor.styling.getStyleBackgrou
|
|||
import com.anytypeio.anytype.presentation.editor.editor.styling.getStyleColorBackgroundToolbarState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.getStyleOtherToolbarState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.getStyleTextToolbarState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetEvent
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetViewState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.toCoreModel
|
||||
import com.anytypeio.anytype.presentation.editor.editor.updateText
|
||||
import com.anytypeio.anytype.presentation.editor.model.EditorFooter
|
||||
|
@ -237,6 +243,7 @@ class EditorViewModel(
|
|||
private val setDocCoverImage: SetDocCoverImage,
|
||||
private val setDocImageIcon: SetDocumentImageIcon,
|
||||
private val templateDelegate: EditorTemplateDelegate,
|
||||
private val simpleTableDelegate: SimpleTableDelegate,
|
||||
private val createNewObject: CreateNewObject
|
||||
) : ViewStateViewModel<ViewState>(),
|
||||
PickerListener,
|
||||
|
@ -246,6 +253,7 @@ class EditorViewModel(
|
|||
ToggleStateHolder by renderer,
|
||||
SelectionStateHolder by orchestrator.memory.selections,
|
||||
EditorTemplateDelegate by templateDelegate,
|
||||
SimpleTableDelegate by simpleTableDelegate,
|
||||
StateReducer<List<Block>, Event> by reducer {
|
||||
|
||||
val actions = MutableStateFlow(ActionItemType.defaultSorting)
|
||||
|
@ -268,6 +276,17 @@ class EditorViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
val simpleTablesViewState = simpleTableDelegateState.map { state ->
|
||||
when (state) {
|
||||
is SimpleTableWidgetState.UpdateItems -> {
|
||||
SimpleTableWidgetViewState.Active(
|
||||
state = state
|
||||
)
|
||||
}
|
||||
SimpleTableWidgetState.Idle -> SimpleTableWidgetViewState.Idle
|
||||
}
|
||||
}
|
||||
|
||||
val searchResultScrollPosition = MutableStateFlow(NO_SEARCH_RESULT_POSITION)
|
||||
|
||||
private val session = MutableStateFlow(Session.IDLE)
|
||||
|
@ -1688,6 +1707,11 @@ class EditorViewModel(
|
|||
is Content.Text -> {
|
||||
excludedActions.add(ActionItemType.Download)
|
||||
}
|
||||
is Content.Table -> {
|
||||
excludedActions.add(ActionItemType.Paste)
|
||||
excludedActions.add(ActionItemType.Copy)
|
||||
excludedActions.add(ActionItemType.Style)
|
||||
}
|
||||
else -> {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -1966,6 +1990,10 @@ class EditorViewModel(
|
|||
viewModelScope.launch { controlPanelInteractor.onEvent(ControlPanelMachine.Event.SearchToolbar.OnEnterSearchMode) }
|
||||
}
|
||||
|
||||
fun onSetTextBlockValue() {
|
||||
viewModelScope.launch { refresh() }
|
||||
}
|
||||
|
||||
fun onDocRelationsClicked() {
|
||||
Timber.d("onDocRelationsClicked, ")
|
||||
dispatch(
|
||||
|
@ -2712,6 +2740,86 @@ class EditorViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addSimpleTableBlock(item: SlashItem.Other.Table) {
|
||||
|
||||
val focused = blocks.first { it.id == orchestrator.stores.focus.current().id }
|
||||
val content = focused.content
|
||||
|
||||
if (content is Content.Text && content.text.isEmpty()) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.Table.CreateTable(
|
||||
ctx = context,
|
||||
target = focused.id,
|
||||
position = Position.REPLACE,
|
||||
rows = item.rowCount,
|
||||
columns = item.columnCount
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
val position: Position
|
||||
|
||||
var target: Id = focused.id
|
||||
|
||||
if (focused.id == context) {
|
||||
if (focused.children.isEmpty()) {
|
||||
position = Position.INNER
|
||||
} else {
|
||||
position = Position.TOP
|
||||
target = focused.children.first()
|
||||
}
|
||||
} else {
|
||||
position = Position.BOTTOM
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.Table.CreateTable(
|
||||
ctx = context,
|
||||
target = target,
|
||||
position = position
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTableRowEmptyCellClicked(cellId: Id, rowId: Id, tableId: Id) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.stores.focus.update(
|
||||
Editor.Focus(
|
||||
id = cellId,
|
||||
cursor = Editor.Cursor.Start
|
||||
)
|
||||
)
|
||||
}
|
||||
fillTableBlockRow(
|
||||
cellId = cellId,
|
||||
targetIds = listOf(rowId),
|
||||
tableId = tableId
|
||||
)
|
||||
}
|
||||
|
||||
private fun fillTableBlockRow(cellId: Id, targetIds: List<Id>, tableId: Id) {
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.Table.FillTableRow(
|
||||
ctx = context,
|
||||
targetIds = targetIds
|
||||
)
|
||||
)
|
||||
}
|
||||
dispatch(
|
||||
Command.OpenSetBlockTextValueScreen(
|
||||
ctx = context,
|
||||
block = cellId,
|
||||
table = tableId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun onAddDividerBlockClicked(style: Content.Divider.Style) {
|
||||
Timber.d("onAddDividerBlockClicked, style:[$style]")
|
||||
addDividerBlock(style)
|
||||
|
@ -3712,6 +3820,46 @@ class EditorViewModel(
|
|||
is ListenerType.Callout.Icon -> {
|
||||
dispatch(Command.OpenTextBlockIconPicker(clicked.blockId))
|
||||
}
|
||||
is ListenerType.TableEmptyCell -> {
|
||||
when (mode) {
|
||||
EditorMode.Edit -> {
|
||||
proceedWithSelectingCell(
|
||||
cellId = clicked.cellId,
|
||||
tableId = clicked.tableId
|
||||
)
|
||||
onTableRowEmptyCellClicked(
|
||||
cellId = clicked.cellId,
|
||||
rowId = clicked.rowId,
|
||||
tableId = clicked.tableId
|
||||
)
|
||||
}
|
||||
EditorMode.Select -> onBlockMultiSelectClicked(target = clicked.tableId)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is ListenerType.TableTextCell -> {
|
||||
when (mode) {
|
||||
EditorMode.Edit -> {
|
||||
proceedWithSelectingCell(
|
||||
cellId = clicked.cellId,
|
||||
tableId = clicked.tableId
|
||||
)
|
||||
dispatch(
|
||||
Command.OpenSetBlockTextValueScreen(
|
||||
ctx = context,
|
||||
block = clicked.cellId,
|
||||
table = clicked.tableId
|
||||
)
|
||||
)
|
||||
}
|
||||
EditorMode.Select -> onBlockMultiSelectClicked(target = clicked.tableId)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is ListenerType.TableEmptyCellMenu -> {}
|
||||
is ListenerType.TableTextCellMenu -> {
|
||||
onShowSimpleTableWidgetClicked(id = clicked.cellId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4341,6 +4489,12 @@ class EditorViewModel(
|
|||
Command.OpenAddRelationScreen(ctx = context, target = targetId)
|
||||
)
|
||||
}
|
||||
is SlashItem.Other.Table -> {
|
||||
cutSlashFilter(targetId = targetId)
|
||||
controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop)
|
||||
onHideKeyboardClicked()
|
||||
addSimpleTableBlock(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5290,7 +5444,7 @@ class EditorViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onMentionClicked(target: String) {
|
||||
fun onMentionClicked(target: String) {
|
||||
proceedWithOpeningObjectByLayout(target)
|
||||
}
|
||||
|
||||
|
@ -5738,6 +5892,42 @@ class EditorViewModel(
|
|||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region SIMPLE TABLES
|
||||
private fun onShowSimpleTableWidgetClicked(id: Id) {
|
||||
viewModelScope.launch {
|
||||
onSimpleTableEvent(SimpleTableWidgetEvent.onStart(id = id))
|
||||
}
|
||||
}
|
||||
|
||||
fun onHideSimpleTableWidget() {}
|
||||
|
||||
private fun proceedWithSelectingCell(cellId:Id, tableId: Id) {
|
||||
|
||||
clearSelections()
|
||||
select(listOf(cellId))
|
||||
|
||||
val updated = views.applyBordersToSelectedCells(
|
||||
tableId = tableId,
|
||||
selection = currentSelection()
|
||||
)
|
||||
|
||||
viewModelScope.launch {
|
||||
orchestrator.stores.focus.update(Editor.Focus.empty())
|
||||
orchestrator.stores.views.update(updated)
|
||||
renderCommand.send(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSetBlockTextValueScreenDismiss() {
|
||||
clearSelections()
|
||||
val updated = views.removeBordersFromCells()
|
||||
viewModelScope.launch {
|
||||
orchestrator.stores.views.update(updated)
|
||||
renderCommand.send(Unit)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
||||
private const val NO_POSITION = -1
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.anytypeio.anytype.presentation.common.StateReducer
|
|||
import com.anytypeio.anytype.domain.page.CreateNewObject
|
||||
import com.anytypeio.anytype.presentation.editor.editor.DetailModificationManager
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Orchestrator
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate
|
||||
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
|
||||
|
@ -69,6 +70,7 @@ open class EditorViewModelFactory(
|
|||
private val setDocCoverImage: SetDocCoverImage,
|
||||
private val setDocImageIcon: SetDocumentImageIcon,
|
||||
private val editorTemplateDelegate: EditorTemplateDelegate,
|
||||
private val simpleTablesDelegate: SimpleTableDelegate,
|
||||
private val createNewObject: CreateNewObject
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
|
@ -105,7 +107,8 @@ open class EditorViewModelFactory(
|
|||
setDocCoverImage = setDocCoverImage,
|
||||
setDocImageIcon = setDocImageIcon,
|
||||
templateDelegate = editorTemplateDelegate,
|
||||
createNewObject = createNewObject
|
||||
createNewObject = createNewObject,
|
||||
simpleTableDelegate = simpleTablesDelegate
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -140,4 +140,10 @@ sealed class Command {
|
|||
) : Command()
|
||||
|
||||
data class ScrollToPosition(val pos: Int) : Command()
|
||||
|
||||
data class OpenSetBlockTextValueScreen(
|
||||
val ctx: Id,
|
||||
val table: Id,
|
||||
val block: Id
|
||||
) : Command()
|
||||
}
|
|
@ -211,4 +211,20 @@ sealed class Intent {
|
|||
val style: Block.Content.Divider.Style
|
||||
) : Divider()
|
||||
}
|
||||
|
||||
sealed class Table : Intent() {
|
||||
|
||||
class CreateTable(
|
||||
val ctx: Id,
|
||||
val target: Id,
|
||||
val position: Position,
|
||||
val rows: Int? = null,
|
||||
val columns: Int? = null
|
||||
) : Table()
|
||||
|
||||
class FillTableRow(
|
||||
val ctx: Id,
|
||||
val targetIds: List<Id>
|
||||
) : Table()
|
||||
}
|
||||
}
|
|
@ -33,6 +33,8 @@ import com.anytypeio.anytype.domain.page.Redo
|
|||
import com.anytypeio.anytype.domain.page.Undo
|
||||
import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock
|
||||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.domain.table.CreateTable
|
||||
import com.anytypeio.anytype.domain.table.FillTableRow
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsChangeTextBlockStyleEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsCopyBlockEvent
|
||||
|
@ -69,6 +71,8 @@ class Orchestrator(
|
|||
private val createBookmarkBlock: CreateBookmarkBlock,
|
||||
private val turnIntoDocument: TurnIntoDocument,
|
||||
private val updateFields: UpdateFields,
|
||||
private val createTable: CreateTable,
|
||||
private val fillTableRow: FillTableRow,
|
||||
private val move: Move,
|
||||
private val copy: Copy,
|
||||
private val paste: Paste,
|
||||
|
@ -569,6 +573,31 @@ class Orchestrator(
|
|||
}
|
||||
)
|
||||
}
|
||||
is Intent.Table.CreateTable -> {
|
||||
createTable(
|
||||
params = CreateTable.Params(
|
||||
ctx = intent.ctx,
|
||||
target = intent.target,
|
||||
position = intent.position,
|
||||
rowCount = intent.rows,
|
||||
columnCount = intent.columns
|
||||
)
|
||||
).process(
|
||||
failure = defaultOnError,
|
||||
success = { payload -> proxies.payloads.send(payload) }
|
||||
)
|
||||
}
|
||||
is Intent.Table.FillTableRow -> {
|
||||
fillTableRow(
|
||||
params = FillTableRow.Params(
|
||||
ctx = intent.ctx,
|
||||
targetIds = intent.targetIds
|
||||
)
|
||||
).process(
|
||||
failure = defaultOnError,
|
||||
success = { payload -> proxies.payloads.send(payload) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -337,6 +337,9 @@ fun List<BlockView>.enterSAM(
|
|||
is BlockView.TableOfContents -> view.copy(
|
||||
isSelected = isSelected
|
||||
)
|
||||
is BlockView.Table -> view.copy(
|
||||
isSelected = isSelected
|
||||
)
|
||||
else -> view.also { check(view !is BlockView.Permission) }
|
||||
}
|
||||
}
|
||||
|
@ -892,6 +895,7 @@ fun BlockView.updateSelection(newSelection: Boolean) = when (this) {
|
|||
is BlockView.Relation.Placeholder -> copy(isSelected = newSelection)
|
||||
is BlockView.Latex -> copy(isSelected = newSelection)
|
||||
is BlockView.TableOfContents -> copy(isSelected = newSelection)
|
||||
is BlockView.Table -> copy(isSelected = newSelection)
|
||||
else -> this.also {
|
||||
if (this is BlockView.Selectable)
|
||||
Timber.e("Error when change selection for Selectable BlockView $this")
|
||||
|
@ -1058,4 +1062,57 @@ fun List<BlockView>.fillTableOfContents(): List<BlockView> {
|
|||
|
||||
fun BlockView.Text.isStyleClearable(): Boolean {
|
||||
return this.isListBlock || this is BlockView.Text.Highlight
|
||||
}
|
||||
|
||||
fun List<BlockView>.applyBordersToSelectedCells(
|
||||
tableId: Id,
|
||||
selection: Set<Id>
|
||||
): List<BlockView> = map { view ->
|
||||
if (view.id == tableId && view is BlockView.Table) {
|
||||
val updatedCells = view.cells.map { cell ->
|
||||
when (cell) {
|
||||
is BlockView.Table.Cell.Empty -> {
|
||||
if (selection.contains(cell.getId())) {
|
||||
val settings = cell.settings.applyAllBorders()
|
||||
cell.copy(settings = settings)
|
||||
} else {
|
||||
cell
|
||||
}
|
||||
}
|
||||
is BlockView.Table.Cell.Text -> {
|
||||
if (selection.contains(cell.getId())) {
|
||||
val settings = cell.settings.applyAllBorders()
|
||||
cell.copy(settings = settings)
|
||||
} else {
|
||||
cell
|
||||
}
|
||||
}
|
||||
BlockView.Table.Cell.Space -> cell
|
||||
}
|
||||
}
|
||||
view.copy(cells = updatedCells)
|
||||
} else {
|
||||
view
|
||||
}
|
||||
}
|
||||
|
||||
fun List<BlockView>.removeBordersFromCells(): List<BlockView> = map { view ->
|
||||
if (view is BlockView.Table) {
|
||||
val updatedCells = view.cells.map { cell ->
|
||||
when (cell) {
|
||||
is BlockView.Table.Cell.Empty -> {
|
||||
val settings = cell.settings.removeAllBorders()
|
||||
cell.copy(settings = settings)
|
||||
}
|
||||
is BlockView.Table.Cell.Text -> {
|
||||
val settings = cell.settings.removeAllBorders()
|
||||
cell.copy(settings = settings)
|
||||
}
|
||||
BlockView.Table.Cell.Space -> cell
|
||||
}
|
||||
}
|
||||
view.copy(cells = updatedCells)
|
||||
} else {
|
||||
view
|
||||
}
|
||||
}
|
|
@ -68,4 +68,8 @@ sealed interface ListenerType {
|
|||
}
|
||||
|
||||
data class TableOfContentsItem(val target: Id, val item: Id) : ListenerType
|
||||
data class TableEmptyCell(val cellId: Id, val rowId: Id, val tableId: Id) : ListenerType
|
||||
data class TableTextCell(val cellId: Id, val tableId: Id) : ListenerType
|
||||
data class TableEmptyCellMenu(val rowId: Id, val columnId: Id) : ListenerType
|
||||
data class TableTextCellMenu(val cellId: Id, val rowId: Id, val tableId: Id) : ListenerType
|
||||
}
|
|
@ -48,6 +48,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
|
|||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_PLACEHOLDER
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_STATUS
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_RELATION_TAGS
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TABLE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TITLE
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TOC
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_TODO_TITLE
|
||||
|
@ -107,6 +108,19 @@ sealed class BlockView : ViewType {
|
|||
val isSelected: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Views implementing this interface can change IME action on keyboard
|
||||
* @property inputAction -
|
||||
*/
|
||||
interface SupportInputAction {
|
||||
val inputAction: InputAction
|
||||
}
|
||||
|
||||
sealed interface InputAction {
|
||||
object NewLine : InputAction
|
||||
object Done : InputAction
|
||||
}
|
||||
|
||||
/**
|
||||
* Views implementing this interface support alignment.
|
||||
*/
|
||||
|
@ -203,7 +217,8 @@ sealed class BlockView : ViewType {
|
|||
Indentable,
|
||||
Permission,
|
||||
Alignable,
|
||||
Selectable {
|
||||
Selectable,
|
||||
SupportInputAction {
|
||||
val id: String
|
||||
}
|
||||
|
||||
|
@ -267,7 +282,8 @@ sealed class BlockView : ViewType {
|
|||
}
|
||||
}
|
||||
|
||||
sealed class Text : BlockView(), TextBlockProps, Searchable, SupportGhostEditorSelection, Decoratable {
|
||||
sealed class Text : BlockView(), TextBlockProps, Searchable, SupportGhostEditorSelection,
|
||||
Decoratable {
|
||||
|
||||
// Dynamic properties (expected to be synchronised with framework widget)
|
||||
|
||||
|
@ -307,7 +323,8 @@ sealed class BlockView : ViewType {
|
|||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text() {
|
||||
override fun getViewType() = HOLDER_PARAGRAPH
|
||||
override val body: String get() = text
|
||||
|
@ -336,7 +353,8 @@ sealed class BlockView : ViewType {
|
|||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Header() {
|
||||
override fun getViewType() = HOLDER_HEADER_ONE
|
||||
override val body: String get() = text
|
||||
|
@ -363,7 +381,8 @@ sealed class BlockView : ViewType {
|
|||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Header() {
|
||||
override fun getViewType() = HOLDER_HEADER_TWO
|
||||
override val body: String get() = text
|
||||
|
@ -390,7 +409,8 @@ sealed class BlockView : ViewType {
|
|||
override var cursor: Int? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Header() {
|
||||
override fun getViewType() = HOLDER_HEADER_THREE
|
||||
override val body: String get() = text
|
||||
|
@ -417,7 +437,8 @@ sealed class BlockView : ViewType {
|
|||
override val alignment: Alignment? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text() {
|
||||
override fun getViewType() = HOLDER_HIGHLIGHT
|
||||
override val body: String get() = text
|
||||
|
@ -438,6 +459,7 @@ sealed class BlockView : ViewType {
|
|||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
val icon: ObjectIcon,
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text() {
|
||||
override val alignment: Alignment? = null
|
||||
override fun getViewType() = HOLDER_CALLOUT
|
||||
|
@ -465,7 +487,8 @@ sealed class BlockView : ViewType {
|
|||
override val alignment: Alignment? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text(), Checkable {
|
||||
override fun getViewType() = HOLDER_CHECKBOX
|
||||
override val body: String get() = text
|
||||
|
@ -492,7 +515,8 @@ sealed class BlockView : ViewType {
|
|||
override val alignment: Alignment? = null,
|
||||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList()
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text() {
|
||||
override fun getViewType() = HOLDER_BULLET
|
||||
override val body: String get() = text
|
||||
|
@ -520,7 +544,8 @@ sealed class BlockView : ViewType {
|
|||
override val searchFields: List<Searchable.Field> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
override val decorations: List<Decoration> = emptyList(),
|
||||
val number: Int
|
||||
val number: Int,
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text() {
|
||||
override fun getViewType() = HOLDER_NUMBERED
|
||||
override val body: String get() = text
|
||||
|
@ -549,7 +574,8 @@ sealed class BlockView : ViewType {
|
|||
override val decorations: List<Decoration> = emptyList(),
|
||||
override val ghostEditorSelection: IntRange? = null,
|
||||
val toggled: Boolean = false,
|
||||
val isEmpty: Boolean = false
|
||||
val isEmpty: Boolean = false,
|
||||
override var inputAction: InputAction = InputAction.NewLine
|
||||
) : Text() {
|
||||
override fun getViewType() = HOLDER_TOGGLE
|
||||
override val body: String get() = text
|
||||
|
@ -1176,4 +1202,58 @@ sealed class BlockView : ViewType {
|
|||
)
|
||||
|
||||
enum class Mode { READ, EDIT }
|
||||
|
||||
data class Table(
|
||||
override val id: String,
|
||||
override val isSelected: Boolean,
|
||||
val backgroundColor: String? = null,
|
||||
val columns: List<Column>,
|
||||
val cells: List<Cell>,
|
||||
val rowCount: Int
|
||||
) : BlockView(), Selectable {
|
||||
override fun getViewType(): Int = HOLDER_TABLE
|
||||
|
||||
data class Column(val id: String, val backgroundColor: String?)
|
||||
|
||||
sealed interface Cell {
|
||||
|
||||
data class Text(
|
||||
val rowId: Id,
|
||||
val columnId: Id,
|
||||
val settings: CellSettings = CellSettings.empty(),
|
||||
val block: BlockView.Text.Paragraph
|
||||
) : Cell {
|
||||
fun getId() = "$rowId-$columnId"
|
||||
}
|
||||
|
||||
data class Empty(
|
||||
val rowId: Id,
|
||||
val columnId: Id,
|
||||
val settings: CellSettings = CellSettings.empty(),
|
||||
) : Cell {
|
||||
fun getId() = "$rowId-$columnId"
|
||||
}
|
||||
|
||||
object Space : Cell
|
||||
}
|
||||
|
||||
data class CellSettings(
|
||||
val width: Int = 0,
|
||||
val isHeader: Boolean = false,
|
||||
val top: Boolean = false,
|
||||
val left: Boolean = false,
|
||||
val right: Boolean = false,
|
||||
val bottom: Boolean = false
|
||||
) {
|
||||
fun applyAllBorders() = this.copy(left = true, top = true, right = true, bottom = true)
|
||||
fun removeAllBorders() =
|
||||
this.copy(left = false, top = false, right = false, bottom = false)
|
||||
|
||||
fun isAllBordersApply() = left && top && right && bottom
|
||||
|
||||
companion object {
|
||||
fun empty() = CellSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,4 +61,5 @@ object Types {
|
|||
const val HOLDER_LATEX = 51
|
||||
const val HOLDER_TOC = 54
|
||||
const val HOLDER_CALLOUT = 55
|
||||
const val HOLDER_TABLE = 56
|
||||
}
|
|
@ -2,9 +2,15 @@ package com.anytypeio.anytype.presentation.editor.editor.slash
|
|||
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_utils.ext.parseMatchedInt
|
||||
import com.anytypeio.anytype.domain.table.CreateTable.Companion.DEFAULT_COLUMN_COUNT
|
||||
import com.anytypeio.anytype.domain.table.CreateTable.Companion.DEFAULT_MAX_COLUMN_COUNT
|
||||
import com.anytypeio.anytype.domain.table.CreateTable.Companion.DEFAULT_MAX_ROW_COUNT
|
||||
import com.anytypeio.anytype.domain.table.CreateTable.Companion.DEFAULT_ROW_COUNT
|
||||
import com.anytypeio.anytype.presentation.editor.editor.ThemeColor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.UiBlock
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types
|
||||
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashItem.Other.Table.Companion.DEFAULT_PATTERN
|
||||
|
||||
fun List<ObjectType>.toSlashItemView(): List<SlashItem.ObjectType> = map { oType ->
|
||||
SlashItem.ObjectType(
|
||||
|
@ -94,7 +100,8 @@ object SlashExtensions {
|
|||
fun getSlashWidgetOtherItems() = listOf(
|
||||
SlashItem.Other.Line,
|
||||
SlashItem.Other.Dots,
|
||||
SlashItem.Other.TOC
|
||||
SlashItem.Other.TOC,
|
||||
SlashItem.Other.Table()
|
||||
)
|
||||
|
||||
fun getSlashWidgetActionItems() = listOf(
|
||||
|
@ -281,12 +288,32 @@ object SlashExtensions {
|
|||
subheading: String
|
||||
): List<SlashItem> {
|
||||
val filtered = items.filter { item ->
|
||||
searchBySubheadingOrName(
|
||||
filter = filter,
|
||||
subheading = subheading,
|
||||
name = item.getSearchName(),
|
||||
abbreviation = item.getAbbreviation()
|
||||
)
|
||||
if (item is SlashItem.Other.Table) {
|
||||
val matchResults = DEFAULT_PATTERN.toRegex().findAll(filter)
|
||||
if (matchResults.none()) {
|
||||
searchBySubheadingOrName(
|
||||
filter = filter,
|
||||
subheading = subheading,
|
||||
name = item.getSearchName(),
|
||||
abbreviation = item.getAbbreviation()
|
||||
)
|
||||
} else {
|
||||
val matchResult = matchResults.firstOrNull()
|
||||
val rowCount = matchResult.parseMatchedInt(1) ?: DEFAULT_ROW_COUNT
|
||||
val columnCount = matchResult.parseMatchedInt(2) ?: DEFAULT_COLUMN_COUNT
|
||||
item.rowCount = 1.coerceAtLeast(DEFAULT_MAX_ROW_COUNT.coerceAtMost(rowCount))
|
||||
item.columnCount =
|
||||
1.coerceAtLeast(DEFAULT_MAX_COLUMN_COUNT.coerceAtMost(columnCount))
|
||||
true
|
||||
}
|
||||
} else {
|
||||
searchBySubheadingOrName(
|
||||
filter = filter,
|
||||
subheading = subheading,
|
||||
name = item.getSearchName(),
|
||||
abbreviation = item.getAbbreviation()
|
||||
)
|
||||
}
|
||||
}
|
||||
return updateWithSubheader(items = filtered)
|
||||
}
|
||||
|
|
|
@ -332,6 +332,22 @@ sealed class SlashItem {
|
|||
override fun getSearchName(): String = SlashConst.SLASH_OTHER_TOC
|
||||
override fun getAbbreviation(): List<String> = listOf(SLASH_OTHER_TOC_ABBREVIATION)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple table
|
||||
*/
|
||||
data class Table(
|
||||
var rowCount: Int? = null,
|
||||
var columnCount: Int? = null
|
||||
) : Other() {
|
||||
override fun getSearchName(): String = SlashConst.SLASH_OTHER_SIMPLE_TABLE
|
||||
override fun getAbbreviation(): List<String> = emptyList()
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_PATTERN = "table(\\d+)(?:[^\\d]{1}([\\d]+))?"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package com.anytypeio.anytype.presentation.editor.editor.table
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.scan
|
||||
import timber.log.Timber
|
||||
|
||||
interface SimpleTableDelegate {
|
||||
val simpleTableDelegateState: Flow<SimpleTableWidgetState>
|
||||
suspend fun onSimpleTableEvent(event: SimpleTableWidgetEvent)
|
||||
}
|
||||
|
||||
class DefaultSimpleTableDelegate : SimpleTableDelegate {
|
||||
|
||||
private val events = MutableSharedFlow<SimpleTableWidgetEvent>(replay = 0)
|
||||
|
||||
override val simpleTableDelegateState =
|
||||
events.scan(SimpleTableWidgetState.init()) { state, event ->
|
||||
when (event) {
|
||||
is SimpleTableWidgetEvent.onStart -> {
|
||||
SimpleTableWidgetState.UpdateItems(
|
||||
cellItems = listOf(
|
||||
SimpleTableWidgetItem.Cell.ClearContents,
|
||||
SimpleTableWidgetItem.Cell.Style,
|
||||
SimpleTableWidgetItem.Cell.Color,
|
||||
SimpleTableWidgetItem.Cell.ClearStyle
|
||||
),
|
||||
rowItems = listOf(
|
||||
SimpleTableWidgetItem.Row.ClearContents,
|
||||
SimpleTableWidgetItem.Row.Color,
|
||||
SimpleTableWidgetItem.Row.Style,
|
||||
SimpleTableWidgetItem.Row.Delete,
|
||||
SimpleTableWidgetItem.Row.MoveUp,
|
||||
SimpleTableWidgetItem.Row.MoveDown,
|
||||
SimpleTableWidgetItem.Row.InsertAbove,
|
||||
SimpleTableWidgetItem.Row.InsertBelow,
|
||||
SimpleTableWidgetItem.Row.Duplicate,
|
||||
SimpleTableWidgetItem.Row.Sort
|
||||
),
|
||||
columnItems = listOf(
|
||||
SimpleTableWidgetItem.Column.ClearContents,
|
||||
SimpleTableWidgetItem.Column.Color,
|
||||
SimpleTableWidgetItem.Column.Style,
|
||||
SimpleTableWidgetItem.Column.Delete,
|
||||
SimpleTableWidgetItem.Column.InsertLeft,
|
||||
SimpleTableWidgetItem.Column.InsertRight,
|
||||
SimpleTableWidgetItem.Column.MoveLeft,
|
||||
SimpleTableWidgetItem.Column.MoveRight,
|
||||
SimpleTableWidgetItem.Column.Sort,
|
||||
SimpleTableWidgetItem.Column.Duplicate
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}.catch { e ->
|
||||
Timber.e(e, "Error while processing simple table ")
|
||||
}
|
||||
|
||||
override suspend fun onSimpleTableEvent(event: SimpleTableWidgetEvent) {
|
||||
events.emit(event)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.anytypeio.anytype.presentation.editor.editor.table
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
|
||||
sealed interface SimpleTableWidgetEvent {
|
||||
data class onStart(val id: Id) : SimpleTableWidgetEvent
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.anytypeio.anytype.presentation.editor.editor.table
|
||||
|
||||
sealed class SimpleTableWidgetItem {
|
||||
|
||||
sealed class Cell : SimpleTableWidgetItem() {
|
||||
object ClearContents : Cell()
|
||||
object Color : Cell()
|
||||
object Style : Cell()
|
||||
object ClearStyle : Cell()
|
||||
}
|
||||
|
||||
sealed class Column : SimpleTableWidgetItem() {
|
||||
object InsertLeft : Column()
|
||||
object InsertRight : Column()
|
||||
object MoveLeft : Column()
|
||||
object MoveRight : Column()
|
||||
object Duplicate : Column()
|
||||
object Delete : Column()
|
||||
object ClearContents : Column()
|
||||
object Sort : Column()
|
||||
object Color : Column()
|
||||
object Style : Column()
|
||||
}
|
||||
|
||||
sealed class Row : SimpleTableWidgetItem() {
|
||||
object InsertAbove : Row()
|
||||
object InsertBelow : Row()
|
||||
object MoveUp : Row()
|
||||
object MoveDown : Row()
|
||||
object Duplicate : Row()
|
||||
object Delete : Row()
|
||||
object ClearContents : Row()
|
||||
object Sort : Row()
|
||||
object Color : Row()
|
||||
object Style : Row()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.anytypeio.anytype.presentation.editor.editor.table
|
||||
|
||||
sealed class SimpleTableWidgetState {
|
||||
|
||||
object Idle : SimpleTableWidgetState()
|
||||
|
||||
data class UpdateItems(
|
||||
val cellItems: List<SimpleTableWidgetItem>,
|
||||
val columnItems: List<SimpleTableWidgetItem>,
|
||||
val rowItems: List<SimpleTableWidgetItem>
|
||||
) : SimpleTableWidgetState() {
|
||||
companion object {
|
||||
fun empty() = UpdateItems(
|
||||
cellItems = emptyList(),
|
||||
columnItems = emptyList(),
|
||||
rowItems = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun init(): SimpleTableWidgetState = Idle
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.anytypeio.anytype.presentation.editor.editor.table
|
||||
|
||||
sealed class SimpleTableWidgetViewState {
|
||||
object Idle : SimpleTableWidgetViewState()
|
||||
data class Active(val state: SimpleTableWidgetState) : SimpleTableWidgetViewState()
|
||||
}
|
|
@ -741,6 +741,21 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
is Content.Table -> {
|
||||
isPreviousBlockMedia = false
|
||||
mCounter = 0
|
||||
result.add(
|
||||
table(
|
||||
mode = mode,
|
||||
block = block,
|
||||
focus = focus,
|
||||
indent = indent,
|
||||
details = details,
|
||||
selection = selection,
|
||||
blocks = this
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -942,7 +957,7 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun bulleted(
|
||||
fun bulleted(
|
||||
mode: EditorMode,
|
||||
block: Block,
|
||||
content: Content.Text,
|
||||
|
@ -1746,6 +1761,147 @@ class DefaultBlockViewRenderer @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun table(
|
||||
mode: EditorMode,
|
||||
block: Block,
|
||||
focus: Focus,
|
||||
indent: Int,
|
||||
details: Block.Details,
|
||||
selection: Set<Id>,
|
||||
blocks: Map<String, List<Block>>
|
||||
): BlockView.Table {
|
||||
|
||||
var cells: List<BlockView.Table.Cell> = emptyList()
|
||||
var columns: List<BlockView.Table.Column> = emptyList()
|
||||
var rowCount = 0
|
||||
|
||||
blocks.getValue(block.id).forEach { container ->
|
||||
val containerContent = container.content
|
||||
if (containerContent !is Content.Layout) return@forEach
|
||||
if (containerContent.type == Content.Layout.Type.TABLE_COLUMN) {
|
||||
columns = blocks.getValue(container.id).map { tableColumn(it) }
|
||||
}
|
||||
if (containerContent.type == Content.Layout.Type.TABLE_ROW) {
|
||||
val rows = blocks.getValue(container.id)
|
||||
rowCount = rows.size
|
||||
cells = tableCells(
|
||||
mode = mode,
|
||||
focus = focus,
|
||||
indent = indent,
|
||||
details = details,
|
||||
selection = selection,
|
||||
rows = rows,
|
||||
columns = columns,
|
||||
blocks = blocks
|
||||
)
|
||||
}
|
||||
}
|
||||
return BlockView.Table(
|
||||
id = block.id,
|
||||
columns = columns,
|
||||
cells = cells,
|
||||
rowCount = rowCount,
|
||||
isSelected = checkIfSelected(
|
||||
mode = mode,
|
||||
block = block,
|
||||
selection = selection
|
||||
),
|
||||
backgroundColor = block.backgroundColor
|
||||
)
|
||||
}
|
||||
|
||||
private fun tableCells(
|
||||
blocks: Map<String, List<Block>>,
|
||||
rows: List<Block>,
|
||||
columns: List<BlockView.Table.Column>,
|
||||
mode: EditorMode,
|
||||
focus: Focus,
|
||||
indent: Int,
|
||||
details: Block.Details,
|
||||
selection: Set<Id>
|
||||
): List<BlockView.Table.Cell> {
|
||||
val cells = mutableListOf<BlockView.Table.Cell>()
|
||||
columns.map { column ->
|
||||
rows.forEach { row ->
|
||||
val isHeader = (row.content as? Content.TableRow)?.isHeader ?: false
|
||||
val cellId = "${row.id}-${column.id}"
|
||||
val rowsChildren = blocks.getValue(row.id)
|
||||
val block = rowsChildren.firstOrNull { it.id == cellId }
|
||||
if (block != null) {
|
||||
val content = block.content
|
||||
check(content is Content.Text)
|
||||
{ Timber.e("Table row block content should be Text") }
|
||||
if (content.style == Content.Text.Style.P) {
|
||||
cells.add(
|
||||
BlockView.Table.Cell.Text(
|
||||
rowId = row.id,
|
||||
columnId = column.id,
|
||||
settings = buildCellSettings(
|
||||
cellId = cellId,
|
||||
selection = selection,
|
||||
isHeader = isHeader
|
||||
),
|
||||
block = paragraph(
|
||||
mode = mode,
|
||||
block = block,
|
||||
content = content,
|
||||
focus = focus,
|
||||
indent = indent,
|
||||
details = details,
|
||||
selection = selection,
|
||||
schema = emptyList()
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Timber.w("Block should be paragraph")
|
||||
}
|
||||
} else {
|
||||
cells.add(
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = row.id,
|
||||
columnId = column.id,
|
||||
settings = buildCellSettings(
|
||||
cellId = cellId,
|
||||
selection = selection,
|
||||
isHeader = isHeader
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
cells.add(BlockView.Table.Cell.Space)
|
||||
return cells
|
||||
}
|
||||
|
||||
private fun buildCellSettings(
|
||||
cellId: Id,
|
||||
selection: Set<Id>,
|
||||
isHeader: Boolean
|
||||
): BlockView.Table.CellSettings {
|
||||
return if (selection.contains(cellId)) {
|
||||
BlockView.Table.CellSettings(
|
||||
left = true,
|
||||
top = true,
|
||||
right = true,
|
||||
bottom = true,
|
||||
isHeader = isHeader
|
||||
)
|
||||
} else {
|
||||
BlockView.Table.CellSettings(
|
||||
isHeader = isHeader
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tableColumn(block: Block): BlockView.Table.Column {
|
||||
return BlockView.Table.Column(
|
||||
id = block.id,
|
||||
backgroundColor = block.backgroundColor
|
||||
)
|
||||
}
|
||||
|
||||
private fun relation(
|
||||
ctx: Id,
|
||||
block: Block,
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package com.anytypeio.anytype.presentation.objects.block
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.domain.block.interactor.UpdateText
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.Markup
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.editor.updateText
|
||||
import com.anytypeio.anytype.presentation.editor.model.TextUpdate
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class SetBlockTextValueViewModel(
|
||||
private val updateText: UpdateText,
|
||||
private val storage: Editor.Storage
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val doc: List<BlockView> get() = storage.views.current()
|
||||
val state = MutableStateFlow<ViewState>(ViewState.Loading)
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
fun onStart(tableId: Id, blockId: Id) {
|
||||
jobs += viewModelScope.launch {
|
||||
storage.views.stream().mapNotNull { views ->
|
||||
val table = views.firstOrNull { it.id == tableId }
|
||||
if (table != null && table is BlockView.Table) {
|
||||
val block = table.cells.firstOrNull { cell ->
|
||||
when (cell) {
|
||||
is BlockView.Table.Cell.Empty -> cell.getId() == blockId
|
||||
is BlockView.Table.Cell.Text -> cell.getId() == blockId
|
||||
BlockView.Table.Cell.Space -> false
|
||||
}
|
||||
}
|
||||
if (block is BlockView.Table.Cell.Text) {
|
||||
block.block.copy(inputAction = BlockView.InputAction.Done)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.collectLatest { block ->
|
||||
state.value = ViewState.Success(data = listOf(block))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
jobs.apply {
|
||||
forEach { it.cancel() }
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun onKeyboardDoneKeyClicked(
|
||||
ctx: Id,
|
||||
tableId: String,
|
||||
targetId: String,
|
||||
text: String,
|
||||
marks: List<Markup.Mark>,
|
||||
markup: List<Block.Content.Text.Mark>
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
storage.views.update(doc.map { view ->
|
||||
if (view.id == tableId && view is BlockView.Table) {
|
||||
val updated = view.cells.map { it ->
|
||||
if (it is BlockView.Table.Cell.Text && it.block.id == targetId) {
|
||||
it.copy(block = it.block.copy(text = text, marks = marks))
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
view.copy(cells = updated)
|
||||
} else {
|
||||
view
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val update = TextUpdate.Default(target = targetId, text = text, markup = markup)
|
||||
|
||||
val updated = storage.document.get().map { block ->
|
||||
if (block.id == update.target) {
|
||||
block.updateText(update)
|
||||
} else
|
||||
block
|
||||
}
|
||||
storage.document.update(updated)
|
||||
|
||||
viewModelScope.launch {
|
||||
updateText(
|
||||
UpdateText.Params(
|
||||
context = ctx,
|
||||
target = targetId,
|
||||
text = text,
|
||||
marks = markup
|
||||
)
|
||||
).process(
|
||||
failure = { e ->
|
||||
Timber.e(e, "Error while updating block text value")
|
||||
_toasts.emit("Error while updating block text value ${e.localizedMessage}")
|
||||
},
|
||||
success = { state.value = ViewState.Exit }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickListener(clicked: ListenerType) {
|
||||
if (clicked is ListenerType.Mention) {
|
||||
state.value = ViewState.OnMention(clicked.target)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val updateText: UpdateText,
|
||||
private val storage: Editor.Storage
|
||||
) :
|
||||
ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return SetBlockTextValueViewModel(
|
||||
updateText = updateText,
|
||||
storage = storage
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ViewState {
|
||||
data class Success(val data: List<BlockView>) : ViewState()
|
||||
data class OnMention(val targetId: String) : ViewState()
|
||||
object Exit : ViewState()
|
||||
object Loading : ViewState()
|
||||
}
|
||||
}
|
|
@ -70,6 +70,8 @@ import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
|||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.domain.table.CreateTable
|
||||
import com.anytypeio.anytype.domain.table.FillTableRow
|
||||
import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage
|
||||
import com.anytypeio.anytype.domain.unsplash.UnsplashRepository
|
||||
import com.anytypeio.anytype.presentation.BuildConfig
|
||||
|
@ -93,6 +95,8 @@ import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMa
|
|||
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashItem
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarState
|
||||
import com.anytypeio.anytype.presentation.editor.editor.styling.StylingEvent
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.DefaultSimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.editor.template.DefaultEditorTemplateDelegate
|
||||
|
@ -308,11 +312,19 @@ open class EditorViewModelTest {
|
|||
@Mock
|
||||
lateinit var applyTemplate: ApplyTemplate
|
||||
|
||||
@Mock
|
||||
lateinit var fillTableRow: FillTableRow
|
||||
|
||||
private lateinit var editorTemplateDelegate: EditorTemplateDelegate
|
||||
|
||||
private lateinit var simpleTableDelegate: SimpleTableDelegate
|
||||
|
||||
@Mock
|
||||
lateinit var createNewObject: CreateNewObject
|
||||
|
||||
@Mock
|
||||
lateinit var createTable: CreateTable
|
||||
|
||||
private lateinit var updateDetail: UpdateDetail
|
||||
|
||||
lateinit var vm: EditorViewModel
|
||||
|
@ -354,6 +366,7 @@ open class EditorViewModelTest {
|
|||
getTemplates = getTemplates,
|
||||
applyTemplate = applyTemplate
|
||||
)
|
||||
simpleTableDelegate = DefaultSimpleTableDelegate()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -3925,6 +3938,8 @@ open class EditorViewModelTest {
|
|||
turnIntoStyle = turnIntoStyle,
|
||||
updateBlocksMark = updateBlocksMark,
|
||||
setObjectType = setObjectType,
|
||||
createTable = createTable,
|
||||
fillTableRow = fillTableRow
|
||||
),
|
||||
dispatcher = Dispatcher.Default(),
|
||||
detailModificationManager = InternalDetailModificationManager(storage.details),
|
||||
|
@ -3940,7 +3955,8 @@ open class EditorViewModelTest {
|
|||
setDocImageIcon = setDocImageIcon,
|
||||
delegator = delegator,
|
||||
templateDelegate = editorTemplateDelegate,
|
||||
createNewObject = createNewObject
|
||||
createNewObject = createNewObject,
|
||||
simpleTableDelegate = simpleTableDelegate
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock
|
|||
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
|
||||
import com.anytypeio.anytype.domain.sets.FindObjectSetForType
|
||||
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
|
||||
import com.anytypeio.anytype.domain.table.CreateTable
|
||||
import com.anytypeio.anytype.domain.table.FillTableRow
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage
|
||||
import com.anytypeio.anytype.domain.unsplash.UnsplashRepository
|
||||
|
@ -72,6 +74,8 @@ import com.anytypeio.anytype.presentation.editor.Editor
|
|||
import com.anytypeio.anytype.presentation.editor.EditorViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
|
||||
import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMatcher
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.DefaultSimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate
|
||||
|
@ -252,6 +256,14 @@ open class EditorPresentationTestSetup {
|
|||
@Mock
|
||||
lateinit var getTemplates: GetTemplates
|
||||
|
||||
@Mock
|
||||
lateinit var createTable: CreateTable
|
||||
|
||||
@Mock
|
||||
lateinit var fillTableRow: FillTableRow
|
||||
|
||||
lateinit var simpleTableDelegate: SimpleTableDelegate
|
||||
|
||||
protected val builder: UrlBuilder get() = UrlBuilder(gateway)
|
||||
|
||||
private lateinit var updateDetail: UpdateDetail
|
||||
|
@ -274,6 +286,7 @@ open class EditorPresentationTestSetup {
|
|||
setDocCoverImage = SetDocCoverImage(repo)
|
||||
setDocImageIcon = SetDocumentImageIcon(repo)
|
||||
downloadUnsplashImage = DownloadUnsplashImage(unsplashRepo)
|
||||
simpleTableDelegate = DefaultSimpleTableDelegate()
|
||||
|
||||
orchestrator = Orchestrator(
|
||||
createBlock = createBlock,
|
||||
|
@ -312,7 +325,9 @@ open class EditorPresentationTestSetup {
|
|||
setRelationKey = setRelationKey,
|
||||
turnIntoStyle = turnIntoStyle,
|
||||
updateBlocksMark = updateBlocksMark,
|
||||
setObjectType = setObjectType
|
||||
setObjectType = setObjectType,
|
||||
createTable = createTable,
|
||||
fillTableRow = fillTableRow
|
||||
)
|
||||
|
||||
return EditorViewModel(
|
||||
|
@ -350,7 +365,8 @@ open class EditorPresentationTestSetup {
|
|||
setDocImageIcon = setDocImageIcon,
|
||||
downloadUnsplashImage = downloadUnsplashImage,
|
||||
templateDelegate = editorTemplateDelegate,
|
||||
createNewObject = createNewObject
|
||||
createNewObject = createNewObject,
|
||||
simpleTableDelegate = simpleTableDelegate
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -601,7 +601,8 @@ class EditorSlashWidgetClicksTest: EditorPresentationTestSetup() {
|
|||
SlashItem.Subheader.OtherWithBack,
|
||||
SlashItem.Other.Line,
|
||||
SlashItem.Other.Dots,
|
||||
SlashItem.Other.TOC
|
||||
SlashItem.Other.TOC,
|
||||
SlashItem.Other.Table()
|
||||
)
|
||||
|
||||
val expected = SlashWidgetState.UpdateItems(
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Build
|
|||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Relation
|
||||
import com.anytypeio.anytype.domain.table.CreateTable
|
||||
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_HEADER_TWO
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_NUMBERED
|
||||
|
@ -1243,7 +1244,8 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
|
|||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.Line,
|
||||
SlashItem.Other.Dots,
|
||||
SlashItem.Other.TOC
|
||||
SlashItem.Other.TOC,
|
||||
SlashItem.Other.Table()
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
@ -1551,7 +1553,7 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should return table of contents on table filter`() {
|
||||
fun `should return table of contents and simple table on table filter`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
|
@ -1582,8 +1584,225 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
|
|||
|
||||
val expectedItems = listOf(
|
||||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.TOC
|
||||
SlashItem.Other.TOC,
|
||||
SlashItem.Other.Table()
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash item should have 9 rows and 7 columns`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
val fields = Block.Fields.empty()
|
||||
val customDetails = Block.Details(mapOf(root to fields))
|
||||
|
||||
stubInterceptEvents()
|
||||
stubGetObjectTypes(listOf())
|
||||
stubOpenDocument(doc, customDetails)
|
||||
|
||||
val vm = buildViewModel()
|
||||
vm.onStart(root)
|
||||
vm.apply {
|
||||
onBlockFocusChanged(a.id, true)
|
||||
onSlashTextWatcherEvent(SlashEvent.Start(100, 0))
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/", HOLDER_NUMBERED))
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/table9x7", HOLDER_NUMBERED))
|
||||
|
||||
val state = vm.controlPanelViewState.value
|
||||
val command = state?.slashWidget?.widgetState as SlashWidgetState.UpdateItems
|
||||
|
||||
val expectedItems = listOf(
|
||||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.Table(
|
||||
rowCount = 9,
|
||||
columnCount = 7
|
||||
)
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash item should have 19 rows and default max columns`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
val fields = Block.Fields.empty()
|
||||
val customDetails = Block.Details(mapOf(root to fields))
|
||||
|
||||
stubInterceptEvents()
|
||||
stubGetObjectTypes(listOf())
|
||||
stubOpenDocument(doc, customDetails)
|
||||
|
||||
val vm = buildViewModel()
|
||||
vm.onStart(root)
|
||||
vm.apply {
|
||||
onBlockFocusChanged(a.id, true)
|
||||
onSlashTextWatcherEvent(SlashEvent.Start(100, 0))
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/", HOLDER_NUMBERED))
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/table19x78t", HOLDER_NUMBERED))
|
||||
|
||||
val state = vm.controlPanelViewState.value
|
||||
val command = state?.slashWidget?.widgetState as SlashWidgetState.UpdateItems
|
||||
|
||||
val expectedItems = listOf(
|
||||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.Table(
|
||||
rowCount = 19,
|
||||
columnCount = 25
|
||||
)
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash item should have default max rows and default max columns`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
val fields = Block.Fields.empty()
|
||||
val customDetails = Block.Details(mapOf(root to fields))
|
||||
|
||||
stubInterceptEvents()
|
||||
stubGetObjectTypes(listOf())
|
||||
stubOpenDocument(doc, customDetails)
|
||||
|
||||
val vm = buildViewModel()
|
||||
vm.onStart(root)
|
||||
vm.apply {
|
||||
onBlockFocusChanged(a.id, true)
|
||||
onSlashTextWatcherEvent(SlashEvent.Start(100, 0))
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/", HOLDER_NUMBERED))
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/table199x78", HOLDER_NUMBERED))
|
||||
|
||||
val state = vm.controlPanelViewState.value
|
||||
val command = state?.slashWidget?.widgetState as SlashWidgetState.UpdateItems
|
||||
|
||||
val expectedItems = listOf(
|
||||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.Table(
|
||||
rowCount = CreateTable.DEFAULT_MAX_ROW_COUNT,
|
||||
columnCount = CreateTable.DEFAULT_MAX_COLUMN_COUNT
|
||||
)
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash item should have 5 rows and default min columns`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
val fields = Block.Fields.empty()
|
||||
val customDetails = Block.Details(mapOf(root to fields))
|
||||
|
||||
stubInterceptEvents()
|
||||
stubGetObjectTypes(listOf())
|
||||
stubOpenDocument(doc, customDetails)
|
||||
|
||||
val vm = buildViewModel()
|
||||
vm.onStart(root)
|
||||
vm.apply {
|
||||
onBlockFocusChanged(a.id, true)
|
||||
onSlashTextWatcherEvent(SlashEvent.Start(100, 0))
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/", HOLDER_NUMBERED))
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/table5", HOLDER_NUMBERED))
|
||||
|
||||
val state = vm.controlPanelViewState.value
|
||||
val command = state?.slashWidget?.widgetState as SlashWidgetState.UpdateItems
|
||||
|
||||
val expectedItems = listOf(
|
||||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.Table(
|
||||
rowCount = 5,
|
||||
columnCount = CreateTable.DEFAULT_COLUMN_COUNT
|
||||
)
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash item should have default max rows and default min columns`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
val fields = Block.Fields.empty()
|
||||
val customDetails = Block.Details(mapOf(root to fields))
|
||||
|
||||
stubInterceptEvents()
|
||||
stubGetObjectTypes(listOf())
|
||||
stubOpenDocument(doc, customDetails)
|
||||
|
||||
val vm = buildViewModel()
|
||||
vm.onStart(root)
|
||||
vm.apply {
|
||||
onBlockFocusChanged(a.id, true)
|
||||
onSlashTextWatcherEvent(SlashEvent.Start(100, 0))
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/", HOLDER_NUMBERED))
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/table26", HOLDER_NUMBERED))
|
||||
|
||||
val state = vm.controlPanelViewState.value
|
||||
val command = state?.slashWidget?.widgetState as SlashWidgetState.UpdateItems
|
||||
|
||||
val expectedItems = listOf(
|
||||
SlashItem.Subheader.Other,
|
||||
SlashItem.Other.Table(
|
||||
rowCount = CreateTable.DEFAULT_MAX_ROW_COUNT,
|
||||
columnCount = CreateTable.DEFAULT_COLUMN_COUNT
|
||||
)
|
||||
)
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slash item tables should not be present`() {
|
||||
// SETUP
|
||||
val doc = MockTypicalDocumentFactory.page(root)
|
||||
val a = MockTypicalDocumentFactory.a
|
||||
val fields = Block.Fields.empty()
|
||||
val customDetails = Block.Details(mapOf(root to fields))
|
||||
|
||||
stubInterceptEvents()
|
||||
stubGetObjectTypes(listOf())
|
||||
stubOpenDocument(doc, customDetails)
|
||||
|
||||
val vm = buildViewModel()
|
||||
vm.onStart(root)
|
||||
vm.apply {
|
||||
onBlockFocusChanged(a.id, true)
|
||||
onSlashTextWatcherEvent(SlashEvent.Start(100, 0))
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/", HOLDER_NUMBERED))
|
||||
vm.onSlashTextWatcherEvent(SlashEvent.Filter("/tablee5x8", HOLDER_NUMBERED))
|
||||
|
||||
val state = vm.controlPanelViewState.value
|
||||
val command = state?.slashWidget?.widgetState as SlashWidgetState.UpdateItems
|
||||
|
||||
val expectedItems = emptyList<SlashItem>()
|
||||
assertEquals(expected = expectedItems, actual = command.otherItems)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,586 @@
|
|||
package com.anytypeio.anytype.presentation.editor.editor.table
|
||||
|
||||
import android.util.Log
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.StubBulleted
|
||||
import com.anytypeio.anytype.core_models.StubHeader
|
||||
import com.anytypeio.anytype.core_models.StubLayoutColumns
|
||||
import com.anytypeio.anytype.core_models.StubLayoutRows
|
||||
import com.anytypeio.anytype.core_models.StubNumbered
|
||||
import com.anytypeio.anytype.core_models.StubParagraph
|
||||
import com.anytypeio.anytype.core_models.StubTable
|
||||
import com.anytypeio.anytype.core_models.StubTableColumn
|
||||
import com.anytypeio.anytype.core_models.StubTableRow
|
||||
import com.anytypeio.anytype.core_models.StubTitle
|
||||
import com.anytypeio.anytype.core_models.ext.asMap
|
||||
import com.anytypeio.anytype.core_models.ext.content
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
import com.anytypeio.anytype.domain.config.Gateway
|
||||
import com.anytypeio.anytype.domain.editor.Editor
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
|
||||
import com.anytypeio.anytype.presentation.util.TXT
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.lachlanmckee.timberjunit.TimberTestRule
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TableBlockRendererTest {
|
||||
|
||||
class BlockViewRenderWrapper(
|
||||
private val blocks: Map<Id, List<Block>>,
|
||||
private val renderer: BlockViewRenderer,
|
||||
private val restrictions: List<ObjectRestriction> = emptyList()
|
||||
) : BlockViewRenderer by renderer {
|
||||
suspend fun render(
|
||||
root: Block,
|
||||
anchor: Id,
|
||||
focus: Editor.Focus,
|
||||
indent: Int,
|
||||
details: Block.Details
|
||||
): List<BlockView> = blocks.render(
|
||||
root = root,
|
||||
anchor = anchor,
|
||||
focus = focus,
|
||||
indent = indent,
|
||||
details = details,
|
||||
relations = emptyList(),
|
||||
restrictions = restrictions,
|
||||
selection = emptySet(),
|
||||
objectTypes = listOf()
|
||||
)
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val timberTestRule: TimberTestRule = TimberTestRule.builder()
|
||||
.minPriority(Log.DEBUG)
|
||||
.showThread(true)
|
||||
.showTimestamp(false)
|
||||
.onlyLogWhenTestFails(true)
|
||||
.build()
|
||||
|
||||
@Mock
|
||||
lateinit var toggleStateHolder: ToggleStateHolder
|
||||
|
||||
@Mock
|
||||
lateinit var gateway: Gateway
|
||||
|
||||
@Mock
|
||||
lateinit var coverImageHashProvider: CoverImageHashProvider
|
||||
|
||||
private lateinit var renderer: DefaultBlockViewRenderer
|
||||
|
||||
private lateinit var wrapper: BlockViewRenderWrapper
|
||||
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
renderer = DefaultBlockViewRenderer(
|
||||
urlBuilder = UrlBuilder(gateway),
|
||||
toggleStateHolder = toggleStateHolder,
|
||||
coverImageHashProvider = coverImageHashProvider
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return table block with columns, rows and text cells`() {
|
||||
|
||||
val blocksUpper = mutableListOf<Block>()
|
||||
val blocksDown = mutableListOf<Block>()
|
||||
|
||||
for (i in 1..5) {
|
||||
val blockU = StubBulleted()
|
||||
blocksUpper.add(blockU)
|
||||
val blockD = StubNumbered()
|
||||
blocksDown.add(blockD)
|
||||
}
|
||||
|
||||
val rowsSize = 6
|
||||
val columnsSize = 4
|
||||
|
||||
val mapRows = mutableMapOf<String, List<Block>>()
|
||||
val rows = mutableListOf<Block>()
|
||||
val columns = mutableListOf<Block>()
|
||||
|
||||
for (i in 1..rowsSize) {
|
||||
val rowId = "rowId$i"
|
||||
val cells = mutableListOf<Block>()
|
||||
for (j in 1..columnsSize) {
|
||||
val columnId = "columnId$j"
|
||||
val cellId = "$rowId-$columnId"
|
||||
val p = StubParagraph(id = cellId)
|
||||
cells.add(p)
|
||||
}
|
||||
val row = StubTableRow(id = rowId, children = cells.map { it.id })
|
||||
rows.add(row)
|
||||
mapRows[row.id] = cells
|
||||
}
|
||||
val layoutRow = StubLayoutRows(children = rows.map { it.id })
|
||||
|
||||
for (j in 1..columnsSize) {
|
||||
columns.add(StubTableColumn(id = "columnId$j"))
|
||||
}
|
||||
val layoutColumn = StubLayoutColumns(children = columns.map { it.id })
|
||||
|
||||
val table = StubTable(children = listOf(layoutColumn.id, layoutRow.id))
|
||||
|
||||
val title = StubTitle()
|
||||
val header = StubHeader(children = listOf(title.id))
|
||||
|
||||
val page = Block(
|
||||
id = "page",
|
||||
children = listOf(header.id) + blocksUpper.map { it.id } + listOf(table.id) + blocksDown.map { it.id },
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Smart()
|
||||
)
|
||||
|
||||
val l = mutableListOf<Block>()
|
||||
columns.forEach { l.add(it) }
|
||||
l.add(layoutRow)
|
||||
layoutRow.children.forEach { child ->
|
||||
val row = rows.first { it.id == child }
|
||||
l.add(row)
|
||||
val cells = mapRows[row.id]
|
||||
cells?.forEach { l.add(it) }
|
||||
}
|
||||
|
||||
val blocks =
|
||||
listOf(page, header) + blocksUpper + listOf(table, title, layoutColumn) + l + blocksDown
|
||||
|
||||
/**
|
||||
* Should be 25 blocks + 6 rows + 4 columns + layoutRow + layoutColumn + table + title + header + 10 text blocks + page
|
||||
*/
|
||||
assertEquals(50, blocks.size)
|
||||
|
||||
val details = mapOf(page.id to Block.Fields.empty())
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
wrapper = BlockViewRenderWrapper(
|
||||
blocks = map,
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
focus = Editor.Focus.empty(),
|
||||
indent = 0,
|
||||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val cells = mutableListOf<BlockView.Table.Cell>()
|
||||
columns.forEachIndexed { columnIndex, column ->
|
||||
rows.forEachIndexed { rowIndex, row ->
|
||||
val cell = mapRows[row.id]?.get(columnIndex)!!
|
||||
val p = BlockView.Text.Paragraph(
|
||||
id = cell.id,
|
||||
text = cell.content.asText().text
|
||||
)
|
||||
cells.add(
|
||||
BlockView.Table.Cell.Text(
|
||||
block = p,
|
||||
rowId = row.id,
|
||||
columnId = column.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
cells.add(BlockView.Table.Cell.Space)
|
||||
|
||||
val columnViews = mutableListOf<BlockView.Table.Column>()
|
||||
|
||||
columns.forEach { column ->
|
||||
columnViews.add(
|
||||
BlockView.Table.Column(
|
||||
id = column.id,
|
||||
backgroundColor = column.backgroundColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Basic(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
)
|
||||
) + blocksUpper.map { block: Block ->
|
||||
BlockView.Text.Bulleted(
|
||||
id = block.id,
|
||||
text = block.content<TXT>().text
|
||||
)
|
||||
} + listOf(
|
||||
BlockView.Table(
|
||||
id = table.id,
|
||||
cells = cells,
|
||||
columns = columnViews,
|
||||
rowCount = rowsSize,
|
||||
isSelected = false
|
||||
)
|
||||
) + blocksDown.mapIndexed { idx, block ->
|
||||
BlockView.Text.Numbered(
|
||||
id = block.id,
|
||||
text = block.content<TXT>().text,
|
||||
number = idx.inc()
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return table block with columns, rows and empty cells`() {
|
||||
|
||||
val blocksUpper = mutableListOf<Block>()
|
||||
val blocksDown = mutableListOf<Block>()
|
||||
|
||||
for (i in 1..5) {
|
||||
val blockU = StubBulleted()
|
||||
blocksUpper.add(blockU)
|
||||
val blockD = StubNumbered()
|
||||
blocksDown.add(blockD)
|
||||
}
|
||||
|
||||
val rowsSize = 6
|
||||
val columnsSize = 4
|
||||
|
||||
val rows = mutableListOf<Block>()
|
||||
val columns = mutableListOf<Block>()
|
||||
|
||||
for (i in 1..rowsSize) {
|
||||
val rowId = "rowId$i"
|
||||
rows.add(StubTableRow(id = rowId))
|
||||
}
|
||||
val layoutRow = StubLayoutRows(children = rows.map { it.id })
|
||||
|
||||
for (j in 1..columnsSize) {
|
||||
columns.add(StubTableColumn(id = "columnId$j"))
|
||||
}
|
||||
val layoutColumn = StubLayoutColumns(children = columns.map { it.id })
|
||||
|
||||
val table = StubTable(children = listOf(layoutColumn.id, layoutRow.id))
|
||||
|
||||
val title = StubTitle()
|
||||
val header = StubHeader(children = listOf(title.id))
|
||||
|
||||
val page = Block(
|
||||
id = "page",
|
||||
children = listOf(header.id) + blocksUpper.map { it.id } + listOf(table.id) + blocksDown.map { it.id },
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Smart()
|
||||
)
|
||||
|
||||
val l = mutableListOf<Block>()
|
||||
columns.forEach { l.add(it) }
|
||||
l.add(layoutRow)
|
||||
layoutRow.children.forEach { child ->
|
||||
val row = rows.first { it.id == child }
|
||||
l.add(row)
|
||||
}
|
||||
|
||||
val blocks =
|
||||
listOf(page, header) + blocksUpper + listOf(table, title, layoutColumn) + l + blocksDown
|
||||
|
||||
/**
|
||||
* 6 rows + 4 columns + layoutRow + layoutColumn + table + title + header + 10 text blocks + page
|
||||
*/
|
||||
assertEquals(26, blocks.size)
|
||||
|
||||
val details = mapOf(page.id to Block.Fields.empty())
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
wrapper = BlockViewRenderWrapper(
|
||||
blocks = map,
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
focus = Editor.Focus.empty(),
|
||||
indent = 0,
|
||||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val cells = mutableListOf<BlockView.Table.Cell>()
|
||||
columns.forEachIndexed { columnIndex, column ->
|
||||
rows.forEachIndexed { rowIndex, row ->
|
||||
cells.add(
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = row.id,
|
||||
columnId = column.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
cells.add(BlockView.Table.Cell.Space)
|
||||
|
||||
val columnViews = mutableListOf<BlockView.Table.Column>()
|
||||
|
||||
columns.forEach { column ->
|
||||
columnViews.add(
|
||||
BlockView.Table.Column(
|
||||
id = column.id,
|
||||
backgroundColor = column.backgroundColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Basic(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
)
|
||||
) + blocksUpper.map { block: Block ->
|
||||
BlockView.Text.Bulleted(
|
||||
id = block.id,
|
||||
text = block.content<TXT>().text
|
||||
)
|
||||
} + listOf(
|
||||
BlockView.Table(
|
||||
id = table.id,
|
||||
cells = cells,
|
||||
columns = columnViews,
|
||||
rowCount = rowsSize,
|
||||
isSelected = false
|
||||
)
|
||||
) + blocksDown.mapIndexed { idx, block ->
|
||||
BlockView.Text.Numbered(
|
||||
id = block.id,
|
||||
text = block.content<TXT>().text,
|
||||
number = idx.inc()
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return table block with columns, rows and empty plus text cells`() {
|
||||
|
||||
val blocksUpper = mutableListOf<Block>()
|
||||
val blocksDown = mutableListOf<Block>()
|
||||
|
||||
for (i in 1..5) {
|
||||
val blockU = StubBulleted()
|
||||
blocksUpper.add(blockU)
|
||||
val blockD = StubNumbered()
|
||||
blocksDown.add(blockD)
|
||||
}
|
||||
|
||||
val rowsSize = 3
|
||||
val columnsSize = 4
|
||||
|
||||
val mapRows = mutableMapOf<String, List<Block>>()
|
||||
val rows = mutableListOf<Block>()
|
||||
val columns = mutableListOf<Block>()
|
||||
|
||||
val rowId1 = "rowId1"
|
||||
val rowId2 = "rowId2"
|
||||
val rowId3 = "rowId3"
|
||||
val columnId1 = "columnId1"
|
||||
val columnId2 = "columnId2"
|
||||
val columnId3 = "columnId3"
|
||||
val columnId4 = "columnId4"
|
||||
|
||||
val row1Block1 = StubParagraph(id = "$rowId1-$columnId2")
|
||||
val row1Block2 = StubParagraph(id = "$rowId1-$columnId4")
|
||||
val row2Block1 = StubParagraph(id = "$rowId2-$columnId1")
|
||||
val row2Block2 = StubParagraph(id = "$rowId2-$columnId2")
|
||||
val row2Block3 = StubParagraph(id = "$rowId2-$columnId4")
|
||||
|
||||
rows.apply {
|
||||
add(StubTableRow(rowId1, listOf(row1Block1.id, row1Block2.id)))
|
||||
add(StubTableRow(rowId2, listOf(row2Block1.id, row2Block2.id, row2Block3.id)))
|
||||
add(StubTableRow(rowId3))
|
||||
mapRows[rowId1] = listOf(row1Block1, row1Block2)
|
||||
mapRows[rowId2] = listOf(row2Block1, row2Block2, row2Block3)
|
||||
}
|
||||
|
||||
val layoutRow = StubLayoutRows(children = rows.map { it.id })
|
||||
|
||||
for (j in 1..columnsSize) {
|
||||
columns.add(StubTableColumn(id = "columnId$j"))
|
||||
}
|
||||
val layoutColumn = StubLayoutColumns(children = columns.map { it.id })
|
||||
|
||||
val table = StubTable(children = listOf(layoutColumn.id, layoutRow.id))
|
||||
|
||||
val title = StubTitle()
|
||||
val header = StubHeader(children = listOf(title.id))
|
||||
|
||||
val page = Block(
|
||||
id = "page",
|
||||
children = listOf(header.id) + blocksUpper.map { it.id } + listOf(table.id) + blocksDown.map { it.id },
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Smart()
|
||||
)
|
||||
|
||||
val l = mutableListOf<Block>()
|
||||
columns.forEach { l.add(it) }
|
||||
l.add(layoutRow)
|
||||
layoutRow.children.forEach { child ->
|
||||
val row = rows.first { it.id == child }
|
||||
l.add(row)
|
||||
val cells = mapRows[row.id]
|
||||
cells?.forEach { l.add(it) }
|
||||
}
|
||||
|
||||
val blocks =
|
||||
listOf(page, header) + blocksUpper + listOf(table, title, layoutColumn) + l + blocksDown
|
||||
|
||||
assertEquals(28, blocks.size)
|
||||
|
||||
val details = mapOf(page.id to Block.Fields.empty())
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
wrapper = BlockViewRenderWrapper(
|
||||
blocks = map,
|
||||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
focus = Editor.Focus.empty(),
|
||||
indent = 0,
|
||||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val cells =
|
||||
listOf(
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId1,
|
||||
columnId = columnId1
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
rowId = rowId2,
|
||||
columnId = columnId1,
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row2Block1.id,
|
||||
text = row2Block1.content.asText().text
|
||||
)
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId3,
|
||||
columnId = columnId1
|
||||
), //column1
|
||||
BlockView.Table.Cell.Text(
|
||||
rowId = rowId1,
|
||||
columnId = columnId2,
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row1Block1.id,
|
||||
text = row1Block1.content.asText().text
|
||||
)
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
rowId = rowId2,
|
||||
columnId = columnId2,
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row2Block2.id,
|
||||
text = row2Block2.content.asText().text
|
||||
)
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId3,
|
||||
columnId = columnId2
|
||||
),//column2
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId1,
|
||||
columnId = columnId3
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId2,
|
||||
columnId = columnId3
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId3,
|
||||
columnId = columnId3
|
||||
),//column3
|
||||
BlockView.Table.Cell.Text(
|
||||
rowId = rowId1,
|
||||
columnId = columnId4,
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row1Block2.id,
|
||||
text = row1Block2.content.asText().text
|
||||
)
|
||||
),
|
||||
BlockView.Table.Cell.Text(
|
||||
rowId = rowId2,
|
||||
columnId = columnId4,
|
||||
block = BlockView.Text.Paragraph(
|
||||
id = row2Block3.id,
|
||||
text = row2Block3.content.asText().text
|
||||
)
|
||||
),
|
||||
BlockView.Table.Cell.Empty(
|
||||
rowId = rowId3,
|
||||
columnId = columnId4
|
||||
),
|
||||
BlockView.Table.Cell.Space
|
||||
)
|
||||
|
||||
val columnViews = mutableListOf<BlockView.Table.Column>()
|
||||
|
||||
columns.forEach { column ->
|
||||
columnViews.add(
|
||||
BlockView.Table.Column(
|
||||
id = column.id,
|
||||
backgroundColor = column.backgroundColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Basic(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
)
|
||||
) + blocksUpper.map { block: Block ->
|
||||
BlockView.Text.Bulleted(
|
||||
id = block.id,
|
||||
text = block.content<TXT>().text
|
||||
)
|
||||
} + listOf(
|
||||
BlockView.Table(
|
||||
id = table.id,
|
||||
cells = cells,
|
||||
columns = columnViews,
|
||||
rowCount = rowsSize,
|
||||
isSelected = false
|
||||
)
|
||||
) + blocksDown.mapIndexed { idx, block ->
|
||||
BlockView.Text.Numbered(
|
||||
id = block.id,
|
||||
text = block.content<TXT>().text,
|
||||
number = idx.inc()
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
}
|
|
@ -4,9 +4,10 @@ import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextConten
|
|||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
|
||||
fun StubHeader(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
children: List<Id> = emptyList()
|
||||
): Block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
id = id,
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
|
@ -15,9 +16,10 @@ fun StubHeader(
|
|||
)
|
||||
|
||||
fun StubTitle(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
text: String = MockDataFactory.randomString()
|
||||
): Block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
id = id,
|
||||
content = StubTextContent(
|
||||
text = text,
|
||||
style = Block.Content.Text.Style.TITLE
|
||||
|
@ -107,12 +109,13 @@ fun StubFile(
|
|||
)
|
||||
|
||||
fun StubBulleted(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
text: String = MockDataFactory.randomString(),
|
||||
children: List<Id> = emptyList(),
|
||||
marks: List<Block.Content.Text.Mark> = emptyList(),
|
||||
isChecked: Boolean = MockDataFactory.randomBoolean()
|
||||
): Block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
id = id,
|
||||
content = StubTextContent(
|
||||
text = text,
|
||||
style = Block.Content.Text.Style.BULLET,
|
||||
|
@ -139,11 +142,12 @@ fun StubToggle(
|
|||
)
|
||||
|
||||
fun StubNumbered(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
text: String = MockDataFactory.randomString(),
|
||||
children: List<Id> = emptyList(),
|
||||
marks: List<Block.Content.Text.Mark> = emptyList()
|
||||
): Block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
id = id,
|
||||
content = StubTextContent(
|
||||
text = text,
|
||||
style = Block.Content.Text.Style.NUMBERED,
|
||||
|
@ -230,4 +234,58 @@ fun StubSmartBlock(
|
|||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Smart()
|
||||
)
|
||||
|
||||
fun StubTable(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
children: List<Id> = emptyList(),
|
||||
background: String? = null
|
||||
): Block = Block(
|
||||
id = id,
|
||||
content = Block.Content.Table,
|
||||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
backgroundColor = background
|
||||
)
|
||||
|
||||
fun StubLayoutRows(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
children: List<Id> = emptyList(),
|
||||
): Block = Block(
|
||||
id = id,
|
||||
content = Block.Content.Layout(type = Block.Content.Layout.Type.TABLE_ROW),
|
||||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
|
||||
fun StubLayoutColumns(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
children: List<Id> = emptyList(),
|
||||
): Block = Block(
|
||||
id = id,
|
||||
content = Block.Content.Layout(type = Block.Content.Layout.Type.TABLE_COLUMN),
|
||||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
|
||||
fun StubTableRow(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
children: List<Id> = emptyList(),
|
||||
): Block = Block(
|
||||
id = id,
|
||||
content = Block.Content.TableRow(false),
|
||||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
)
|
||||
|
||||
fun StubTableColumn(
|
||||
id: Id = MockDataFactory.randomUuid(),
|
||||
children: List<Id> = emptyList(),
|
||||
background: String? = null
|
||||
): Block = Block(
|
||||
id = id,
|
||||
content = Block.Content.TableColumn,
|
||||
children = children,
|
||||
fields = Block.Fields.empty(),
|
||||
backgroundColor = background
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue