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

DRAFT-718 Editor | Enhancement | Inline set block (#2864)

* DROID-718 Editor | Enhancement | Inline set block (#2813)

* DROID-718 data view blocks, xml

* DROID-718 add content description

* DROID-718 data view holders

* DROID-718 add holders to editor adapter

* DROID-718 blockview, data view

* DROID-718 block data view, mapping

* DROID-718 target object id

* DROID-718 target object id mapping

* DROID-718 design fix

* DROID-718 fix

* DROID-718 add default value

* DROID-718 fixes

* DROID-718 rename

* DROID-718 pr fix

* DROID-794 Editor | Enhancement | Inline set, open source icon (#2830)

* DROID-794 open source icon added

* DROID-794 fix toast

* DROID-794 refactoring

* DROID-718 Editor | Enhancement | Set View, fixes (#2834)

* DROID-718 rename Inline set -> Set View

* DROID-718 set view blocks ui fixes

* DROID-718 legacy

* DROID-718 legacy

* DROID-718 pr fix

* DROID-718 Editor | Fix | Rename to inline sets (#2863)

* test fix
This commit is contained in:
Konstantin Ivanov 2023-01-25 14:42:49 +01:00 committed by GitHub
parent d94b07a338
commit d670823b7a
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 885 additions and 27 deletions

View file

@ -288,7 +288,8 @@ data class Block(
val viewers: List<Viewer>,
@Deprecated("To be deleted")
val relations: List<Relation>,
val relationsIndex: List<RelationLink> = emptyList()
val relationsIndex: List<RelationLink> = emptyList(),
val targetObjectId: Id = "",
) : Content() {
data class Viewer(

View file

@ -21,6 +21,9 @@ import com.anytypeio.anytype.core_ui.databinding.ItemBlockBulletedBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockCalloutBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockCheckboxBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockCodeSnippetBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDataViewDefaultBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDataViewEmptyDataBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDataViewEmptySourceBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDescriptionBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDividerDotsBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDividerLineBinding
@ -67,6 +70,9 @@ import com.anytypeio.anytype.core_ui.databinding.ItemBlockVideoErrorBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockVideoUploadingBinding
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil.Payload
import com.anytypeio.anytype.core_ui.features.editor.decoration.DecoratableViewHolder
import com.anytypeio.anytype.core_ui.features.editor.holders.dataview.DataViewBlockDefaultHolder
import com.anytypeio.anytype.core_ui.features.editor.holders.dataview.DataViewBlockEmptyDataHolder
import com.anytypeio.anytype.core_ui.features.editor.holders.dataview.DataViewBlockEmptySourceHolder
import com.anytypeio.anytype.core_ui.features.editor.holders.`interface`.TextHolder
import com.anytypeio.anytype.core_ui.features.editor.holders.error.BookmarkError
import com.anytypeio.anytype.core_ui.features.editor.holders.error.FileError
@ -136,6 +142,9 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_CALLOUT
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_CHECKBOX
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_CODE_SNIPPET
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DATA_VIEW_DEFAULT
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DATA_VIEW_EMPTY_DATA
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DATA_VIEW_EMPTY_SOURCE
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DESCRIPTION
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DIVIDER_DOTS
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DIVIDER_LINE
@ -811,6 +820,21 @@ class BlockAdapter(
clipboardInterceptor = clipboardInterceptor,
onDragAndDropTrigger = onDragAndDropTrigger
)
HOLDER_DATA_VIEW_EMPTY_SOURCE -> {
DataViewBlockEmptySourceHolder(
ItemBlockDataViewEmptySourceBinding.inflate(inflater, parent, false)
)
}
HOLDER_DATA_VIEW_EMPTY_DATA -> {
DataViewBlockEmptyDataHolder(
ItemBlockDataViewEmptyDataBinding.inflate(inflater, parent, false)
)
}
HOLDER_DATA_VIEW_DEFAULT -> {
DataViewBlockDefaultHolder(
ItemBlockDataViewDefaultBinding.inflate(inflater, parent, false)
)
}
else -> throw IllegalStateException("Unexpected view type: $viewType")
}
@ -1224,6 +1248,24 @@ class BlockAdapter(
item = blocks[position] as BlockView.Table
)
}
is DataViewBlockDefaultHolder -> {
holder.processChangePayloads(
payloads = payloads.typeOf(),
item = blocks[position] as BlockView.DataView.Default
)
}
is DataViewBlockEmptySourceHolder -> {
holder.processChangePayloads(
payloads = payloads.typeOf(),
item = blocks[position] as BlockView.DataView.EmptySource
)
}
is DataViewBlockEmptyDataHolder -> {
holder.processChangePayloads(
payloads = payloads.typeOf(),
item = blocks[position] as BlockView.DataView.EmptyData
)
}
else -> throw IllegalStateException("Unexpected view holder: $holder")
}
checkIfDecorationChanged(holder, payloads.typeOf(), position)
@ -1544,6 +1586,24 @@ class BlockAdapter(
is TableBlockHolder -> {
holder.bind(item = blocks[position] as BlockView.Table)
}
is DataViewBlockEmptySourceHolder -> {
holder.bind(
item = blocks[position] as BlockView.DataView.EmptySource,
clicked = onClickListener
)
}
is DataViewBlockEmptyDataHolder -> {
holder.bind(
item = blocks[position] as BlockView.DataView.EmptyData,
clicked = onClickListener
)
}
is DataViewBlockDefaultHolder -> {
holder.bind(
item = blocks[position] as BlockView.DataView.Default,
clicked = onClickListener
)
}
}
if (holder is Text<*>) {

View file

@ -259,6 +259,18 @@ class BlockViewDiffUtil(
}
}
if (newBlock is BlockView.DataView && oldBlock is BlockView.DataView) {
if (newBlock.title != oldBlock.title) {
changes.add(DATA_VIEW_TITLE_CHANGED)
}
if (newBlock.icon != oldBlock.icon) {
changes.add(DATA_VIEW_ICON_CHANGED)
}
if (newBlock.background != oldBlock.background) {
changes.add(BACKGROUND_COLOR_CHANGED)
}
}
return if (changes.isNotEmpty())
Payload(changes).also { Timber.d("Returning payload: $it") }
else
@ -301,6 +313,10 @@ class BlockViewDiffUtil(
val isCalloutIconChanged: Boolean get() = changes.contains(CALLOUT_ICON_CHANGED)
val isDataViewTitleChanged : Boolean get() = changes.contains(DATA_VIEW_TITLE_CHANGED)
val isDataViewIconChanged : Boolean get() = changes.contains(DATA_VIEW_ICON_CHANGED)
val isDataViewBackgroundChanged : Boolean get() = changes.contains(DATA_VIEW_BACKGROUND_CHANGED)
fun markupChanged() = changes.contains(MARKUP_CHANGED)
fun textChanged() = changes.contains(TEXT_CHANGED)
fun textColorChanged() = changes.contains(TEXT_COLOR_CHANGED)
@ -351,5 +367,9 @@ class BlockViewDiffUtil(
const val TABLE_CELLS_SELECTION_CHANGED = 340
const val TABLE_CELLS_CHANGED = 341
const val DATA_VIEW_TITLE_CHANGED = 350
const val DATA_VIEW_ICON_CHANGED = 351
const val DATA_VIEW_BACKGROUND_CHANGED = 352
}
}

View file

@ -0,0 +1,273 @@
package com.anytypeio.anytype.core_ui.features.editor.holders.dataview
import android.text.Spannable
import android.text.SpannableString
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.SearchHighlightSpan
import com.anytypeio.anytype.core_ui.common.SearchTargetHighlightSpan
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDataViewDefaultBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDataViewEmptyDataBinding
import com.anytypeio.anytype.core_ui.databinding.ItemBlockDataViewEmptySourceBinding
import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
import com.anytypeio.anytype.core_ui.features.editor.EditorTouchProcessor
import com.anytypeio.anytype.core_ui.features.editor.SupportCustomTouchProcessor
import com.anytypeio.anytype.core_ui.features.editor.SupportNesting
import com.anytypeio.anytype.core_ui.features.editor.decoration.DecoratableCardViewHolder
import com.anytypeio.anytype.core_ui.features.editor.decoration.EditorDecorationContainer
import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.core_utils.ext.removeSpans
import com.anytypeio.anytype.core_utils.ext.visible
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.objects.ObjectIcon
class DataViewBlockEmptySourceHolder(binding: ItemBlockDataViewEmptySourceBinding) :
DataViewBlockViewHolder(binding.root) {
override val decoratableContainer: EditorDecorationContainer = binding.decorationContainer
override val rootView: View = binding.root
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
init {
itemView.setOnTouchListener { v, e -> editorTouchProcessor.process(v, e) }
}
fun bind(
item: BlockView.DataView.EmptySource,
clicked: (ListenerType) -> Unit
) {
super.bind(item = item, clicked = clicked)
}
fun processChangePayloads(
payloads: List<BlockViewDiffUtil.Payload>,
item: BlockView.DataView
) {
payloads.forEach { payload ->
processChangeBasePayloads(payload, item)
}
}
}
class DataViewBlockEmptyDataHolder(binding: ItemBlockDataViewEmptyDataBinding) :
DataViewBlockViewHolder(binding.root) {
override val decoratableContainer: EditorDecorationContainer = binding.decorationContainer
override val rootView: View = binding.root
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
init {
itemView.setOnTouchListener { v, e -> editorTouchProcessor.process(v, e) }
}
fun bind(
item: BlockView.DataView.EmptyData,
clicked: (ListenerType) -> Unit
) {
super.bind(item = item, clicked = clicked)
}
fun processChangePayloads(
payloads: List<BlockViewDiffUtil.Payload>,
item: BlockView.DataView
) {
payloads.forEach { payload ->
processChangeBasePayloads(payload, item)
}
}
}
data class DataViewBlockDefaultHolder(
val binding: ItemBlockDataViewDefaultBinding
) : DataViewBlockViewHolder(binding.root) {
override val decoratableContainer: EditorDecorationContainer = binding.decorationContainer
override val rootView: View = binding.root
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
init {
itemView.setOnTouchListener { v, e -> editorTouchProcessor.process(v, e) }
}
fun bind(
item: BlockView.DataView.Default,
clicked: (ListenerType) -> Unit
) {
super.bind(item = item, clicked = clicked)
}
fun processChangePayloads(
payloads: List<BlockViewDiffUtil.Payload>,
item: BlockView.DataView
) {
payloads.forEach { payload ->
processChangeBasePayloads(payload, item)
}
}
}
sealed class DataViewBlockViewHolder(
view: View
) : BlockViewHolder(view),
BlockViewHolder.DragAndDropHolder,
DecoratableCardViewHolder,
SupportCustomTouchProcessor,
SupportNesting {
protected abstract val rootView: View
abstract val containerView: ConstraintLayout
private val untitled = itemView.resources.getString(R.string.untitled_set)
abstract val objectIconView: ObjectIconWidget
abstract val titleView: TextView
abstract val descriptionView: TextView
abstract val selectedView: View
abstract override val decoratableCard: CardView
override val editorTouchProcessor = EditorTouchProcessor(
fallback = { e -> itemView.onTouchEvent(e) }
)
protected fun bind(
item: BlockView.DataView,
clicked: (ListenerType) -> Unit
) {
selected(item)
applyName(item)
applyBackground(item.background)
applySearchHighlight(item)
applyImageOrEmoji(item)
itemView.setOnClickListener { clicked(ListenerType.DataViewClick(item.id)) }
}
private fun selected(item: BlockView.DataView) {
selectedView.isSelected = item.isSelected
}
private fun applyName(item: BlockView.DataView) {
val name = item.title
val sb = if (name.isNullOrBlank()) "" else SpannableString(name)
titleView.append(sb)
}
private fun applyImageOrEmoji(item: BlockView.DataView) {
when (item.icon) {
ObjectIcon.None -> {
objectIconView.gone()
}
else -> {
objectIconView.visible()
objectIconView.setIcon(item.icon)
}
}
}
private fun applySearchHighlight(item: BlockView.Searchable) {
item.searchFields.find { it.key == BlockView.Searchable.Field.DEFAULT_SEARCH_FIELD_KEY }
?.let { field ->
applySearchHighlight(field, titleView)
} ?: clearSearchHighlights()
}
private fun applySearchHighlight(field: BlockView.Searchable.Field, input: TextView) {
val content = input.text as Spannable
content.removeSpans<SearchHighlightSpan>()
content.removeSpans<SearchTargetHighlightSpan>()
field.highlights.forEach { highlight ->
content.setSpan(
SearchHighlightSpan(),
highlight.first,
highlight.last,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
if (field.isTargeted) {
content.setSpan(
SearchTargetHighlightSpan(),
field.target.first,
field.target.last,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
private fun clearSearchHighlights() {
titleView.editableText?.removeSpans<SearchHighlightSpan>()
titleView.editableText?.removeSpans<SearchTargetHighlightSpan>()
}
protected fun processChangeBasePayloads(
payload: BlockViewDiffUtil.Payload,
item: BlockView.DataView
) {
if (payload.isSelectionChanged) {
selected(item)
}
if (payload.isDataViewTitleChanged) {
applyName(item)
}
if (payload.isDataViewIconChanged) {
applyImageOrEmoji(item)
}
if (payload.isDataViewBackgroundChanged) {
applyBackground(item.background)
}
if (payload.isSearchHighlightChanged) {
applySearchHighlight(item)
}
}
private fun applyBackground(background: ThemeColor) {
containerView.setBlockBackgroundColor(background)
}
override fun applyDecorations(decorations: List<BlockView.Decoration>) {
super.applyDecorations(decorations)
decoratableContainer.decorate(decorations) { rect ->
rootView.updateLayoutParams<RecyclerView.LayoutParams> {
topMargin = if (rect.left == 0) {
itemView.resources.getDimension(R.dimen.dp_10).toInt()
} else {
0
}
}
selectedView.updateLayoutParams<FrameLayout.LayoutParams> {
val defaultIndentOffset =
itemView.resources.getDimension(R.dimen.default_indent).toInt()
leftMargin = defaultIndentOffset + rect.left
rightMargin = defaultIndentOffset + rect.right
}
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/shape_tertiary" />
<corners android:radius="3dp" />
</shape>

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="@dimen/block_data_view_height"
tools:context="com.anytypeio.anytype.core_ui.features.editor.holders.dataview.DataViewBlockDefaultHolder">
<com.anytypeio.anytype.core_ui.features.editor.decoration.EditorDecorationContainer
android:id="@+id/decorationContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
style="@style/BlockDataViewCardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/containerWithBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/cardName"
style="@style/BlockDataViewTitleStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/cardDescription"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cardIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginStart="@dimen/dp_16"
tools:text="Reading List"
tools:visibility="visible" />
<TextView
android:id="@+id/cardDescription"
style="@style/BlockDataViewSubtitleStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/inline_set"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardName" />
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/cardIcon"
style="@style/BlockDataViewIconStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<View
android:id="@+id/selected"
style="@style/BlockSelectionStyle" />
</FrameLayout>

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="@dimen/block_data_view_height"
tools:context="com.anytypeio.anytype.core_ui.features.editor.holders.dataview.DataViewBlockEmptyDataHolder">
<com.anytypeio.anytype.core_ui.features.editor.decoration.EditorDecorationContainer
android:id="@+id/decorationContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
style="@style/BlockDataViewCardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/containerWithBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/cardName"
style="@style/BlockDataViewTitleStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/cardDescription"
app:layout_constraintEnd_toStartOf="@+id/cardInfo"
app:layout_constraintStart_toEndOf="@+id/cardIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginStart="@dimen/dp_16"
tools:text="Reading List"
tools:visibility="visible" />
<TextView
android:id="@+id/cardDescription"
style="@style/BlockDataViewSubtitleStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/inline_set"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardName" />
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/cardIcon"
style="@style/BlockDataViewIconStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/cardInfo"
style="@style/BlockDataViewInfoStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_data"
android:contentDescription="@string/block_data_view_content_description_no_data"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<View
android:id="@+id/selected"
style="@style/BlockSelectionStyle" />
</FrameLayout>

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
style="@style/BlockDataViewRootStyle"
tools:context="com.anytypeio.anytype.core_ui.features.editor.holders.dataview.DataViewBlockEmptySourceHolder">
<com.anytypeio.anytype.core_ui.features.editor.decoration.EditorDecorationContainer
android:id="@+id/decorationContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
style="@style/BlockDataViewCardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/containerWithBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/cardName"
style="@style/BlockDataViewTitleStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/cardDescription"
app:layout_constraintEnd_toStartOf="@+id/cardInfo"
app:layout_constraintStart_toEndOf="@+id/cardIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginStart="@dimen/dp_16"
tools:text="Is your feature request related to a problem? Please describe."
tools:visibility="visible" />
<TextView
android:id="@+id/cardDescription"
style="@style/BlockDataViewSubtitleStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/inline_set"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardName" />
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/cardIcon"
style="@style/BlockDataViewIconStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/cardInfo"
style="@style/BlockDataViewInfoStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_source"
android:contentDescription="@string/block_data_view_content_description_no_source"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<View
android:id="@+id/selected"
style="@style/BlockSelectionStyle" />
</FrameLayout>

View file

@ -306,4 +306,6 @@
<dimen name="featured_relations_text_size">13sp</dimen>
<dimen name="block_data_view_height">96dp</dimen>
</resources>

View file

@ -174,6 +174,7 @@
<string name="untitled">Untitled</string>
<string name="untitled_set">Untitled set</string>
<string name="archived">Archived</string>
@ -487,6 +488,8 @@
<string name="name_type_page_icon">📄</string>
<string name="name_type_page_subtitle">Proto type to start with</string>
<string name="non_existent_object">Non-existent object</string>
<string name="no_data">No data</string>
<string name="no_source">No source</string>
<string name="deleted_object">Deleted object</string>
<string name="open_set">Open set</string>
<string name="deleted">Deleted</string>
@ -519,6 +522,8 @@
<string name="visible">Visible</string>
<string name="card">Card</string>
<string name="inline">Inline</string>
<string name="set_view">Set View</string>
<string name="inline_set">Inline set</string>
<string name="fit_image">Fit image</string>
<string name="image_preview">Image preview</string>
<string name="cover">Cover</string>
@ -563,6 +568,7 @@
<string name="simple_tables_widget_tab_row">Row</string>
<string name="simple_tables_widget_tab_column">Column</string>
<string name="open_object">Open object</string>
<string name="open_source">Open source</string>
<string name="reload_object_content">Reload object content</string>
<string name="open_link">Open link</string>
<string name="copy_email">Copy email</string>
@ -589,6 +595,9 @@
<string name="my_types">My types</string>
<string name="my_relations">My relations</string>
<string name="type_added">Type \"%1$s\" added to your library</string>
<string name="block_data_view_content_description_title">The name of the source set of the inline set</string>
<string name="block_data_view_content_description_no_source">The inline set has no source</string>
<string name="block_data_view_content_description_no_data">The source of the inline set has no type</string>
<string name="object_search_recently_opened_section_title">Recently opened</string>

View file

@ -1096,4 +1096,82 @@
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
<!-- Editor, data view block -->
<style name="BlockDataViewRootStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/block_data_view_height</item>
</style>
<style name="BlockDataViewCardStyle" parent="CardView">
<item name="cardCornerRadius">12dp</item>
<item name="cardElevation">0dp</item>
<item name="rippleColor">@android:color/transparent</item>
<item name="strokeColor">@color/background_highlighted</item>
<item name="strokeWidth">1dp</item>
<item name="android:layout_gravity">center_vertical</item>
<item name="android:layout_marginStart">@dimen/dp_12</item>
<item name="android:layout_marginEnd">@dimen/dp_12</item>
</style>
<style name="BlockDataViewTitleStyle">
<item name="android:fontFamily">@font/inter_bold</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:textSize">17sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:layout_marginStart">6dp</item>
<item name="android:layout_marginTop">18dp</item>
<item name="android:layout_marginEnd">16dp</item>
<item name="android:includeFontPadding">false</item>
<item name="android:hint">@string/untitled_set</item>
<item name="android:textColorHint">@color/text_tertiary</item>
<item name="android:contentDescription">@string/block_data_view_content_description_title</item>
</style>
<style name="BlockDataViewSubtitleStyle">
<item name="android:fontFamily">@font/inter_regular</item>
<item name="android:textColor">@color/text_secondary</item>
<item name="android:textSize">12sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:layout_marginStart">16dp</item>
<item name="android:layout_marginTop">4dp</item>
<item name="android:layout_marginEnd">16dp</item>
<item name="android:layout_marginBottom">16dp</item>
</style>
<style name="BlockDataViewInfoStyle">
<item name="android:fontFamily">@font/inter_regular</item>
<item name="android:textColor">@color/text_secondary</item>
<item name="android:textSize">11sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:background">@drawable/bg_rect_3_radius</item>
<item name="android:paddingTop">1dp</item>
<item name="android:paddingEnd">5dp</item>
<item name="android:paddingBottom">1dp</item>
<item name="android:paddingStart">5dp</item>
<item name="android:layout_marginTop">20dp</item>
<item name="android:layout_marginEnd">@dimen/dp_16</item>
</style>
<style name="BlockDataViewIconStyle">
<item name="android:layout_width">20dp</item>
<item name="android:layout_height">20dp</item>
<item name="android:layout_marginStart">16dp</item>
<item name="android:layout_marginTop">18dp</item>
<item name="checkboxSize">18dp</item>
<item name="emojiSize">18dp</item>
<item name="imageSize">18dp</item>
<item name="initialTextSize">11sp</item>
</style>
<style name="BlockSelectionStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<item name="android:background">@drawable/item_block_multi_select_mode_selector</item>
</style>
<!-- Editor, data view block -->
</resources>

View file

@ -354,7 +354,8 @@ fun MBlock.toCoreModelsDataView(): Block.Content.DataView {
sources = content.source,
viewers = content.views.map { it.toCoreModels() },
relations = content.relations.map { it.toCoreModels() },
relationsIndex = content.relationLinks.map { it.toCoreModels() }
relationsIndex = content.relationLinks.map { it.toCoreModels() },
targetObjectId = content.TargetObjectId
)
}

View file

@ -3022,12 +3022,27 @@ class EditorViewModel(
sendToast("Couldn't find the target of the link")
}
}
is Content.DataView -> {
proceedWithOpeningInlineSetTarget(target = content.targetObjectId)
}
else -> {
sendToast("Couldn't find the target of the link")
}
}
}
private fun proceedWithOpeningInlineSetTarget(target: Id) {
if (target.isNotEmpty()) {
proceedWithOpeningSet(target)
viewModelScope.sendAnalyticsOpenAsObject(
analytics = analytics,
type = EventsDictionary.Type.dataView
)
} else {
sendToast("This inline set doesnt have a source.")
}
}
private fun proceedWithOpeningObjectByLayout(target: String) {
proceedWithClearingFocus()
val details = orchestrator.stores.details.current()
@ -3907,6 +3922,14 @@ class EditorViewModel(
else -> Unit
}
}
is ListenerType.DataViewClick -> {
when (mode) {
EditorMode.Edit -> onPageClicked(blockLinkId = clicked.target)
EditorMode.Locked -> onPageClicked(blockLinkId = clicked.target)
EditorMode.Select -> onBlockMultiSelectClicked(clicked.target)
else -> Unit
}
}
else -> {}
}
}
@ -5277,15 +5300,7 @@ class EditorViewModel(
onSendBlockActionAnalyticsEvent(EventsDictionary.BlockAction.paste)
}
ActionItemType.OpenObject -> {
val selected = blocks.firstOrNull { currentSelection().contains(it.id) }
proceedWithExitingMultiSelectMode()
if (selected != null) {
proceedWithMultiSelectOpenObjectAction(
selected = selected
)
} else {
sendToast("No blocks were selected. Please, try again.")
}
proceedWithOpeningTargetForCurrentSelection()
onSendBlockActionAnalyticsEvent(EventsDictionary.BlockAction.openObject)
}
else -> {
@ -5294,19 +5309,27 @@ class EditorViewModel(
}
}
private fun proceedWithMultiSelectOpenObjectAction(selected: Block) {
when (val content = selected.content) {
is Content.Bookmark -> {
val target = content.targetObjectId
if (target != null) {
proceedWithOpeningObject(target)
viewModelScope.sendAnalyticsOpenAsObject(
analytics = analytics,
type = EventsDictionary.Type.bookmark
)
private fun proceedWithOpeningTargetForCurrentSelection() {
val selected = blocks.firstOrNull { currentSelection().contains(it.id) }
proceedWithExitingMultiSelectMode()
if (selected != null) {
when (val content = selected.content) {
is Content.Bookmark -> {
val target = content.targetObjectId
if (target != null) {
proceedWithOpeningObject(target)
viewModelScope.sendAnalyticsOpenAsObject(
analytics = analytics,
type = EventsDictionary.Type.bookmark
)
} else {
sendToast("This bookmark doesnt have a source.")
}
}
else -> sendToast("Unexpected object")
}
else -> sendToast("Unexpected object")
} else {
sendToast("No blocks were selected. Please, try again.")
}
}

View file

@ -359,6 +359,15 @@ fun List<BlockView>.enterSAM(
isSelected = isSelected,
cells = view.cells.updateCellsMode(mode = BlockView.Mode.READ),
)
is BlockView.DataView.Default -> view.copy(
isSelected = isSelected
)
is BlockView.DataView.EmptyData -> view.copy(
isSelected = isSelected
)
is BlockView.DataView.EmptySource -> view.copy(
isSelected = isSelected
)
else -> view.also { check(view !is BlockView.Permission) }
}
}
@ -587,6 +596,9 @@ fun List<BlockView>.clearSearchHighlights(): List<BlockView> = map { view ->
is BlockView.LinkToObject.Default.Card.MediumIconCover -> view.copy(searchFields = emptyList())
is BlockView.LinkToObject.Archived -> view.copy(searchFields = emptyList())
is BlockView.Table -> view.copy(cells = view.cells.map { cell -> cell.clearHighlight() })
is BlockView.DataView.Default -> view.copy(searchFields = emptyList())
is BlockView.DataView.EmptyData -> view.copy(searchFields = emptyList())
is BlockView.DataView.EmptySource -> view.copy(searchFields = emptyList())
else -> view.also { check(view !is BlockView.Searchable) }
}
}
@ -687,6 +699,18 @@ fun List<BlockView>.highlight(
val updatedCells = view.cells.map { it.addHighlight(highlighter) }
view.copy(cells = updatedCells)
}
is BlockView.DataView.EmptySource -> {
val fields = listOf(DEFAULT_SEARCH_FIELD_KEY to view.title.orEmpty())
view.copy(searchFields = highlighter(fields))
}
is BlockView.DataView.EmptyData -> {
val fields = listOf(DEFAULT_SEARCH_FIELD_KEY to view.title.orEmpty())
view.copy(searchFields = highlighter(fields))
}
is BlockView.DataView.Default -> {
val fields = listOf(DEFAULT_SEARCH_FIELD_KEY to view.title.orEmpty())
view.copy(searchFields = highlighter(fields))
}
else -> {
view.also { v ->
if (v is BlockView.Searchable) {
@ -742,6 +766,9 @@ fun BlockView.setHighlight(
is BlockView.LinkToObject.Default.Card.SmallIconCover -> copy(searchFields = highlights)
is BlockView.LinkToObject.Default.Card.MediumIconCover -> copy(searchFields = highlights)
is BlockView.LinkToObject.Archived -> copy(searchFields = highlights)
is BlockView.DataView.EmptySource -> copy(searchFields = highlights)
is BlockView.DataView.EmptyData -> copy(searchFields = highlights)
is BlockView.DataView.Default -> copy(searchFields = highlights)
else -> this.also { check(this !is BlockView.Searchable) }
}
@ -1034,6 +1061,9 @@ fun BlockView.updateSelection(newSelection: Boolean) = when (this) {
is BlockView.Latex -> copy(isSelected = newSelection)
is BlockView.TableOfContents -> copy(isSelected = newSelection)
is BlockView.Table -> copy(isSelected = newSelection)
is BlockView.DataView.EmptyData -> copy(isSelected = newSelection)
is BlockView.DataView.EmptySource -> copy(isSelected = newSelection)
is BlockView.DataView.Default -> copy(isSelected = newSelection)
else -> this.also {
if (this is BlockView.Selectable)
Timber.e("Error when change selection for Selectable BlockView $this")

View file

@ -78,4 +78,6 @@ sealed interface ListenerType {
data class TableOfContents(val target: Id) : ListenerType
data class TableEmptyCell(val cell: BlockView.Table.Cell) : ListenerType
data class TableTextCell(val cell: BlockView.Table.Cell) : ListenerType
data class DataViewClick(val target: Id) : ListenerType
}

View file

@ -16,6 +16,9 @@ import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_CALLOUT
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_CHECKBOX
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_CODE_SNIPPET
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DATA_VIEW_DEFAULT
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DATA_VIEW_EMPTY_DATA
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DATA_VIEW_EMPTY_SOURCE
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DESCRIPTION
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DIVIDER_DOTS
import com.anytypeio.anytype.presentation.editor.editor.model.types.Types.HOLDER_DIVIDER_LINE
@ -1379,4 +1382,56 @@ sealed class BlockView : ViewType {
enum class Tab { CELL, COLUMN, ROW }
}
sealed class DataView : BlockView(), Selectable, Decoratable, Searchable {
abstract val title: String?
abstract val icon: ObjectIcon
abstract val background: ThemeColor
/**
* UI-model for a data view block. There is no master set.
*/
data class EmptySource(
override val id: String,
override val isSelected: Boolean,
override val decorations: List<Decoration>,
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_EMPTY_SOURCE
}
/**
* UI-model for a data view block. There is a master set but no source for the set
*/
data class EmptyData(
override val id: String,
override val isSelected: Boolean,
override val decorations: List<Decoration>,
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_EMPTY_DATA
}
/**
* UI-model for a data view block. There is a master set and source for the set
*/
data class Default(
override val id: String,
override val isSelected: Boolean,
override val decorations: List<Decoration>,
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_DEFAULT
}
}
}

View file

@ -70,4 +70,8 @@ object Types {
const val HOLDER_TOC = 54
const val HOLDER_CALLOUT = 55
const val HOLDER_TABLE = 56
const val HOLDER_DATA_VIEW_DEFAULT = 300
const val HOLDER_DATA_VIEW_EMPTY_SOURCE = 301
const val HOLDER_DATA_VIEW_EMPTY_DATA = 302
}

View file

@ -777,6 +777,20 @@ class DefaultBlockViewRenderer @Inject constructor(
)
)
}
is Content.DataView -> {
isPreviousBlockMedia = false
mCounter = 0
result.add(
dataView(
mode = mode,
block = block,
content = content,
details = details,
selection = selection,
schema = parentScheme
)
)
}
else -> {}
}
}
@ -2145,4 +2159,67 @@ class DefaultBlockViewRenderer @Inject constructor(
is Cursor.Range -> cursor.range.first
}
}
private fun dataView(
mode: EditorMode,
block: Block,
content: Content.DataView,
details: Block.Details,
selection: Set<Id>,
schema: NestedDecorationData
): BlockView.DataView {
val targetObjectId = content.targetObjectId
val isSelected = checkIfSelected(
mode = mode,
block = block,
selection = selection
)
val background = block.parseThemeBackgroundColor()
val decorations = buildNestedDecorationData(
block = block,
parentScheme = schema,
currentDecoration = DecorationData(
style = DecorationData.Style.Card,
background = background
)
).toBlockViewDecoration(block)
if (targetObjectId.isBlank()) {
return BlockView.DataView.EmptySource(
id = block.id,
decorations = decorations,
isSelected = isSelected,
background = background,
icon = ObjectIcon.None,
title = null
)
} else {
val targetSet = ObjectWrapper.Basic(
map = details.details[content.targetObjectId]?.map ?: emptyMap()
)
val icon = ObjectIcon.getEditorLinkToObjectIcon(
obj = targetSet,
layout = targetSet.layout,
builder = urlBuilder
)
if (targetSet.setOf.isEmpty()) {
return BlockView.DataView.EmptyData(
id = block.id,
decorations = decorations,
isSelected = isSelected,
title = targetSet.name,
background = background,
icon = icon
)
} else {
return BlockView.DataView.Default(
id = block.id,
decorations = decorations,
isSelected = isSelected,
title = targetSet.name,
background = background,
icon = icon
)
}
}
}
}

View file

@ -348,6 +348,9 @@ fun List<BlockView>.toggleTableMode(
is BlockView.Unsupported -> view.copy(isSelected = false)
is BlockView.Upload.Bookmark -> view.copy(isSelected = false)
is BlockView.Relation.Deleted -> view.copy(isSelected = false)
is BlockView.DataView.Default -> view.copy(isSelected = false)
is BlockView.DataView.EmptyData -> view.copy(isSelected = false)
is BlockView.DataView.EmptySource -> view.copy(isSelected = false)
}
}
}

View file

@ -473,7 +473,8 @@ class ObjectSetReducerTest {
filters = listOf()
)
),
relations = listOf()
relations = listOf(),
targetObjectId = (dataView.content as Block.Content.DataView).targetObjectId
),
fields = Block.Fields.empty(),
children = listOf()
@ -573,7 +574,8 @@ class ObjectSetReducerTest {
filters = expectedFilters
)
),
relations = listOf()
relations = listOf(),
targetObjectId = (dataView.content as Block.Content.DataView).targetObjectId
),
fields = Block.Fields.empty(),
children = listOf()
@ -673,7 +675,8 @@ class ObjectSetReducerTest {
filters = viewer1.filters
)
),
relations = listOf()
relations = listOf(),
targetObjectId = (dataView.content as Block.Content.DataView).targetObjectId
),
fields = Block.Fields.empty(),
children = listOf()

View file

@ -6,14 +6,16 @@ fun StubDataView(
id : Id = MockDataFactory.randomUuid(),
views: List<DVViewer> = emptyList(),
relations: List<RelationLink> = emptyList(),
sources: List<Id> = emptyList()
sources: List<Id> = emptyList(),
targetObjectId: Id = MockDataFactory.randomUuid()
) : Block = Block(
id = id,
content = DV(
sources = sources,
relations = emptyList(),
relationsIndex= relations,
viewers = views
viewers = views,
targetObjectId = targetObjectId
),
children = emptyList(),
fields = Block.Fields.empty()