From b30d60f4dfe6cf74cbb60b02477c9c0cad37f821 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Wed, 9 Nov 2022 10:47:09 +0100 Subject: [PATCH] DROID-122 Editor | Enhancement | Simple tables, cell menu, cells selecting (#2696) * DROID-132 cell selection state + logic + tests * DROID-132 added cell border, cell rect classes * DROID-132 refactoring * DROID-132 refactoring * DROID-132 cell selection decoration * DROID-132 remove legacy * DROID-132 table block holder, add selection decoration * DROID-132 delete legacy clicks * DROID-132 cell model update * DROID-132 simple table widget events * DROID-132 editor table mode * DROID-132 table cell extension * DROID-132 legacy classes * DROID-132 delete legacy * DROID-132 update cell clicks * DROID-132 cells extensions * DROID-132 update item decorations * DROID-132 cell selection top toolbar * DROID-132 select cells, show widget * DROID-132 clear content command * DROID-132 clear content use case * DROID-132 menu item click * DROID-132 table delegate * DROID-132 fixes * DROID-132 refactoring * DROID-132 fix hide simple widget event * DROID-132 refactoring * DROID-132 test fixes * DROID-132 fix tests * DROID-132 turn off buttons * DROID-122 delete legacy delegate * DROID-122 table block diff util * DROID-122 add selected cells to table view model * DROID-122 rename param * DROID-122 add selection state to table block holder * DROID-122 delete * DROID-122 mapping * DROID-122 rename funcs * DROID-122 fix tests * DROID-122 update selection logic * DROID-122 pr fix * DROID-122 code style * DROID-122 pr fixes Co-authored-by: konstantiniiv --- .../features/editor/base/EditorTestSetup.kt | 11 +- .../anytypeio/anytype/di/feature/EditorDI.kt | 22 +- .../anytype/ui/editor/EditorFragment.kt | 68 +++- app/src/main/res/layout/fragment_editor.xml | 10 + .../features/editor/BlockViewDiffUtil.kt | 12 + .../features/table/TableBlockAdapter.kt | 11 +- .../table/TableEditableCellsAdapter.kt | 12 +- .../table/holders/TableBlockHolder.kt | 41 ++ .../layout/TableCellSelectionDecoration.kt | 82 ++++ .../toolbar/MultiSelectTopToolbarWidget.kt | 53 +++ .../toolbar/table/SimpleTableSettingWidget.kt | 32 +- .../toolbar/table/SimpleTableWidgetAdapter.kt | 8 +- .../src/main/res/drawable/cell_top_border.xml | 7 + core-ui/src/main/res/values/strings.xml | 2 + .../core_ui/BlockViewSearchTextTest.kt | 112 ++++-- .../anytype/core_ui/BlockViewStubs.kt | 27 +- .../features/editor/table/TableBlockTest.kt | 36 +- .../auth/repo/block/BlockDataRepository.kt | 4 + .../data/auth/repo/block/BlockDataStore.kt | 2 + .../data/auth/repo/block/BlockRemote.kt | 2 + .../auth/repo/block/BlockRemoteDataStore.kt | 4 + .../block/interactor/ClearBlockContent.kt | 24 ++ .../domain/block/repo/BlockRepository.kt | 2 + .../middleware/block/BlockMiddleware.kt | 4 + .../middleware/interactor/Middleware.kt | 15 + .../middleware/service/MiddlewareService.kt | 4 + .../MiddlewareServiceImplementation.kt | 13 + .../editor/ControlPanelMachine.kt | 86 +++- .../anytype/presentation/editor/Editor.kt | 5 + .../presentation/editor/EditorViewModel.kt | 207 +++++++--- .../editor/EditorViewModelFactory.kt | 3 - .../presentation/editor/editor/Intent.kt | 5 + .../editor/editor/Orchestrator.kt | 15 +- .../editor/control/ControlPanelState.kt | 43 +- .../editor/editor/listener/ListenerType.kt | 6 +- .../editor/editor/model/BlockView.kt | 31 +- .../editor/table/SimpleTableDelegate.kt | 63 --- .../editor/table/SimpleTableWidgetEvent.kt | 7 - .../editor/table/SimpleTableWidgetState.kt | 24 -- .../table/SimpleTableWidgetViewState.kt | 6 - .../editor/render/DefaultBlockViewRenderer.kt | 12 +- .../editor/selection/TableCellExt.kt | 378 ++++++++++++++++++ .../selection/TableCellsSelectionState.kt | 27 ++ .../editor/EditorViewModelTest.kt | 12 +- .../editor/EditorPresentationTestSetup.kt | 12 +- .../ext/TableCellsSelectionStateTest.kt | 369 +++++++++++++++++ .../editor/table/EditorTableBlockTest.kt | 69 +++- .../editor/table/TableBlockRendererTest.kt | 75 +++- 48 files changed, 1735 insertions(+), 340 deletions(-) create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/layout/TableCellSelectionDecoration.kt create mode 100644 core-ui/src/main/res/drawable/cell_top_border.xml create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/block/interactor/ClearBlockContent.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableDelegate.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetEvent.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetState.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetViewState.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellsSelectionState.kt create mode 100644 presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/ext/TableCellsSelectionStateTest.kt diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt index 52c85cc7c2..ac739d5528 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result import com.anytypeio.anytype.domain.block.UpdateDivider +import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.DuplicateBlock import com.anytypeio.anytype.domain.block.interactor.MergeBlocks @@ -88,7 +89,6 @@ 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 @@ -190,6 +190,7 @@ open class EditorTestSetup { lateinit var turnIntoStyle: TurnIntoStyle lateinit var setObjectType: SetObjectType lateinit var objectToSet: ConvertObjectToSet + lateinit var clearBlockContent: ClearBlockContent lateinit var getDefaultEditorType: GetDefaultEditorType @@ -243,9 +244,6 @@ open class EditorTestSetup { @Mock lateinit var fillTableRow: FillTableRow - @Mock - lateinit var simpleTableDelegate: SimpleTableDelegate - val root: String = "rootId123" private val urlBuilder by lazy { @@ -289,6 +287,7 @@ open class EditorTestSetup { getSearchObjects = SearchObjects(repo) interceptThreadStatus = InterceptThreadStatus(channel = threadStatusChannel) downloadUnsplashImage = DownloadUnsplashImage(unsplashRepository) + clearBlockContent = ClearBlockContent(repo) downloadFile = DownloadFile( downloader = mock(), context = Dispatchers.Main @@ -388,7 +387,8 @@ open class EditorTestSetup { setObjectType = setObjectType, createBookmarkBlock = createBookmarkBlock, createTable = createTable, - fillTableRow = fillTableRow + fillTableRow = fillTableRow, + clearBlockContent = clearBlockContent ), createNewDocument = createNewDocument, interceptThreadStatus = interceptThreadStatus, @@ -409,7 +409,6 @@ open class EditorTestSetup { setDocImageIcon = setDocImageIcon, editorTemplateDelegate = editorTemplateDelegate, createNewObject = createNewObject, - simpleTablesDelegate = simpleTableDelegate, objectToSet = objectToSet ) } diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt index 77852c86bb..c50c113a9e 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt @@ -18,6 +18,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.UpdateDivider +import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.DuplicateBlock import com.anytypeio.anytype.domain.block.interactor.MergeBlocks @@ -93,8 +94,6 @@ 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 @@ -224,7 +223,6 @@ object EditorSessionModule { setDocImageIcon: SetDocumentImageIcon, editorTemplateDelegate: EditorTemplateDelegate, createNewObject: CreateNewObject, - simpleTableDelegate: SimpleTableDelegate, objectToSet: ConvertObjectToSet ): EditorViewModelFactory = EditorViewModelFactory( openPage = openPage, @@ -257,7 +255,6 @@ object EditorSessionModule { setDocImageIcon = setDocImageIcon, editorTemplateDelegate = editorTemplateDelegate, createNewObject = createNewObject, - simpleTablesDelegate = simpleTableDelegate, objectToSet = objectToSet ) @@ -285,12 +282,6 @@ object EditorSessionModule { applyTemplate = applyTemplate ) - @JvmStatic - @Provides - @PerScreen - fun provideSimpleTableDelegate( - ): SimpleTableDelegate = DefaultSimpleTableDelegate() - @JvmStatic @Provides fun provideDefaultBlockViewRenderer( @@ -366,7 +357,8 @@ object EditorSessionModule { setRelationKey: SetRelationKey, analytics: Analytics, updateBlocksMark: UpdateBlocksMark, - middlewareShareDownloader: MiddlewareShareDownloader + middlewareShareDownloader: MiddlewareShareDownloader, + clearBlockContent: ClearBlockContent ): Orchestrator = Orchestrator( stores = storage, createBlock = createBlock, @@ -408,6 +400,7 @@ object EditorSessionModule { setObjectType = setObjectType, createTable = createTable, fillTableRow = fillTableRow, + clearBlockContent = clearBlockContent ) } @@ -1000,6 +993,13 @@ object EditorUseCaseModule { uriFileProvider = fileProvider ) + @JvmStatic + @Provides + @PerScreen + fun provideBlockListClearContent( + repo: BlockRepository + ): ClearBlockContent = ClearBlockContent(repo) + @Module interface Bindings { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index b15bd896a0..8a5aec053c 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -109,7 +109,6 @@ 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 @@ -481,20 +480,6 @@ open class EditorFragment : NavigationFragment(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() @@ -615,6 +600,13 @@ open class EditorFragment : NavigationFragment(R.layout.f .onEach { vm.onExitMultiSelectModeClicked() } .launchIn(lifecycleScope) + binding.cellSelectionTopToolbar + .doneButton + .clicks() + .throttleFirst() + .onEach { vm.onCellsSelectionDoneClick() } + .launchIn(lifecycleScope) + binding.bottomToolbar .homeClicks() .onEach { vm.onHomeButtonClicked() } @@ -677,6 +669,10 @@ open class EditorFragment : NavigationFragment(R.layout.f } } + binding.simpleTableWidget.setListener { + vm.onSimpleTableWidgetItemClicked(it) + } + binding.undoRedoToolbar.undo.clicks() .throttleFirst() .onEach { @@ -755,6 +751,8 @@ open class EditorFragment : NavigationFragment(R.layout.f BottomSheetBehavior.STATE_HIDDEN BottomSheetBehavior.from(binding.typeHasTemplateToolbar).state = BottomSheetBehavior.STATE_HIDDEN + BottomSheetBehavior.from(binding.simpleTableWidget).state = + BottomSheetBehavior.STATE_HIDDEN observeNavBackStack() } @@ -1558,6 +1556,46 @@ open class EditorFragment : NavigationFragment(R.layout.f binding.objectTypesToolbar.clear() } } + + state.simpleTableWidget.apply { + val behavior = BottomSheetBehavior.from(binding.simpleTableWidget) + if (isVisible) { + binding.simpleTableWidget.onStateChanged( + cellItems = state.simpleTableWidget.cellItems, + rowItems = state.simpleTableWidget.rowItems, + columnItems = state.simpleTableWidget.columnItems + ) + if (behavior.state == BottomSheetBehavior.STATE_HIDDEN) { + keyboardDelayJobs += lifecycleScope.launch { + if (binding.recycler.itemDecorationCount == 0) { + binding.recycler.addItemDecoration(styleToolbarFooter) + } + proceedWithHidingSoftInput() + delayKeyboardHide(insets) + behavior.apply { + setState(BottomSheetBehavior.STATE_EXPANDED) + addBottomSheetCallback(onHideBottomSheetCallback) + } + } + } + } else { + if (behavior.state == BottomSheetBehavior.STATE_EXPANDED) { + behavior.removeBottomSheetCallback(onHideBottomSheetCallback) + behavior.state = BottomSheetBehavior.STATE_HIDDEN + } + } + } + + state.cellsSelectTopWidget.apply { + if (isVisible) { + binding.cellSelectionTopToolbar.showWithAnimation() + binding.cellSelectionTopToolbar.setCellSelectionText(count) + } else { + binding.cellSelectionTopToolbar.hideWithAnimation { + if (hasBinding) binding.topToolbar.visible() + } + } + } } private fun applySlideTransition(transTarget: View, transDuration: Long, transRoot: ViewGroup) { diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml index abeda219ae..29cbcd0f96 100644 --- a/app/src/main/res/layout/fragment_editor.xml +++ b/app/src/main/res/layout/fragment_editor.xml @@ -58,6 +58,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + if (payload.changes.contains(BlockViewDiffUtil.TABLE_CELLS_CHANGED)) { + bind(item) + } if (payload.changes.contains(BlockViewDiffUtil.SELECTION_CHANGED)) { selected.isSelected = item.isSelected } if (payload.changes.contains(BlockViewDiffUtil.BACKGROUND_COLOR_CHANGED)) { applyBackground(item.background) } + if (payload.changes.contains(BlockViewDiffUtil.TABLE_CELLS_SELECTION_CHANGED)) { + updateCellsSelection(item) + } } } @@ -118,4 +134,29 @@ class TableBlockHolder( fun recycle() { tableAdapter.submitList(emptyList()) } + + private fun updateCellsSelection(item: BlockView.Table) { + if (item.selectedCellsIds.isEmpty()) { + cellsSelectionState.clear() + if (recycler.containsItemDecoration(cellSelectionDecoration)) { + recycler.removeItemDecoration(cellSelectionDecoration) + } + } else { + val selectedCells = item.cells.filter { cell -> + item.selectedCellsIds.contains(cell.getId()) + } + cellsSelectionState.clear() + cellsSelectionState.set(cells = selectedCells) + if (cellsSelectionState.current().isNotEmpty()) { + cellSelectionDecoration.setSelectionState(cellsSelectionState.current()) + if (!recycler.containsItemDecoration(cellSelectionDecoration)) { + recycler.addItemDecoration(cellSelectionDecoration) + } else { + recycler.invalidateItemDecorations() + } + } else { + recycler.removeItemDecoration(cellSelectionDecoration) + } + } + } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/layout/TableCellSelectionDecoration.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/layout/TableCellSelectionDecoration.kt new file mode 100644 index 0000000000..35f2e62e59 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/layout/TableCellSelectionDecoration.kt @@ -0,0 +1,82 @@ +package com.anytypeio.anytype.core_ui.layout + +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import androidx.core.view.children +import androidx.recyclerview.widget.RecyclerView +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView +import kotlin.math.roundToInt + +class TableCellSelectionDecoration( + private val drawable: Drawable +) : RecyclerView.ItemDecoration() { + + private val selectionState: MutableList = mutableListOf() + + fun setSelectionState(newState: List) { + selectionState.clear() + selectionState.addAll(newState) + } + + override fun onDraw( + canvas: Canvas, + parent: RecyclerView, + state: RecyclerView.State + ) { + canvas.save() + val rect = Rect() + parent.children.forEach { view -> + val position = parent.getChildAdapterPosition(view) + if (position != RecyclerView.NO_POSITION) { + val cellSelection = selectionState.find { it.cellIndex == position } + if (cellSelection != null) { + parent.getDecoratedBoundsWithMargins(view, rect) + if (cellSelection.left) { + drawable.setBounds( + rect.left, + rect.top, + rect.left + drawable.intrinsicWidth, + rect.bottom + ) + drawable.draw(canvas) + } + if (cellSelection.top) { + val top = rect.top + view.translationY.roundToInt() + val bottom = top + drawable.intrinsicHeight + drawable.setBounds( + rect.left, + top, + rect.right, + bottom + ) + drawable.draw(canvas) + } + if (cellSelection.right) { + val right = rect.right + view.translationX.roundToInt() + val left = right - drawable.intrinsicWidth + drawable.setBounds( + left, + rect.top, + right, + rect.bottom + ) + drawable.draw(canvas) + } + if (cellSelection.bottom) { + val bottom = rect.bottom + view.translationY.roundToInt() + val top = bottom - drawable.intrinsicHeight + drawable.setBounds( + rect.left, + top, + rect.right, + bottom + ) + drawable.draw(canvas) + } + } + } + } + canvas.restore() + } +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MultiSelectTopToolbarWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MultiSelectTopToolbarWidget.kt index e422538e1e..6c5d3f0101 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MultiSelectTopToolbarWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/MultiSelectTopToolbarWidget.kt @@ -1,12 +1,17 @@ package com.anytypeio.anytype.core_ui.widgets.toolbar +import android.animation.ObjectAnimator import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.view.View +import android.view.animation.DecelerateInterpolator import android.widget.FrameLayout import android.widget.TextView +import androidx.core.animation.doOnEnd +import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.WidgetMultiSelectTopToolbarBinding +import com.anytypeio.anytype.core_utils.ext.dimen class MultiSelectTopToolbarWidget @JvmOverloads constructor( context: Context, @@ -19,4 +24,52 @@ class MultiSelectTopToolbarWidget @JvmOverloads constructor( val selectText get() : TextView = binding.tvToolbarTitle val doneButton get() : View = binding.btnDone + + fun setCellSelectionText(count: Int) { + when { + count == 1 -> { + selectText.text = context.getString(R.string.one_selected_cell) + } + count > 1 -> { + selectText.text = context.getString(R.string.number_selected_cells, count) + } + else -> { + selectText.text = null + } + } + } + + fun showWithAnimation() { + if (translationY < 0) { + ObjectAnimator.ofFloat( + this, + SELECT_BUTTON_ANIMATION_PROPERTY, + 0f + ).apply { + duration = SELECT_BUTTON_ANIMATION_DURATION + interpolator = DecelerateInterpolator() + start() + } + } + } + + fun hideWithAnimation(action: () -> Unit) { + if (translationY >= 0) { + ObjectAnimator.ofFloat( + this, + SELECT_BUTTON_ANIMATION_PROPERTY, + -context.dimen(R.dimen.dp_48) + ).apply { + duration = SELECT_BUTTON_ANIMATION_DURATION + interpolator = DecelerateInterpolator() + doOnEnd { action.invoke() } + start() + } + } + } + + companion object { + const val SELECT_BUTTON_ANIMATION_PROPERTY = "translationY" + const val SELECT_BUTTON_ANIMATION_DURATION = 200L + } } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableSettingWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableSettingWidget.kt index 58b5733503..b27c7d7f27 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableSettingWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableSettingWidget.kt @@ -6,7 +6,8 @@ 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.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem +import com.anytypeio.anytype.presentation.editor.markup.MarkupColorView import com.google.android.material.tabs.TabLayoutMediator class SimpleTableSettingWidget @JvmOverloads constructor( @@ -19,12 +20,14 @@ class SimpleTableSettingWidget @JvmOverloads constructor( LayoutInflater.from(context), this, true ) + var onItemClickListener: (SimpleTableWidgetItem) -> Unit = {} + private val cellAdapter = SimpleTableWidgetAdapter(items = listOf(), - onClick = { item -> }) + onClick = { item -> onItemClickListener.invoke(item)}) private val columnAdapter = SimpleTableWidgetAdapter(items = listOf(), - onClick = { item -> }) + onClick = { item -> onItemClickListener.invoke(item)}) private val rowAdapter = SimpleTableWidgetAdapter(items = listOf(), - onClick = { item -> }) + onClick = { item -> onItemClickListener.invoke(item)}) private val pagerAdapter = SimpleTableSettingAdapter( cellAdapter = cellAdapter, @@ -32,15 +35,18 @@ class SimpleTableSettingWidget @JvmOverloads constructor( 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 -> {} - } + fun onStateChanged( + cellItems: List, + rowItems: List, + columnItems: List + ) { + cellAdapter.update(cellItems) + columnAdapter.update(columnItems) + rowAdapter.update(rowItems) + } + + fun setListener(listener: (SimpleTableWidgetItem) -> Unit = {}) { + onItemClickListener = listener } init { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableWidgetAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableWidgetAdapter.kt index 191c5aaf87..e4e6ee26cf 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableWidgetAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/table/SimpleTableWidgetAdapter.kt @@ -23,9 +23,11 @@ class SimpleTableWidgetAdapter( val holder = VH( binding = ItemSimpleTableActionBinding.inflate(inflater, parent, false) ).apply { - val pos = bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) - onClick(items[pos]) + itemView.setOnClickListener { + val pos = bindingAdapterPosition + if (pos != RecyclerView.NO_POSITION) + onClick(items[pos]) + } } return holder } diff --git a/core-ui/src/main/res/drawable/cell_top_border.xml b/core-ui/src/main/res/drawable/cell_top_border.xml new file mode 100644 index 0000000000..f1cf771043 --- /dev/null +++ b/core-ui/src/main/res/drawable/cell_top_border.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/values/strings.xml b/core-ui/src/main/res/values/strings.xml index fd4044df20..a01eb8b2ab 100644 --- a/core-ui/src/main/res/values/strings.xml +++ b/core-ui/src/main/res/values/strings.xml @@ -563,6 +563,8 @@ Send email Call phone number Bookmark icon + 1 cell selected + %1$d cells selected Could\'t find the selected block Block selection error diff --git a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewSearchTextTest.kt b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewSearchTextTest.kt index 7583d04925..1394a3b869 100644 --- a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewSearchTextTest.kt +++ b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewSearchTextTest.kt @@ -1101,6 +1101,8 @@ class BlockViewSearchTextTest { val row2Block2 = StubParagraph(id = "$rowId2-$columnId2", text = "bb2") val row2Block3 = StubParagraph(id = "$rowId2-$columnId3", text = "bc3") + val tableId = MockDataFactory.randomUuid() + val cells = listOf( BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1110,7 +1112,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1120,7 +1124,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1130,7 +1136,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1140,7 +1148,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 1, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1150,7 +1160,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 3, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1160,7 +1172,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 5, + tableId = tableId ) ) @@ -1170,15 +1184,14 @@ class BlockViewSearchTextTest { BlockView.Table.Column(id = columnId3, background = ThemeColor.DEFAULT) ) - val tableId = MockDataFactory.randomUuid() - val views = listOf( BlockView.Table( id = tableId, cells = cells, columns = columns, rowCount = 2, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) @@ -1198,7 +1211,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1215,7 +1230,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1232,7 +1249,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1249,7 +1268,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 1, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1266,7 +1287,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 3, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1283,7 +1306,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 5, + tableId = tableId ) ) @@ -1306,7 +1331,8 @@ class BlockViewSearchTextTest { cells = expectedCells, columns = columns, rowCount = 2, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) @@ -1331,6 +1357,8 @@ class BlockViewSearchTextTest { val row2Block2 = StubParagraph(id = "$rowId2-$columnId2", text = "bb2") val row2Block3 = StubParagraph(id = "$rowId2-$columnId3", text = "bc3") + val tableId = MockDataFactory.randomUuid() + val cells = listOf( BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1340,7 +1368,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1350,7 +1380,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1360,7 +1392,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1370,7 +1404,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 1, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1380,7 +1416,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 3, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1390,7 +1428,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 5, + tableId = tableId ) ) @@ -1400,15 +1440,14 @@ class BlockViewSearchTextTest { BlockView.Table.Column(id = columnId3, background = ThemeColor.DEFAULT) ) - val tableId = MockDataFactory.randomUuid() - val views = listOf( BlockView.Table( id = tableId, cells = cells, columns = columns, rowCount = 2, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) @@ -1421,7 +1460,9 @@ class BlockViewSearchTextTest { rowId = rowId1, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1432,6 +1473,8 @@ class BlockViewSearchTextTest { columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1442,6 +1485,8 @@ class BlockViewSearchTextTest { columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1451,7 +1496,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 1, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1461,7 +1508,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 3, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -1471,7 +1520,9 @@ class BlockViewSearchTextTest { rowId = rowId2, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 5, + tableId = tableId ) ) @@ -1496,7 +1547,8 @@ class BlockViewSearchTextTest { cells = expectedCells, columns = columns, rowCount = 2, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) diff --git a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewStubs.kt b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewStubs.kt index ed91b9fde2..5e696e3720 100644 --- a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewStubs.kt +++ b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/BlockViewStubs.kt @@ -348,7 +348,9 @@ fun StubTwoRowsThreeColumnsSimpleTable( rowId = rowId1, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -358,7 +360,9 @@ fun StubTwoRowsThreeColumnsSimpleTable( rowId = rowId1, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -368,7 +372,9 @@ fun StubTwoRowsThreeColumnsSimpleTable( rowId = rowId1, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -378,7 +384,9 @@ fun StubTwoRowsThreeColumnsSimpleTable( rowId = rowId2, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 1, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -388,7 +396,9 @@ fun StubTwoRowsThreeColumnsSimpleTable( rowId = rowId2, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 3, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -398,7 +408,9 @@ fun StubTwoRowsThreeColumnsSimpleTable( rowId = rowId2, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 5, + tableId = tableId ) ) @@ -413,7 +425,8 @@ fun StubTwoRowsThreeColumnsSimpleTable( cells = cells, columns = columns, rowCount = 2, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) } diff --git a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/editor/table/TableBlockTest.kt b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/editor/table/TableBlockTest.kt index fe4c03cab5..51f19c0e4c 100644 --- a/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/editor/table/TableBlockTest.kt +++ b/core-ui/src/test/java/com/anytypeio/anytype/core_ui/features/editor/table/TableBlockTest.kt @@ -60,6 +60,7 @@ class TableBlockTest { val row1Block1 = StubParagraph(id = "$rowId1-$columnId2", text = "a1") val row1Block2 = StubParagraph(id = "$rowId1-$columnId3", text = oldText) + val tableId = MockDataFactory.randomUuid() val cells = listOf( BlockView.Table.Cell( @@ -67,7 +68,9 @@ class TableBlockTest { columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(0), - block = null + block = null, + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -78,6 +81,8 @@ class TableBlockTest { columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -88,13 +93,17 @@ class TableBlockTest { columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( rowId = rowId1, columnId = columnId4, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(3), - block = null + block = null, + cellIndex = 6, + tableId = tableId ) ) @@ -104,7 +113,9 @@ class TableBlockTest { columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(0), - block = null + block = null, + cellIndex = 0, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -114,7 +125,9 @@ class TableBlockTest { rowId = rowId1, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 2, + tableId = tableId ), BlockView.Table.Cell( block = BlockView.Text.Paragraph( @@ -124,14 +137,18 @@ class TableBlockTest { rowId = rowId1, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(2) + columnIndex = BlockView.Table.ColumnIndex(2), + cellIndex = 4, + tableId = tableId ), BlockView.Table.Cell( rowId = rowId1, columnId = columnId4, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(3), - block = null + block = null, + cellIndex = 6, + tableId = tableId ) ) @@ -148,14 +165,14 @@ class TableBlockTest { } val recycler = givenRecycler(it) - val tableId = MockDataFactory.randomUuid() val views = listOf( BlockView.Table( id = tableId, cells = cells, columns = columns, rowCount = 1, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) val adapter = givenAdapter(views) @@ -186,7 +203,8 @@ class TableBlockTest { cells = cellsNew, columns = columns, rowCount = 1, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index 91533b251b..6c7202c026 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -632,6 +632,10 @@ class BlockDataRepository( return remote.objectToSet(ctx, source) } + override suspend fun clearBlockContent(ctx: Id, blockIds: List) : Payload { + return remote.clearBlockContent(ctx, blockIds) + } + override suspend fun blockDataViewSetSource( ctx: Id, block: Id, diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt index 3c933877a1..e371ed9038 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt @@ -258,4 +258,6 @@ interface BlockDataStore { suspend fun objectToSet(ctx: Id, source: List): Id suspend fun blockDataViewSetSource(ctx: Id, block: Id, sources: List): Payload + + suspend fun clearBlockContent(ctx: Id, blockIds: List) : Payload } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index 875e69a2af..6f6235e96c 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -257,4 +257,6 @@ interface BlockRemote { suspend fun objectToSet(ctx: Id, source: List): Id suspend fun blockDataViewSetSource(ctx: Id, block: Id, sources: List): Payload + + suspend fun clearBlockContent(ctx: Id, blockIds: List) : Payload } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt index 2ad9923a33..6603ec27eb 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt @@ -555,4 +555,8 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore { ): Payload { return remote.blockDataViewSetSource(ctx, block, sources) } + + override suspend fun clearBlockContent(ctx: Id, blockIds: List): Payload { + return remote.clearBlockContent(ctx, blockIds) + } } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/interactor/ClearBlockContent.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/interactor/ClearBlockContent.kt new file mode 100644 index 0000000000..ea545b11c7 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/interactor/ClearBlockContent.kt @@ -0,0 +1,24 @@ +package com.anytypeio.anytype.domain.block.interactor + +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 ClearBlockContent( + private val repository: BlockRepository, +) : BaseUseCase() { + + data class Params( + val ctx: Id, + val blockIds: List + ) + + override suspend fun run(params: Params): Either = safe { + repository.clearBlockContent( + ctx = params.ctx, + blockIds = params.blockIds + ) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index c780ee3b6e..c46e9ebf7c 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -323,4 +323,6 @@ interface BlockRepository { suspend fun objectToSet(ctx: Id, source: List): Id suspend fun blockDataViewSetSource(ctx: Id, block: Id, sources: List): Payload + + suspend fun clearBlockContent(ctx: Id, blockIds: List) : Payload } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index 4f4d3efd92..fb94762442 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -594,4 +594,8 @@ class BlockMiddleware( ): Payload { return middleware.blockDataViewSetSource(ctx, block, sources) } + + override suspend fun clearBlockContent(ctx: Id, blockIds: List): Payload { + return middleware.clearBlockContent(ctx, blockIds) + } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index d73096c705..21f633a92a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -1756,6 +1756,21 @@ class Middleware( return response.event.toPayload() } + @Throws(Exception::class) + fun clearBlockContent( + ctx: Id, + blockIds: List + ): Payload { + val request = Rpc.BlockText.ListClearContent.Request( + contextId = ctx, + blockIds = blockIds + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.blockListClearContent(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) diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 5aedf1b9d6..2586e1821b 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -236,6 +236,10 @@ interface MiddlewareService { @Throws(Exception::class) fun blockRelationSetKey(request: Rpc.BlockRelation.SetKey.Request): Rpc.BlockRelation.SetKey.Response + @Throws(Exception::class) + fun blockListClearContent(request: Rpc.BlockText.ListClearContent.Request) + : Rpc.BlockText.ListClearContent.Response + //endregion //region NAVIGATION commands diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 265400f4ed..02ec78a8a7 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -438,6 +438,19 @@ class MiddlewareServiceImplementation : MiddlewareService { } } + override fun blockListClearContent(request: Rpc.BlockText.ListClearContent.Request): Rpc.BlockText.ListClearContent.Response { + val encoded = Service.blockTextListClearContent( + Rpc.BlockText.ListClearContent.Request.ADAPTER.encode(request) + ) + val response = Rpc.BlockText.ListClearContent.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.BlockText.ListClearContent.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + override fun blockSplit(request: Rpc.Block.Split.Request): Rpc.Block.Split.Response { val encoded = Service.blockSplit(Rpc.Block.Split.Request.ADAPTER.encode(request)) val response = Rpc.Block.Split.Response.ADAPTER.decode(encoded) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt index 44a302c079..c50769b597 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/ControlPanelMachine.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.editor import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Block.Content.Text.Style +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.TextBlock import com.anytypeio.anytype.presentation.common.StateReducer import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Event @@ -10,9 +11,11 @@ import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Reducer import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState.Companion.init import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState.Toolbar +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.editor.editor.slash.SlashWidgetState import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarState import com.anytypeio.anytype.presentation.editor.editor.styling.getSupportedMarkupTypes +import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem import com.anytypeio.anytype.presentation.extension.style import com.anytypeio.anytype.presentation.navigation.DefaultObjectView import com.anytypeio.anytype.presentation.objects.ObjectTypeView @@ -212,6 +215,18 @@ sealed class ControlPanelMachine { data class Show(val data: List) : ObjectTypesWidgetEvent() object Hide : ObjectTypesWidgetEvent() } + + sealed class SimpleTableWidget : Event() { + data class Show( + val tableId: Id, + val cells: List, + val cellItems: List = emptyList(), + val rowItems: List = emptyList(), + val columnItems: List = emptyList() + ) : SimpleTableWidget() + + data class Hide(val tableId: Id) : SimpleTableWidget() + } } /** @@ -374,7 +389,8 @@ sealed class ControlPanelMachine { ), slashWidget = Toolbar.SlashWidget.reset(), objectTypesToolbar = Toolbar.ObjectTypes.reset(), - styleBackgroundToolbar = Toolbar.Styling.Background.reset() + styleBackgroundToolbar = Toolbar.Styling.Background.reset(), + simpleTableWidget = Toolbar.SimpleTableWidget.reset() ) } else { state.copy( @@ -434,7 +450,7 @@ sealed class ControlPanelMachine { !state.mainToolbar.isVisible -> state.copy( mainToolbar = state.mainToolbar.copy( isVisible = true, - targetBlockType = when(event.style) { + targetBlockType = when (event.style) { Style.TITLE -> Toolbar.Main.TargetBlockType.Title else -> Toolbar.Main.TargetBlockType.Any } @@ -446,12 +462,13 @@ sealed class ControlPanelMachine { navigationToolbar = Toolbar.Navigation( isVisible = false ), - styleBackgroundToolbar = Toolbar.Styling.Background.reset() + styleBackgroundToolbar = Toolbar.Styling.Background.reset(), + simpleTableWidget = Toolbar.SimpleTableWidget.reset() ) else -> { state.copy( mainToolbar = state.mainToolbar.copy( - targetBlockType = when(event.style) { + targetBlockType = when (event.style) { Style.TITLE -> Toolbar.Main.TargetBlockType.Title else -> Toolbar.Main.TargetBlockType.Any } @@ -462,7 +479,8 @@ sealed class ControlPanelMachine { mentionToolbar = Toolbar.MentionToolbar.reset(), slashWidget = Toolbar.SlashWidget.reset(), objectTypesToolbar = Toolbar.ObjectTypes.reset(), - styleBackgroundToolbar = Toolbar.Styling.Background.reset() + styleBackgroundToolbar = Toolbar.Styling.Background.reset(), + simpleTableWidget = Toolbar.SimpleTableWidget.reset() ) } } @@ -495,6 +513,9 @@ sealed class ControlPanelMachine { ) ) } + is Event.SimpleTableWidget -> { + handleSimpleTableEvent(event, state) + } } private fun handleStylingToolbarEvent( @@ -722,7 +743,8 @@ sealed class ControlPanelMachine { isVisible = false ), slashWidget = Toolbar.SlashWidget.reset(), - mentionToolbar = Toolbar.MentionToolbar.reset() + mentionToolbar = Toolbar.MentionToolbar.reset(), + simpleTableWidget = Toolbar.SimpleTableWidget.reset() ) is Event.MultiSelect.OnExit -> state.copy( multiSelect = state.multiSelect.copy( @@ -774,7 +796,8 @@ sealed class ControlPanelMachine { styleTextToolbar = Toolbar.Styling.reset(), mentionToolbar = Toolbar.MentionToolbar.reset(), slashWidget = Toolbar.SlashWidget.reset(), - styleBackgroundToolbar = Toolbar.Styling.Background.reset() + styleBackgroundToolbar = Toolbar.Styling.Background.reset(), + simpleTableWidget = Toolbar.SimpleTableWidget.reset() ) } Event.ReadMode.OnExit -> state.copy() @@ -865,6 +888,55 @@ sealed class ControlPanelMachine { } } + private fun handleSimpleTableEvent( + event: Event.SimpleTableWidget, + state: ControlPanelState + ): ControlPanelState = when (event) { + is Event.SimpleTableWidget.Show -> { + state.copy( + simpleTableWidget = state.simpleTableWidget.copy( + isVisible = true, + tableId = event.tableId, + cells = event.cells, + cellItems = event.cellItems, + rowItems = event.rowItems, + columnItems = event.columnItems + ), + cellsSelectTopWidget = state.cellsSelectTopWidget.copy( + isVisible = true, + count = event.cells.size + ), + mainToolbar = Toolbar.Main.reset(), + styleColorBackgroundToolbar = Toolbar.Styling.ColorBackground.reset(), + styleExtraToolbar = Toolbar.Styling.Extra.reset(), + styleTextToolbar = Toolbar.Styling.reset(), + styleBackgroundToolbar = Toolbar.Styling.Background.reset(), + navigationToolbar = Toolbar.Navigation.reset(), + slashWidget = Toolbar.SlashWidget.reset(), + mentionToolbar = Toolbar.MentionToolbar.reset(), + multiSelect = Toolbar.MultiSelect.reset() + ) + } + is Event.SimpleTableWidget.Hide -> { + state.copy( + navigationToolbar = state.navigationToolbar.copy( + isVisible = true + ), + multiSelect = Toolbar.MultiSelect.reset(), + mainToolbar = Toolbar.Main.reset(), + styleColorBackgroundToolbar = Toolbar.Styling.ColorBackground.reset(), + styleExtraToolbar = Toolbar.Styling.Extra.reset(), + styleTextToolbar = Toolbar.Styling.reset(), + styleBackgroundToolbar = Toolbar.Styling.Background.reset(), + simpleTableWidget = state.simpleTableWidget.copy( + isVisible = false, + tableId = event.tableId + ), + cellsSelectTopWidget = Toolbar.CellSelection.reset() + ) + } + } + private fun logState(text: String, state: ControlPanelState) { Timber.i( "REDUCER, $text STATE:${ diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt index ccfe23eb33..a5b2f70466 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt @@ -75,6 +75,11 @@ interface Editor { */ data class Multi(val targets: Set) : Styling() } + + /** + * Editor in simple table menu mode. + */ + data class Table(val tableId: Id) : Mode() } class Storage { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index b268915efb..487cf90665 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -140,10 +140,7 @@ 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.table.SimpleTableWidgetItem import com.anytypeio.anytype.presentation.editor.editor.toCoreModel import com.anytypeio.anytype.presentation.editor.editor.updateText import com.anytypeio.anytype.presentation.editor.model.EditorFooter @@ -153,6 +150,9 @@ import com.anytypeio.anytype.presentation.editor.render.BlockViewRenderer import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer import com.anytypeio.anytype.presentation.editor.search.search import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder +import com.anytypeio.anytype.presentation.editor.selection.getSimpleTableWidgetItems +import com.anytypeio.anytype.presentation.editor.selection.toggleTableMode +import com.anytypeio.anytype.presentation.editor.selection.updateTableBlockSelection import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate import com.anytypeio.anytype.presentation.editor.template.SelectTemplateEvent import com.anytypeio.anytype.presentation.editor.template.SelectTemplateState @@ -248,7 +248,6 @@ class EditorViewModel( private val setDocCoverImage: SetDocCoverImage, private val setDocImageIcon: SetDocumentImageIcon, private val templateDelegate: EditorTemplateDelegate, - private val simpleTableDelegate: SimpleTableDelegate, private val createNewObject: CreateNewObject, private val objectToSet: ConvertObjectToSet ) : ViewStateViewModel(), @@ -259,7 +258,6 @@ class EditorViewModel( ToggleStateHolder by renderer, SelectionStateHolder by orchestrator.memory.selections, EditorTemplateDelegate by templateDelegate, - SimpleTableDelegate by simpleTableDelegate, StateReducer, Event> by reducer { val actions = MutableStateFlow(ActionItemType.defaultSorting) @@ -282,17 +280,6 @@ 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) @@ -2444,7 +2431,7 @@ class EditorViewModel( if (view == null) { val cell = views.findTableCellView(target) if (cell != null) { - onShowSimpleTableWidgetClicked(target) + proceedWithEnterTableMode(cell) viewModelScope.sendAnalyticsSelectionMenuEvent(analytics) } } else { @@ -3842,13 +3829,13 @@ class EditorViewModel( return } proceedWithSelectingCell( - cellId = clicked.cellId, - tableId = clicked.tableId + cellId = clicked.cell.getId(), + tableId = clicked.cell.tableId ) onTableRowEmptyCellClicked( - cellId = clicked.cellId, - rowId = clicked.rowId, - tableId = clicked.tableId + cellId = clicked.cell.getId(), + rowId = clicked.cell.rowId, + tableId = clicked.cell.tableId ) } } @@ -3860,16 +3847,28 @@ class EditorViewModel( } } proceedWithSelectingCell( - cellId = clicked.cellId, - tableId = clicked.tableId + cellId = clicked.cell.getId(), + tableId = clicked.cell.tableId ) onTableRowEmptyCellClicked( - cellId = clicked.cellId, - rowId = clicked.rowId, - tableId = clicked.tableId + cellId = clicked.cell.getId(), + rowId = clicked.cell.rowId, + tableId = clicked.cell.tableId ) } - EditorMode.Select -> onBlockMultiSelectClicked(target = clicked.tableId) + EditorMode.Select -> onBlockMultiSelectClicked(target = clicked.cell.tableId) + is EditorMode.Table -> { + val modeTableId = (mode as EditorMode.Table).tableId + val cellTableId = clicked.cell.tableId + if (cellTableId == modeTableId) { + proceedWithClickingOnCellInTableMode( + cell = clicked.cell, + tableId = cellTableId + ) + } else { + Timber.e("Cell is from the different table, amend click") + } + } else -> Unit } } @@ -3881,15 +3880,15 @@ class EditorViewModel( return } proceedWithSelectingCell( - cellId = clicked.cellId, - tableId = clicked.tableId + cellId = clicked.cell.getId(), + tableId = clicked.cell.tableId ) if (!BuildConfig.USE_SIMPLE_TABLES_IN_EDITOR_EDDITING) { dispatch( Command.OpenSetBlockTextValueScreen( ctx = context, - block = clicked.cellId, - table = clicked.tableId + block = clicked.cell.getId(), + table = clicked.cell.tableId ) ) } @@ -3901,26 +3900,34 @@ class EditorViewModel( } if (!BuildConfig.USE_SIMPLE_TABLES_IN_EDITOR_EDDITING) { proceedWithSelectingCell( - cellId = clicked.cellId, - tableId = clicked.tableId + cellId = clicked.cell.getId(), + tableId = clicked.cell.tableId ) dispatch( Command.OpenSetBlockTextValueScreen( ctx = context, - block = clicked.cellId, - table = clicked.tableId + block = clicked.cell.getId(), + table = clicked.cell.tableId ) ) } } - EditorMode.Select -> onBlockMultiSelectClicked(target = clicked.tableId) + EditorMode.Select -> onBlockMultiSelectClicked(target = clicked.cell.tableId) + is EditorMode.Table -> { + val modeTableId = (mode as EditorMode.Table).tableId + val cellTableId = clicked.cell.tableId + if (cellTableId == modeTableId) { + proceedWithClickingOnCellInTableMode( + cell = clicked.cell, + tableId = cellTableId + ) + } else { + Timber.e("Cell is from the different table, amend click") + } + } else -> Unit } } - is ListenerType.TableEmptyCellMenu -> {} - is ListenerType.TableTextCellMenu -> { - onShowSimpleTableWidgetClicked(id = clicked.cellId) - } else -> {} } } @@ -6111,13 +6118,121 @@ class EditorViewModel( //endregion //region SIMPLE TABLES - private fun onShowSimpleTableWidgetClicked(id: Id) { - viewModelScope.launch { - onSimpleTableEvent(SimpleTableWidgetEvent.onStart(id = id)) + fun onCellsSelectionDoneClick() { + proceedWithExitingTableMode() + } + + fun onHideSimpleTableWidget() { + proceedWithExitingTableMode() + } + + fun onSimpleTableWidgetItemClicked(item: SimpleTableWidgetItem) { + when (item) { + SimpleTableWidgetItem.Cell.ClearContents -> { + val selected = currentSelection() + viewModelScope.launch { + orchestrator.proxies.intents.send( + Intent.Text.ClearContent( + context = context, + targets = selected.toList() + ) + ) + } + } + else -> Unit } } - fun onHideSimpleTableWidget() {} + /** + * Enter EditorMode.Table + */ + private fun proceedWithEnterTableMode(cell: BlockView.Table.Cell) { + viewModelScope.launch { + mode = EditorMode.Table(tableId = cell.tableId) + clearSelections() + toggleSelection(target = cell.getId()) + + orchestrator.stores.focus.update(Editor.Focus.empty()) + orchestrator.stores.views.update( + views.toggleTableMode( + cellsMode = BlockView.Mode.READ, + selectedCellsIds = currentSelection().toList() + ) + ) + renderCommand.send(Unit) + controlPanelInteractor.onEvent( + ControlPanelMachine.Event.SimpleTableWidget.Show( + cellItems = listOf(cell).getSimpleTableWidgetItems(), + rowItems = emptyList(), + columnItems = emptyList(), + cells = listOf(cell), + tableId = cell.tableId + ) + ) + } + } + + /** + * Exit EditorMode.Table + */ + private fun proceedWithExitingTableMode() { + Timber.d("proceedWithExitingTableMode, mode:[$mode]") + if (currentSelection().isNotEmpty()) clearSelections() + val currentMode = mode + if (currentMode is EditorMode.Table) { + val tableId = currentMode.tableId + mode = EditorMode.Edit + controlPanelInteractor.onEvent( + ControlPanelMachine.Event.SimpleTableWidget.Hide( + tableId = tableId + ) + ) + viewModelScope.launch { + orchestrator.stores.views.update( + views.toggleTableMode( + cellsMode = BlockView.Mode.EDIT, + selectedCellsIds = currentSelection().toList() + ) + ) + renderCommand.send(Unit) + } + } else { + Timber.w("Can't exit Mode.Table, current mode is $mode") + } + } + + private fun proceedWithClickingOnCellInTableMode( + tableId: Id, + cell: BlockView.Table.Cell + ) { + toggleSelection(target = cell.getId()) + if (currentSelection().isEmpty()) { + proceedWithExitingTableMode() + } else { + val tableBlock = views.find { it.id == tableId } as BlockView.Table + val selectedCells = tableBlock.cells.mapNotNull { + if (currentSelection().contains(it.getId())) it else null + } + viewModelScope.launch { + orchestrator.stores.views.update( + views.updateTableBlockSelection( + tableId = tableBlock.id, + selection = currentSelection().toList() + ) + ) + renderCommand.send(Unit) + } + controlPanelInteractor.onEvent( + ControlPanelMachine.Event.SimpleTableWidget.Show( + cellItems = listOf(cell).getSimpleTableWidgetItems(), + rowItems = emptyList(), + columnItems = emptyList(), + cells = selectedCells, + tableId = cell.tableId + ) + ) + } + } private fun proceedWithSelectingCell(cellId: Id, tableId: Id) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt index 4b3a86cca7..229b43489e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt @@ -33,7 +33,6 @@ import com.anytypeio.anytype.presentation.common.Delegator import com.anytypeio.anytype.presentation.common.StateReducer 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,7 +68,6 @@ open class EditorViewModelFactory( private val setDocCoverImage: SetDocCoverImage, private val setDocImageIcon: SetDocumentImageIcon, private val editorTemplateDelegate: EditorTemplateDelegate, - private val simpleTablesDelegate: SimpleTableDelegate, private val createNewObject: CreateNewObject, private val objectToSet: ConvertObjectToSet ) : ViewModelProvider.Factory { @@ -107,7 +105,6 @@ open class EditorViewModelFactory( setDocImageIcon = setDocImageIcon, templateDelegate = editorTemplateDelegate, createNewObject = createNewObject, - simpleTableDelegate = simpleTablesDelegate, objectToSet = objectToSet ) as T } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt index 4f357fffd9..6df45fcb3c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt @@ -169,6 +169,11 @@ sealed class Intent { val targets: List, val mark: Block.Content.Text.Mark ) : Text() + + class ClearContent( + val context: Id, + val targets: List + ): Text() } sealed class Media : Intent() { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt index 5c8ad16f2e..d0af1a3d2f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.analytics.event.EventAnalytics import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.domain.base.suspendFold import com.anytypeio.anytype.domain.block.UpdateDivider +import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.DuplicateBlock import com.anytypeio.anytype.domain.block.interactor.MergeBlocks @@ -88,7 +89,8 @@ class Orchestrator( val stores: Editor.Storage, val proxies: Editor.Proxer, val textInteractor: Interactor.TextInteractor, - private val analytics: Analytics + private val analytics: Analytics, + private val clearBlockContent: ClearBlockContent ) { private val defaultOnError: suspend (Throwable) -> Unit = { Timber.e(it) } @@ -627,6 +629,17 @@ class Orchestrator( success = { payload -> proxies.payloads.send(payload) } ) } + is Intent.Text.ClearContent -> { + clearBlockContent( + params = ClearBlockContent.Params( + ctx = intent.context, + blockIds = intent.targets + ) + ).process( + failure = defaultOnError, + success = { payload -> proxies.payloads.send(payload) } + ) + } } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt index 84dde96a79..e831f3df20 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/control/ControlPanelState.kt @@ -1,8 +1,11 @@ package com.anytypeio.anytype.presentation.editor.editor.control +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.presentation.editor.editor.Markup +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.editor.editor.slash.SlashWidgetState import com.anytypeio.anytype.presentation.editor.editor.styling.StyleToolbarState +import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem import com.anytypeio.anytype.presentation.editor.markup.MarkupStyleDescriptor import com.anytypeio.anytype.presentation.navigation.DefaultObjectView import com.anytypeio.anytype.presentation.objects.ObjectTypeView @@ -26,7 +29,9 @@ data class ControlPanelState( val mentionToolbar: Toolbar.MentionToolbar = Toolbar.MentionToolbar.reset(), val slashWidget: Toolbar.SlashWidget = Toolbar.SlashWidget.reset(), val searchToolbar: Toolbar.SearchToolbar = Toolbar.SearchToolbar.reset(), - val objectTypesToolbar: Toolbar.ObjectTypes = Toolbar.ObjectTypes.reset() + val objectTypesToolbar: Toolbar.ObjectTypes = Toolbar.ObjectTypes.reset(), + val simpleTableWidget: Toolbar.SimpleTableWidget = Toolbar.SimpleTableWidget.reset(), + val cellsSelectTopWidget: Toolbar.CellSelection = Toolbar.CellSelection.reset() ) { sealed class Toolbar { @@ -251,6 +256,38 @@ data class ControlPanelState( ) } } + + data class SimpleTableWidget( + override val isVisible: Boolean, + val tableId: Id, + val cells: List, + val cellItems: List = emptyList(), + val rowItems: List = emptyList(), + val columnItems: List = emptyList() + ) : Toolbar() { + companion object { + fun reset(): SimpleTableWidget = SimpleTableWidget( + isVisible = false, + tableId = "", + cells = emptyList(), + cellItems = emptyList(), + rowItems = emptyList(), + columnItems = emptyList() + ) + } + } + + data class CellSelection( + override val isVisible: Boolean, + val count: Int + ) : Toolbar() { + companion object { + fun reset(): CellSelection = CellSelection( + isVisible = false, + count = 0 + ) + } + } } /** @@ -300,7 +337,9 @@ data class ControlPanelState( isVisible = false ), slashWidget = Toolbar.SlashWidget.reset(), - objectTypesToolbar = Toolbar.ObjectTypes.reset() + objectTypesToolbar = Toolbar.ObjectTypes.reset(), + simpleTableWidget = Toolbar.SimpleTableWidget.reset(), + cellsSelectTopWidget = Toolbar.CellSelection.reset() ) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt index 5c59bcf2fa..28ff2d533b 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt @@ -72,8 +72,6 @@ sealed interface ListenerType { data class TableOfContentsItem(val target: Id, val item: Id) : ListenerType data class TableOfContents(val target: 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 + data class TableEmptyCell(val cell: BlockView.Table.Cell) : ListenerType + data class TableTextCell(val cell: BlockView.Table.Cell) : ListenerType } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt index 917fa36e31..b33bcbf378 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt @@ -192,6 +192,7 @@ sealed class BlockView : ViewType { object H2 : Header() object H3 : Header() } + object Code : Style() object Card : Style() } @@ -739,7 +740,8 @@ sealed class BlockView : ViewType { override val indent: Int = 0, override val decorations: List = emptyList(), val lang: String? = null - ) : BlockView(), Permission, Selectable, Focusable, Cursor, Indentable, TextSupport, Decoratable { + ) : BlockView(), Permission, Selectable, Focusable, Cursor, Indentable, TextSupport, + Decoratable { override fun getViewType() = HOLDER_CODE_SNIPPET } @@ -1090,7 +1092,7 @@ sealed class BlockView : ViewType { override fun getViewType() = HOLDER_OBJECT_LINK_DEFAULT } - sealed class Card: Default() { + sealed class Card : Default() { abstract val isPreviousBlockMedia: Boolean @@ -1138,7 +1140,7 @@ sealed class BlockView : ViewType { override val decorations: List = emptyList(), override val objectTypeName: String? = null, override val isPreviousBlockMedia: Boolean, - val cover : Cover? + val cover: Cover? ) : Card() { override fun getViewType() = HOLDER_OBJECT_LINK_CARD_SMALL_ICON_COVER } @@ -1155,7 +1157,7 @@ sealed class BlockView : ViewType { override val decorations: List = emptyList(), override val objectTypeName: String? = null, override val isPreviousBlockMedia: Boolean, - val cover : Cover? + val cover: Cover? ) : Card() { override fun getViewType() = HOLDER_OBJECT_LINK_CARD_MEDIUM_ICON_COVER } @@ -1239,7 +1241,7 @@ sealed class BlockView : ViewType { data class FeaturedRelation( override val id: String, val relations: List, - val allowChangingObjectType : Boolean = true + val allowChangingObjectType: Boolean = true ) : BlockView() { override fun getViewType(): Int = HOLDER_FEATURED_RELATION } @@ -1320,7 +1322,8 @@ sealed class BlockView : ViewType { val background: ThemeColor = ThemeColor.DEFAULT, val columns: List, val cells: List, - val rowCount: Int + val rowCount: Int, + val selectedCellsIds: List ) : BlockView(), Selectable { override fun getViewType(): Int = HOLDER_TABLE @@ -1332,13 +1335,27 @@ sealed class BlockView : ViewType { val columnId: Id, val columnIndex: ColumnIndex, val isHeader: Boolean = false, - val block: Text.Paragraph? + val block: Text.Paragraph?, + val tableId: Id, + val cellIndex: Int ) { fun getId() = "$rowId-$columnId" } + data class CellSelection( + val cellId: String, + val rowIndex: RowIndex, + val columnIndex: ColumnIndex, + var left: Boolean, + var top: Boolean, + var right: Boolean, + var bottom: Boolean, + var cellIndex: Int + ) + @JvmInline value class RowIndex(val value: Int) + @JvmInline value class ColumnIndex(val value: Int) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableDelegate.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableDelegate.kt deleted file mode 100644 index 17aa79fd7b..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableDelegate.kt +++ /dev/null @@ -1,63 +0,0 @@ -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 - suspend fun onSimpleTableEvent(event: SimpleTableWidgetEvent) -} - -class DefaultSimpleTableDelegate : SimpleTableDelegate { - - private val events = MutableSharedFlow(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) - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetEvent.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetEvent.kt deleted file mode 100644 index ea49fe61b2..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -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 -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetState.kt deleted file mode 100644 index 1d33bde0ef..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetState.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.anytypeio.anytype.presentation.editor.editor.table - -sealed class SimpleTableWidgetState { - - object Idle : SimpleTableWidgetState() - - data class UpdateItems( - val cellItems: List, - val columnItems: List, - val rowItems: List - ) : SimpleTableWidgetState() { - companion object { - fun empty() = UpdateItems( - cellItems = emptyList(), - columnItems = emptyList(), - rowItems = emptyList() - ) - } - } - - companion object { - fun init(): SimpleTableWidgetState = Idle - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetViewState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetViewState.kt deleted file mode 100644 index a02e3c7a80..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/table/SimpleTableWidgetViewState.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.anytypeio.anytype.presentation.editor.editor.table - -sealed class SimpleTableWidgetViewState { - object Idle : SimpleTableWidgetViewState() - data class Active(val state: SimpleTableWidgetState) : SimpleTableWidgetViewState() -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt index 8aa2876f27..f4a4f6006a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt @@ -1994,7 +1994,8 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, rows = rows, columns = columns, - blocks = blocks + blocks = blocks, + tableId = block.id ) } } @@ -2008,11 +2009,13 @@ class DefaultBlockViewRenderer @Inject constructor( block = block, selection = selection ), - background = block.parseThemeBackgroundColor() + background = block.parseThemeBackgroundColor(), + selectedCellsIds = selection.toList() ) } private fun tableCells( + tableId: Id, blocks: Map>, rows: List, columns: List, @@ -2051,6 +2054,7 @@ class DefaultBlockViewRenderer @Inject constructor( } else { null } + val cellIndex = columnIndex*rows.size + rowIndex cells.add( BlockView.Table.Cell( rowId = row.id, @@ -2058,7 +2062,9 @@ class DefaultBlockViewRenderer @Inject constructor( columnId = column.id, columnIndex = BlockView.Table.ColumnIndex(columnIndex), isHeader = isHeader, - block = paragraph + tableId = tableId, + block = paragraph, + cellIndex = cellIndex ) ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt new file mode 100644 index 0000000000..b309b833cb --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellExt.kt @@ -0,0 +1,378 @@ +package com.anytypeio.anytype.presentation.editor.selection + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView +import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableWidgetItem + +fun updateTableCellsSelectionState( + cellId: Id, + rowIndex: BlockView.Table.RowIndex, + columnIndex: BlockView.Table.ColumnIndex, + selectionState: List, + cellIndex: Int +): List { + + val latestSelection = BlockView.Table.CellSelection( + cellId = cellId, + rowIndex = rowIndex, + columnIndex = columnIndex, + left = true, + top = true, + right = true, + bottom = true, + cellIndex = cellIndex + ) + + val latestSelectionIndex = selectionState.indexOfFirst { it.cellId == cellId } + return if (latestSelectionIndex != -1) { + //Latest selection is already in selection state - move on to deselecting a cell + val newState = selectionState.toMutableList() + newState.removeAt(latestSelectionIndex) + newState.restoreBordersVisibility(deselectedSelection = latestSelection) + newState + } else { + //Latest selection is not in selection state - move on to adding new selection to state + selectionState.forEach { selection -> + if (isLeftBorderMatch(cell1 = selection, cell2 = latestSelection)) { + selection.left = false + latestSelection.right = false + } + + if (isRightBorderMatch(cell1 = selection, cell2 = latestSelection)) { + selection.right = false + latestSelection.left = false + } + + if (isTopBorderMatch(cell1 = selection, cell2 = latestSelection)) { + selection.top = false + latestSelection.bottom = false + } + + if (isBottomBorderMatch(cell1 = selection, cell2 = latestSelection)) { + selection.bottom = false + latestSelection.top = false + } + } + val newState = mutableListOf().apply { + addAll(selectionState) + add(latestSelection) + } + newState + } +} + +/** + * In the case of deselecting the cell it is necessary + * to restore the borders of the remaining selected cells + */ +fun List.restoreBordersVisibility(deselectedSelection: BlockView.Table.CellSelection): List = + map { cellSelection -> + if (isLeftBorderMatch(cell1 = cellSelection, cell2 = deselectedSelection)) { + cellSelection.left = true + } + if (isRightBorderMatch(cell1 = cellSelection, cell2 = deselectedSelection)) { + cellSelection.right = true + } + if (isTopBorderMatch(cell1 = cellSelection, cell2 = deselectedSelection)) { + cellSelection.top = true + } + if (isBottomBorderMatch(cell1 = cellSelection, cell2 = deselectedSelection)) { + cellSelection.bottom = true + } + cellSelection + } + +/** + * Check that the left border of the first cell coincides with the right border of the second cell + * |cell2|cell1| + */ +fun isLeftBorderMatch( + cell1: BlockView.Table.CellSelection, + cell2: BlockView.Table.CellSelection +): Boolean { + val isSameRow = cell1.rowIndex.value == cell2.rowIndex.value + if (!isSameRow) return false + return cell1.columnIndex.value == (cell2.columnIndex.value + 1) +} + +/** + * Check that the right border of the first cell coincides with the left border of the second cell + * |cell1|cell2| + */ +fun isRightBorderMatch( + cell1: BlockView.Table.CellSelection, + cell2: BlockView.Table.CellSelection +): Boolean { + val isSameRow = cell1.rowIndex.value == cell2.rowIndex.value + if (!isSameRow) return false + return cell2.columnIndex.value == (cell1.columnIndex.value + 1) +} + +/** + * Check that the bottom border of the first cell coincides with the top border of the second cell + * cell1 + * ------ + * cell2 + */ +fun isBottomBorderMatch( + cell1: BlockView.Table.CellSelection, + cell2: BlockView.Table.CellSelection +): Boolean { + val isSameColumn = cell1.columnIndex.value == cell2.columnIndex.value + if (!isSameColumn) return false + return cell1.rowIndex.value + 1 == cell2.rowIndex.value +} + +/** + * Check that the top border of the first cell coincides with the top bottom of the second cell + * cell2 + * ------ + * cell1 + */ +fun isTopBorderMatch( + cell1: BlockView.Table.CellSelection, + cell2: BlockView.Table.CellSelection +): Boolean { + val isSameColumn = cell1.columnIndex.value == cell2.columnIndex.value + if (!isSameColumn) return false + return cell1.rowIndex.value == cell2.rowIndex.value + 1 +} + +fun List.toggleTableMode( + cellsMode: BlockView.Mode, + selectedCellsIds: List +): List { + return map { view -> + when (view) { + is BlockView.Text.Paragraph -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Checkbox -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Bulleted -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Numbered -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Highlight -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Callout -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Header.One -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Header.Two -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Header.Three -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Text.Toggle -> view.copy( + mode = cellsMode, + isSelected = false, + isFocused = false, + cursor = null + ) + is BlockView.Code -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Error.File -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Error.Video -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Error.Picture -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Error.Bookmark -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Upload.File -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Upload.Video -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Upload.Picture -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.MediaPlaceholder.File -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.MediaPlaceholder.Video -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.MediaPlaceholder.Bookmark -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.MediaPlaceholder.Picture -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Media.File -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Media.Video -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Media.Bookmark -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Media.Picture -> view.copy( + mode = cellsMode, + isSelected = false + ) + is BlockView.Title.Basic -> view.copy( + mode = cellsMode + ) + is BlockView.Title.Profile -> view.copy( + mode = cellsMode + ) + is BlockView.Title.Todo -> view.copy( + mode = cellsMode + ) + is BlockView.Title.Archive -> view.copy( + mode = cellsMode + ) + is BlockView.Description -> view.copy( + mode = cellsMode + ) + is BlockView.Relation.Placeholder -> view.copy( + isSelected = false + ) + is BlockView.Relation.Related -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Default.Text -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Default.Card.SmallIcon -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Default.Card.MediumIcon -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Default.Card.SmallIconCover -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Default.Card.MediumIconCover -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Archived -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Deleted -> view.copy( + isSelected = false + ) + is BlockView.LinkToObject.Loading -> view.copy( + isSelected = false + ) + is BlockView.DividerDots -> view.copy( + isSelected = false + ) + is BlockView.DividerLine -> view.copy( + isSelected = false + ) + is BlockView.Latex -> view.copy( + isSelected = false + ) + is BlockView.TableOfContents -> view.copy( + isSelected = false + ) + is BlockView.Table -> { + view.copy( + isSelected = false, + cells = view.cells.updateCellsMode(mode = cellsMode), + selectedCellsIds = selectedCellsIds + ) + } + is BlockView.FeaturedRelation -> view + is BlockView.Unsupported -> view.copy(isSelected = false) + is BlockView.Upload.Bookmark -> view.copy(isSelected = false) + } + } +} + +fun List.updateCellsMode( + mode: BlockView.Mode +): List = map { cell -> + val block = cell.block + if (block == null) { + cell + } else { + cell.copy( + block = block.copy( + mode = mode, + isSelected = false, + isFocused = false, + cursor = null + ) + ) + } +} + +fun List.updateTableBlockSelection(tableId: Id, selection: List): List = + map { + if (it.id == tableId && it is BlockView.Table) { + it.copy( + selectedCellsIds = selection + ) + } else { + it + } + } + +fun List.getSimpleTableWidgetItems(): List { + return listOf( + SimpleTableWidgetItem.Cell.ClearContents +// SimpleTableWidgetItem.Cell.Style, +// SimpleTableWidgetItem.Cell.Color, +// SimpleTableWidgetItem.Cell.ClearStyle + ) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellsSelectionState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellsSelectionState.kt new file mode 100644 index 0000000000..32491bcb62 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/selection/TableCellsSelectionState.kt @@ -0,0 +1,27 @@ +package com.anytypeio.anytype.presentation.editor.selection + +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView + +class TableCellsSelectionState { + + private var memory = listOf() + + fun set(cells: List) { + cells.forEach { cell -> + val currentSelection = current() + memory = updateTableCellsSelectionState( + cellId = cell.getId(), + rowIndex = cell.rowIndex, + columnIndex = cell.columnIndex, + selectionState = currentSelection, + cellIndex = cell.cellIndex + ) + } + } + + fun clear() { + memory = emptyList() + } + + fun current(): List = memory +} diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt index 38c50c22b7..b748015e5f 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt @@ -27,6 +27,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result import com.anytypeio.anytype.domain.block.UpdateDivider +import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.DuplicateBlock import com.anytypeio.anytype.domain.block.interactor.MergeBlocks @@ -101,8 +102,6 @@ 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.render.parseThemeBackgroundColor import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder @@ -327,8 +326,6 @@ open class EditorViewModelTest { private lateinit var editorTemplateDelegate: EditorTemplateDelegate - private lateinit var simpleTableDelegate: SimpleTableDelegate - @Mock lateinit var createNewObject: CreateNewObject @@ -344,6 +341,7 @@ open class EditorViewModelTest { private lateinit var setDocCoverImage: SetDocCoverImage private lateinit var setDocImageIcon: SetDocumentImageIcon private lateinit var objectToSet: ConvertObjectToSet + private lateinit var clearBlockContent: ClearBlockContent val root = MockDataFactory.randomUuid() @@ -377,7 +375,6 @@ open class EditorViewModelTest { getTemplates = getTemplates, applyTemplate = applyTemplate ) - simpleTableDelegate = DefaultSimpleTableDelegate() } @Test @@ -3942,6 +3939,7 @@ open class EditorViewModelTest { setDocCoverImage = SetDocCoverImage(repo) setDocImageIcon = SetDocumentImageIcon(repo) downloadUnsplashImage = DownloadUnsplashImage(unsplashRepo) + clearBlockContent = ClearBlockContent(repo) vm = EditorViewModel( openPage = openPage, @@ -4000,7 +3998,8 @@ open class EditorViewModelTest { updateBlocksMark = updateBlocksMark, setObjectType = setObjectType, createTable = createTable, - fillTableRow = fillTableRow + fillTableRow = fillTableRow, + clearBlockContent = clearBlockContent ), analytics = analytics, dispatcher = Dispatcher.Default(), @@ -4018,7 +4017,6 @@ open class EditorViewModelTest { setDocCoverImage = setDocCoverImage, setDocImageIcon = setDocImageIcon, templateDelegate = editorTemplateDelegate, - simpleTableDelegate = simpleTableDelegate, createNewObject = createNewObject, objectToSet = objectToSet ) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt index 3310efa624..e164b72e80 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result import com.anytypeio.anytype.domain.block.UpdateDivider +import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.DuplicateBlock import com.anytypeio.anytype.domain.block.interactor.MergeBlocks @@ -75,8 +76,6 @@ 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.DefaultEditorTemplateDelegate @@ -265,8 +264,6 @@ open class EditorPresentationTestSetup { @Mock lateinit var fillTableRow: FillTableRow - lateinit var simpleTableDelegate: SimpleTableDelegate - lateinit var editorTemplateDelegate: EditorTemplateDelegate protected val builder: UrlBuilder get() = UrlBuilder(gateway) @@ -276,6 +273,7 @@ open class EditorPresentationTestSetup { private lateinit var setDocCoverImage: SetDocCoverImage private lateinit var setDocImageIcon: SetDocumentImageIcon private lateinit var objectToSet: ConvertObjectToSet + private lateinit var clearBlockContent: ClearBlockContent open lateinit var orchestrator: Orchestrator @@ -293,11 +291,11 @@ open class EditorPresentationTestSetup { setDocCoverImage = SetDocCoverImage(repo) setDocImageIcon = SetDocumentImageIcon(repo) downloadUnsplashImage = DownloadUnsplashImage(unsplashRepo) - simpleTableDelegate = DefaultSimpleTableDelegate() editorTemplateDelegate = DefaultEditorTemplateDelegate( getTemplates = getTemplates, applyTemplate = applyTemplate ) + clearBlockContent = ClearBlockContent(repo) orchestrator = Orchestrator( createBlock = createBlock, @@ -339,7 +337,8 @@ open class EditorPresentationTestSetup { updateBlocksMark = updateBlocksMark, setObjectType = setObjectType, createTable = createTable, - fillTableRow = fillTableRow + fillTableRow = fillTableRow, + clearBlockContent = clearBlockContent ) return EditorViewModel( @@ -376,7 +375,6 @@ open class EditorPresentationTestSetup { setDocCoverImage = setDocCoverImage, setDocImageIcon = setDocImageIcon, templateDelegate = editorTemplateDelegate, - simpleTableDelegate = simpleTableDelegate, createNewObject = createNewObject, objectToSet = objectToSet ) diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/ext/TableCellsSelectionStateTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/ext/TableCellsSelectionStateTest.kt new file mode 100644 index 0000000000..030fca1989 --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/ext/TableCellsSelectionStateTest.kt @@ -0,0 +1,369 @@ +package com.anytypeio.anytype.presentation.editor.editor.ext + +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView.Table.CellSelection +import com.anytypeio.anytype.presentation.editor.selection.TableCellsSelectionState +import com.anytypeio.anytype.test_utils.MockDataFactory +import org.junit.Test +import kotlin.test.assertEquals + +class TableCellsSelectionStateTest { + + val tableId = MockDataFactory.randomUuid() + + @Test + fun `when click on cell expecting selected state with this cell`() { + + //SETUP + val cellsSelectionState = TableCellsSelectionState() + + //TESTING + //click on cell row1, column1 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + block = null, + cellIndex = 22, + tableId = tableId + ) + ) + ) + + val actual = cellsSelectionState.current() + + //EXPECTED + val expected = listOf( + CellSelection( + cellId = "row1-column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + left = true, + top = true, + right = true, + bottom = true, + cellIndex = 22 + ) + ) + + //ASSERT + assertEquals(expected, actual) + } + + @Test + fun `when clicking on the same sell expecting empty state`() { + + //SETUP + val cellsSelectionState = TableCellsSelectionState() + + //TESTING + //click on cell row1, column1 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + block = null, + tableId = tableId, + cellIndex = 11 + ) + ) + ) + + //second click on cell row1, column1 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + block = null, + cellIndex = 11, + tableId = tableId + ) + ) + ) + + val actual = cellsSelectionState.current() + + //EXPECTED + val expected = emptyList() + + //ASSERT + assertEquals(expected, actual) + } + + @Test + fun `when clicking on different cells expecting proper borders visibility`() { + + //SETUP + val cellsSelectionState = TableCellsSelectionState() + + //TESTING + //click on cell row1, column0 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column0", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(0), + block = null, + cellIndex = 1, + tableId = tableId + ) + ) + ) + //click on cell row1, column2 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column2", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(2), + block = null, + cellIndex = 7, + tableId = tableId + ) + ) + ) + + //click on cell row2, column0 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row2", + columnId = "column0", + rowIndex = BlockView.Table.RowIndex(2), + columnIndex = BlockView.Table.ColumnIndex(0), + block = null, + cellIndex = 2, + tableId = tableId + ) + ) + ) + + //click on cell row0, column2 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row0", + columnId = "column2", + rowIndex = BlockView.Table.RowIndex(0), + columnIndex = BlockView.Table.ColumnIndex(2), + block = null, + cellIndex = 6, + tableId = tableId + ) + ) + ) + + //click on cell row1, column1 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + block = null, + cellIndex = 4, + tableId = tableId + ) + ) + ) + + val actual = cellsSelectionState.current() + + //EXPECTED + val expected = listOf( + CellSelection( + cellId = "row1-column0", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(0), + left = true, + top = true, + right = false, + bottom = false, + cellIndex = 1 + ), + CellSelection( + cellId = "row1-column2", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(2), + left = false, + top = false, + right = true, + bottom = true, + cellIndex = 7 + ), + CellSelection( + cellId = "row2-column0", + rowIndex = BlockView.Table.RowIndex(2), + columnIndex = BlockView.Table.ColumnIndex(0), + left = true, + top = false, + right = true, + bottom = true, + cellIndex = 2 + ), + CellSelection( + cellId = "row0-column2", + rowIndex = BlockView.Table.RowIndex(0), + columnIndex = BlockView.Table.ColumnIndex(2), + left = true, + top = true, + right = true, + bottom = false, + cellIndex = 6 + ), + CellSelection( + cellId = "row1-column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + left = false, + top = true, + right = false, + bottom = true, + cellIndex = 4 + ) + ) + + //ASSERT + assertEquals(5, actual.size, message = "Size of selected cells expected 5") + assertEquals(expected, actual) + + //TESTING + //click on cell row1, column1 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row1", + columnId = "column1", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(1), + block = null, + cellIndex = 4, + tableId = tableId + ) + ) + ) + + val actual2 = cellsSelectionState.current() + + //EXPECTED + val expected2 = listOf( + CellSelection( + cellId = "row1-column0", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(0), + left = true, + top = true, + right = true, + bottom = false, + cellIndex = 1 + ), + CellSelection( + cellId = "row1-column2", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(2), + left = true, + top = false, + right = true, + bottom = true, + cellIndex = 7 + ), + CellSelection( + cellId = "row2-column0", + rowIndex = BlockView.Table.RowIndex(2), + columnIndex = BlockView.Table.ColumnIndex(0), + left = true, + top = false, + right = true, + bottom = true, + cellIndex = 2 + ), + CellSelection( + cellId = "row0-column2", + rowIndex = BlockView.Table.RowIndex(0), + columnIndex = BlockView.Table.ColumnIndex(2), + left = true, + top = true, + right = true, + bottom = false, + cellIndex = 6 + ) + ) + + //ASSERT + assertEquals(4, actual2.size, message = "Size of selected cells expected 4") + assertEquals(expected2, actual2) + + //TESTING + //click on cell row0, column2 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row0", + columnId = "column2", + rowIndex = BlockView.Table.RowIndex(0), + columnIndex = BlockView.Table.ColumnIndex(2), + block = null, + cellIndex = 6, + tableId = tableId + ) + ) + ) + //click on cell row0, column2 + cellsSelectionState.set( + cells = listOf( + BlockView.Table.Cell( + rowId = "row2", + columnId = "column0", + rowIndex = BlockView.Table.RowIndex(2), + columnIndex = BlockView.Table.ColumnIndex(0), + block = null, + cellIndex = 2, + tableId = tableId + ) + ) + ) + + val actual3 = cellsSelectionState.current() + + //EXPECTED + val expected3 = listOf( + CellSelection( + cellId = "row1-column0", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(0), + left = true, + top = true, + right = true, + bottom = true, + cellIndex = 1 + ), + CellSelection( + cellId = "row1-column2", + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(2), + left = true, + top = true, + right = true, + bottom = true, + cellIndex = 7 + ) + ) + + //ASSERT + assertEquals(2, actual3.size, message = "Size of selected cells expected 2") + assertEquals(expected3, actual3) + } +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableBlockTest.kt index ab6c1133c5..3bf0ab8f4e 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/EditorTableBlockTest.kt @@ -4,15 +4,18 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule 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.StubParagraph import com.anytypeio.anytype.core_models.StubSmartBlock import com.anytypeio.anytype.core_models.StubTable import com.anytypeio.anytype.core_models.StubTableCells import com.anytypeio.anytype.core_models.StubTableColumns import com.anytypeio.anytype.core_models.StubTableRows import com.anytypeio.anytype.core_models.StubTitle +import com.anytypeio.anytype.core_models.ext.content import com.anytypeio.anytype.domain.table.FillTableRow import com.anytypeio.anytype.presentation.editor.editor.EditorPresentationTestSetup import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import kotlinx.coroutines.runBlocking import org.junit.Before @@ -41,7 +44,10 @@ class EditorTableBlockTest : EditorPresentationTestSetup() { val columns = StubTableColumns(size = 3) val rows = StubTableRows(size = 2) - val cells = StubTableCells(columns = listOf(), rows = listOf()) + val cells = StubTableCells( + columns = listOf(columns[0], columns[1], columns[2]), + rows = listOf(rows[0], rows[1]) + ) val columnLayout = StubLayoutColumns(children = columns.map { it.id }) val rowLayout = StubLayoutRows(children = rows.map { it.id }) val table = StubTable(children = listOf(columnLayout.id, rowLayout.id)) @@ -62,24 +68,33 @@ class EditorTableBlockTest : EditorPresentationTestSetup() { val vm = buildViewModel() - val cell1Id = "${rows[0].id}-${columns[1].id}" - val cell2Id = "${rows[1].id}-${columns[0].id}" - vm.onStart(root) vm.apply { onClickListener( ListenerType.TableEmptyCell( - cellId = cell1Id, - rowId = rows[0].id, - tableId = table.id + cell = BlockView.Table.Cell( + rowId = rows[0].id, + columnId = columns[1].id, + rowIndex = BlockView.Table.RowIndex(0), + columnIndex = BlockView.Table.ColumnIndex(1), + block = null, + cellIndex = 0, + tableId = table.id + ) ) ) onClickListener( ListenerType.TableEmptyCell( - cellId = cell2Id, - rowId = rows[1].id, - tableId = table.id + cell = BlockView.Table.Cell( + rowId = rows[1].id, + columnId = columns[0].id, + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(0), + block = null, + cellIndex = 1, + tableId = table.id + ) ) ) } @@ -98,7 +113,7 @@ class EditorTableBlockTest : EditorPresentationTestSetup() { } @Test - fun `should amend second text cell click`() { + fun `should not amend second text cell click`() { val columns = StubTableColumns(size = 3) val rows = StubTableRows(size = 2) @@ -121,22 +136,39 @@ class EditorTableBlockTest : EditorPresentationTestSetup() { stubOpenDocument(document) val vm = buildViewModel() - val cell1Id = "${rows[0].id}-${columns[1].id}" - val cell2Id = "${rows[1].id}-${columns[0].id}" - vm.onStart(root) vm.apply { onClickListener( ListenerType.TableTextCell( - cellId = cell1Id, - tableId = table.id + cell = BlockView.Table.Cell( + rowId = rows[0].id, + columnId = columns[1].id, + rowIndex = BlockView.Table.RowIndex(0), + columnIndex = BlockView.Table.ColumnIndex(1), + block = BlockView.Text.Paragraph( + id = cells[0].id, + text = cells[0].content.asText().text + ), + cellIndex = 0, + tableId = table.id + ) ) ) onClickListener( ListenerType.TableTextCell( - cellId = cell2Id, - tableId = table.id + cell = BlockView.Table.Cell( + rowId = rows[1].id, + columnId = columns[0].id, + rowIndex = BlockView.Table.RowIndex(1), + columnIndex = BlockView.Table.ColumnIndex(0), + block = BlockView.Text.Paragraph( + id = cells[1].id, + text = cells[1].content.asText().text + ), + cellIndex = 1, + tableId = table.id + ) ) ) } @@ -144,7 +176,6 @@ class EditorTableBlockTest : EditorPresentationTestSetup() { val selectedState = vm.currentSelection() runBlocking { assertEquals(1, selectedState.size) - assertEquals(cell1Id, selectedState.first()) verifyNoInteractions(fillTableRow) } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt index bf5a4c3e27..4e6065bab4 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/table/TableBlockRendererTest.kt @@ -43,7 +43,8 @@ class TableBlockRendererTest { class BlockViewRenderWrapper( private val blocks: Map>, private val renderer: BlockViewRenderer, - private val restrictions: List = emptyList() + private val restrictions: List = emptyList(), + private val selections: Set = emptySet() ) : BlockViewRenderer by renderer { suspend fun render( root: Block, @@ -59,7 +60,7 @@ class TableBlockRendererTest { details = details, relations = emptyList(), restrictions = restrictions, - selection = emptySet(), + selection = selections, objectTypes = listOf() ) } @@ -199,7 +200,9 @@ class TableBlockRendererTest { rowId = row.id, columnId = column.id, rowIndex = BlockView.Table.RowIndex(rowIndex), - columnIndex = BlockView.Table.ColumnIndex(columnIndex) + columnIndex = BlockView.Table.ColumnIndex(columnIndex), + cellIndex = columnIndex*rows.size + rowIndex, + tableId = table.id ) ) } @@ -243,7 +246,8 @@ class TableBlockRendererTest { cells = cells, columns = columnViews, rowCount = rowsSize, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) + blocksDown.mapIndexed { idx, block -> BlockView.Text.Numbered( @@ -351,7 +355,9 @@ class TableBlockRendererTest { columnId = column.id, rowIndex = BlockView.Table.RowIndex(rowIndex), columnIndex = BlockView.Table.ColumnIndex(columnIndex), - block = null + block = null, + cellIndex = columnIndex*rows.size + rowIndex, + tableId = table.id ) ) } @@ -395,7 +401,8 @@ class TableBlockRendererTest { cells = cells, columns = columnViews, rowCount = rowsSize, - isSelected = false + isSelected = false, + selectedCellsIds = emptyList() ) ) + blocksDown.mapIndexed { idx, block -> BlockView.Text.Numbered( @@ -499,7 +506,8 @@ class TableBlockRendererTest { wrapper = BlockViewRenderWrapper( blocks = map, - renderer = renderer + renderer = renderer, + selections = setOf("$rowId2-$columnId1") ) val result = runBlocking { @@ -519,7 +527,9 @@ class TableBlockRendererTest { columnId = columnId1, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(0), - block = null + block = null, + cellIndex = 0, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId2, @@ -529,14 +539,18 @@ class TableBlockRendererTest { text = row2Block1.content.asText().text ), rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(0) + columnIndex = BlockView.Table.ColumnIndex(0), + cellIndex = 1, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId3, columnId = columnId1, rowIndex = BlockView.Table.RowIndex(2), columnIndex = BlockView.Table.ColumnIndex(0), - block = null + block = null, + cellIndex = 2, + tableId = table.id ), //column1 BlockView.Table.Cell( rowId = rowId1, @@ -546,7 +560,9 @@ class TableBlockRendererTest { text = row1Block1.content.asText().text ), rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 3, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId2, @@ -556,35 +572,45 @@ class TableBlockRendererTest { text = row2Block2.content.asText().text ), rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(1) + columnIndex = BlockView.Table.ColumnIndex(1), + cellIndex = 4, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId3, columnId = columnId2, rowIndex = BlockView.Table.RowIndex(2), columnIndex = BlockView.Table.ColumnIndex(1), - block = null + block = null, + cellIndex = 5, + tableId = table.id ),//column2 BlockView.Table.Cell( rowId = rowId1, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(0), columnIndex = BlockView.Table.ColumnIndex(2), - block = null + block = null, + cellIndex = 6, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId2, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(1), columnIndex = BlockView.Table.ColumnIndex(2), - block = null + block = null, + cellIndex = 7, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId3, columnId = columnId3, rowIndex = BlockView.Table.RowIndex(2), columnIndex = BlockView.Table.ColumnIndex(2), - block = null + block = null, + cellIndex = 8, + tableId = table.id ),//column3 BlockView.Table.Cell( rowId = rowId1, @@ -594,7 +620,9 @@ class TableBlockRendererTest { text = row1Block2.content.asText().text ), rowIndex = BlockView.Table.RowIndex(0), - columnIndex = BlockView.Table.ColumnIndex(3) + columnIndex = BlockView.Table.ColumnIndex(3), + cellIndex = 9, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId2, @@ -604,15 +632,19 @@ class TableBlockRendererTest { text = row2Block3.content.asText().text ), rowIndex = BlockView.Table.RowIndex(1), - columnIndex = BlockView.Table.ColumnIndex(3) + columnIndex = BlockView.Table.ColumnIndex(3), + cellIndex = 10, + tableId = table.id ), BlockView.Table.Cell( rowId = rowId3, columnId = columnId4, rowIndex = BlockView.Table.RowIndex(2), columnIndex = BlockView.Table.ColumnIndex(3), - block = null - ), + block = null, + cellIndex = 11, + tableId = table.id + ) ) val columnViews = mutableListOf() @@ -653,7 +685,8 @@ class TableBlockRendererTest { cells = cells, columns = columnViews, rowCount = rowsSize, - isSelected = false + isSelected = false, + selectedCellsIds = listOf("$rowId2-$columnId1") ) ) + blocksDown.mapIndexed { idx, block -> BlockView.Text.Numbered(