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

DROID-1043 Editor | Enhancement | Inline collection block (#3020)

* DROID-1043 data view block, isCollection param + mapping to views

* DROID-1043 data view holders + design

* DROID-1043 dv collection change, diff

* DROID-1043 collection block icon

* DROID-1043 inline block click

* DROID-1043 update fun name

* DROID-1043 blockDataviewIsCollectionSet event
This commit is contained in:
Konstantin Ivanov 2023-03-20 10:32:06 +01:00 committed by GitHub
parent 5ee6443ebb
commit feab2d0a3e
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 125 additions and 33 deletions

View file

@ -1083,7 +1083,7 @@ open class EditorFragment : NavigationFragment<FragmentEditorBinding>(R.layout.f
middleString = R.string.snack_move_to
) {
if (command.isSet) {
vm.proceedWithOpeningSet(command.id)
vm.proceedWithOpeningDataViewObject(command.id)
} else {
vm.proceedWithOpeningObject(command.id)
}

View file

@ -286,7 +286,8 @@ data class Block(
data class DataView(
val viewers: List<Viewer>,
val relationLinks: List<RelationLink> = emptyList(),
val targetObjectId: Id = ""
val targetObjectId: Id = "",
val isCollection: Boolean = false
) : Content() {
data class Viewer(

View file

@ -266,6 +266,12 @@ sealed class Event {
val keys: List<Key>
) : DataView()
data class SetIsCollection(
override val context: Id,
val dv: Id,
val isCollection: Boolean
) : DataView()
data class UpdateView(
override val context: Id,
val block: Id,

View file

@ -269,6 +269,9 @@ class BlockViewDiffUtil(
if (newBlock.background != oldBlock.background) {
changes.add(BACKGROUND_COLOR_CHANGED)
}
if (newBlock.isCollection != oldBlock.isCollection) {
changes.add(DATA_VIEW_TYPE_CHANGED)
}
}
return if (changes.isNotEmpty())
@ -316,6 +319,7 @@ class BlockViewDiffUtil(
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)
val isDataViewTypeChanged : Boolean get() = changes.contains(DATA_VIEW_TYPE_CHANGED)
fun markupChanged() = changes.contains(MARKUP_CHANGED)
fun textChanged() = changes.contains(TEXT_CHANGED)
@ -371,5 +375,6 @@ class BlockViewDiffUtil(
const val DATA_VIEW_TITLE_CHANGED = 350
const val DATA_VIEW_ICON_CHANGED = 351
const val DATA_VIEW_BACKGROUND_CHANGED = 352
const val DATA_VIEW_TYPE_CHANGED = 353
}
}

View file

@ -7,6 +7,7 @@ import android.widget.FrameLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.text.toSpannable
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_models.ThemeColor
@ -40,6 +41,7 @@ class DataViewBlockEmptySourceHolder(binding: ItemBlockDataViewEmptySourceBindin
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val typeView: TextView = binding.cardDescription
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
@ -73,6 +75,7 @@ class DataViewBlockEmptyDataHolder(binding: ItemBlockDataViewEmptyDataBinding) :
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val typeView: TextView = binding.cardDescription
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
@ -107,6 +110,7 @@ data class DataViewBlockDefaultHolder(
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val typeView: TextView = binding.cardDescription
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
@ -141,6 +145,7 @@ data class DataViewBlockDeleteHolder(
override val containerView: ConstraintLayout = binding.containerWithBackground
override val objectIconView: ObjectIconWidget = binding.cardIcon
override val titleView: TextView = binding.cardName
override val typeView: TextView = binding.cardDescription
override val descriptionView: TextView = binding.cardDescription
override val selectedView: View = binding.selected
override val decoratableCard: CardView = binding.card
@ -187,9 +192,13 @@ sealed class DataViewBlockViewHolder(
protected abstract val rootView: View
abstract val containerView: ConstraintLayout
private val untitled = itemView.resources.getString(R.string.untitled_set)
private val untitledSet = itemView.resources.getString(R.string.untitled_set)
private val untitledCollection = itemView.resources.getString(R.string.untitled_collection)
private val typeSet = itemView.resources.getString(R.string.inline_set)
private val typeCollection = itemView.resources.getString(R.string.inline_collection)
abstract val objectIconView: ObjectIconWidget
abstract val titleView: TextView
abstract val typeView: TextView
abstract val descriptionView: TextView
abstract val selectedView: View
abstract override val decoratableCard: CardView
@ -206,6 +215,8 @@ sealed class DataViewBlockViewHolder(
applyName(item)
applyType(item)
applyBackground(item.background)
applySearchHighlight(item)
@ -221,8 +232,20 @@ sealed class DataViewBlockViewHolder(
private fun applyName(item: BlockView.DataView) {
val name = item.title
val sb = if (name.isNullOrBlank()) "" else SpannableString(name)
titleView.text = sb
if (name.isNullOrBlank()) {
titleView.text = SpannableString("")
setHint(item)
} else {
titleView.text = SpannableString(name)
}
}
private fun setHint(item: BlockView.DataView) {
titleView.hint = if (item.isCollection) untitledCollection else untitledSet
}
private fun applyType(item: BlockView.DataView) {
typeView.text = if (item.isCollection) typeCollection else typeSet
}
private fun applyImageOrEmoji(item: BlockView.DataView) {
@ -245,7 +268,7 @@ sealed class DataViewBlockViewHolder(
}
private fun applySearchHighlight(field: BlockView.Searchable.Field, input: TextView) {
val content = input.text as Spannable
val content = input.text.toSpannable()
content.removeSpans<SearchHighlightSpan>()
content.removeSpans<SearchTargetHighlightSpan>()
field.highlights.forEach { highlight ->
@ -264,6 +287,7 @@ sealed class DataViewBlockViewHolder(
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
input.text = content
}
private fun clearSearchHighlights() {
@ -290,6 +314,9 @@ sealed class DataViewBlockViewHolder(
if (payload.isSearchHighlightChanged) {
applySearchHighlight(item)
}
if (payload.isDataViewTypeChanged) {
applyType(item)
}
}
private fun applyBackground(background: ThemeColor) {

View file

@ -175,6 +175,7 @@
<string name="untitled">Untitled</string>
<string name="untitled_set">Untitled set</string>
<string name="untitled_collection">Untitled collection</string>
<string name="archived">Archived</string>
@ -528,6 +529,7 @@
<string name="inline">Inline</string>
<string name="set_view">Set View</string>
<string name="inline_set">Inline set</string>
<string name="inline_collection">Inline collection</string>
<string name="fit_image">Fit image</string>
<string name="image_preview">Image preview</string>
<string name="cover">Cover</string>

View file

@ -1144,7 +1144,6 @@
<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>

View file

@ -253,5 +253,14 @@ fun anytype.Event.Message.toCoreModels(
keys = event.relationKeys
)
}
blockDataviewIsCollectionSet != null -> {
val event = blockDataviewIsCollectionSet
checkNotNull(event)
Event.Command.DataView.SetIsCollection(
context = context,
dv = event.id,
isCollection = event.value_
)
}
else -> null
}

View file

@ -353,7 +353,8 @@ fun MBlock.toCoreModelsDataView(): Block.Content.DataView {
return Block.Content.DataView(
viewers = content.views.map { it.toCoreModels() },
relationLinks = content.relationLinks.map { it.toCoreModels() },
targetObjectId = content.TargetObjectId
targetObjectId = content.TargetObjectId,
isCollection = content.isCollection
)
}

View file

@ -3033,7 +3033,7 @@ class EditorViewModel(
}
}
is Content.DataView -> {
proceedWithOpeningInlineSetTarget(target = content.targetObjectId)
proceedWithOpeningDataViewBlock(dv = content)
}
else -> {
sendToast("Couldn't find the target of the link")
@ -3041,15 +3041,20 @@ class EditorViewModel(
}
}
private fun proceedWithOpeningInlineSetTarget(target: Id) {
if (target.isNotEmpty()) {
proceedWithOpeningSet(target)
private fun proceedWithOpeningDataViewBlock(dv: Content.DataView) {
if (dv.targetObjectId.isNotEmpty()) {
proceedWithOpeningDataViewObject(dv.targetObjectId)
viewModelScope.sendAnalyticsOpenAsObject(
analytics = analytics,
type = EventsDictionary.Type.dataView
)
} else {
sendToast("This inline set doesnt have a source.")
val toastMessage = if (dv.isCollection) {
"This inline collection doesn't have a source"
} else {
"This inline set doesn't have a source"
}
sendToast(toastMessage)
}
}
@ -3067,7 +3072,7 @@ class EditorViewModel(
proceedWithOpeningObject(target = target)
}
ObjectType.Layout.SET, ObjectType.Layout.COLLECTION -> {
proceedWithOpeningSet(target = target)
proceedWithOpeningDataViewObject(target = target)
}
else -> {
sendToast("Cannot open object with layout: ${wrapper.layout}")
@ -3872,7 +3877,7 @@ class EditorViewModel(
snacks.emit(Snack.ObjectSetNotFound(clicked.type))
}
is FindObjectSetForType.Response.Success -> {
proceedWithOpeningSet(response.obj.id)
proceedWithOpeningDataViewObject(response.obj.id)
}
}
}
@ -4132,7 +4137,7 @@ class EditorViewModel(
}
}
fun proceedWithOpeningSet(target: Id, isPopUpToDashboard: Boolean = false) {
fun proceedWithOpeningDataViewObject(target: Id, isPopUpToDashboard: Boolean = false) {
viewModelScope.launch {
closePage.execute(context).fold(
onFailure = {
@ -4291,7 +4296,7 @@ class EditorViewModel(
objectToSet.invoke(params).proceed(
failure = { error -> Timber.e(error, "Error convert object to set") },
success = { setId ->
proceedWithOpeningSet(target = setId, isPopUpToDashboard = true)
proceedWithOpeningDataViewObject(target = setId, isPopUpToDashboard = true)
}
)
}
@ -4301,7 +4306,7 @@ class EditorViewModel(
objectToCollection.execute(params).fold(
onFailure = { error -> Timber.e(error, "Error convert object to collection") },
onSuccess = { setId ->
proceedWithOpeningSet(target = setId, isPopUpToDashboard = true)
proceedWithOpeningDataViewObject(target = setId, isPopUpToDashboard = true)
}
)
}
@ -5983,7 +5988,7 @@ class EditorViewModel(
viewModelScope.launch {
createObjectSet(CreateObjectSet.Params(type = type)).process(
failure = { Timber.e(it, "Error while creating a set of type: $type") },
success = { response -> proceedWithOpeningSet(response.target) }
success = { response -> proceedWithOpeningDataViewObject(response.target) }
)
}
}

View file

@ -1393,6 +1393,7 @@ sealed class BlockView : ViewType {
abstract val title: String?
abstract val icon: ObjectIcon
abstract val background: ThemeColor
abstract val isCollection: Boolean
/**
* UI-model for a data view block. There is no master set.
@ -1404,7 +1405,8 @@ sealed class BlockView : ViewType {
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
override val background: ThemeColor,
override val isCollection: Boolean
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_EMPTY_SOURCE
}
@ -1419,7 +1421,8 @@ sealed class BlockView : ViewType {
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
override val background: ThemeColor,
override val isCollection: Boolean
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_EMPTY_DATA
}
@ -1434,7 +1437,8 @@ sealed class BlockView : ViewType {
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
override val background: ThemeColor,
override val isCollection: Boolean
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_DEFAULT
}
@ -1446,7 +1450,8 @@ sealed class BlockView : ViewType {
override val searchFields: List<Searchable.Field> = emptyList(),
override val title: String?,
override val icon: ObjectIcon,
override val background: ThemeColor
override val background: ThemeColor,
override val isCollection: Boolean
) : DataView() {
override fun getViewType(): Int = HOLDER_DATA_VIEW_SOURCE_DELETED
}

View file

@ -2169,6 +2169,7 @@ class DefaultBlockViewRenderer @Inject constructor(
schema: NestedDecorationData
): BlockView.DataView {
val targetObjectId = content.targetObjectId
val isCollection = content.isCollection
val isSelected = checkIfSelected(
mode = mode,
block = block,
@ -2190,7 +2191,8 @@ class DefaultBlockViewRenderer @Inject constructor(
isSelected = isSelected,
background = background,
icon = ObjectIcon.None,
title = null
title = null,
isCollection = isCollection
)
} else {
val targetSet = ObjectWrapper.Basic(
@ -2203,7 +2205,8 @@ class DefaultBlockViewRenderer @Inject constructor(
isSelected = isSelected,
background = background,
icon = ObjectIcon.None,
title = null
title = null,
isCollection = isCollection
)
}
val icon = ObjectIcon.getEditorLinkToObjectIcon(
@ -2211,14 +2214,15 @@ class DefaultBlockViewRenderer @Inject constructor(
layout = targetSet.layout,
builder = urlBuilder
)
if (targetSet.setOf.isEmpty()) {
if (targetSet.setOf.isEmpty() && !content.isCollection) {
return BlockView.DataView.EmptyData(
id = block.id,
decorations = decorations,
isSelected = isSelected,
title = targetSet.name,
background = background,
icon = icon
icon = icon,
isCollection = isCollection
)
} else {
return BlockView.DataView.Default(
@ -2227,7 +2231,8 @@ class DefaultBlockViewRenderer @Inject constructor(
isSelected = isSelected,
title = targetSet.name,
background = background,
icon = icon
icon = icon,
isCollection = isCollection
)
}
}

View file

@ -98,7 +98,7 @@ sealed class ObjectIcon {
} else {
Profile.Avatar(name = obj.name.orEmpty())
}
ObjectType.Layout.SET -> if (!img.isNullOrBlank()) {
ObjectType.Layout.SET, ObjectType.Layout.COLLECTION -> if (!img.isNullOrBlank()) {
Basic.Image(hash = builder.thumbnail(img))
} else if (!emoji.isNullOrBlank()) {
Basic.Emoji(unicode = emoji)
@ -123,7 +123,6 @@ sealed class ObjectIcon {
ObjectType.Layout.SPACE -> None
ObjectType.Layout.DATABASE -> None
null -> None
ObjectType.Layout.COLLECTION -> None
}
}
}

View file

@ -72,6 +72,9 @@ class DefaultObjectStateReducer : ObjectStateReducer {
is Command.DataView.DeleteRelation -> {
handleDeleteRelation(state, event)
}
is Command.DataView.SetIsCollection -> {
handleSetIsCollection(state, event)
}
is Command.DataView.SetTargetObjectId -> {
handleSetTargetObjectId(state, event)
}
@ -249,6 +252,26 @@ class DefaultObjectStateReducer : ObjectStateReducer {
}
}
private fun handleSetIsCollection(
state: ObjectState,
event: Command.DataView.SetIsCollection
): ObjectState {
val updateBlockContent = { content: Block.Content.DataView ->
content.copy(isCollection = event.isCollection)
}
return when (state) {
is ObjectState.DataView.Collection -> state.updateBlockContent(
target = event.dv,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.Set -> state.updateBlockContent(
target = event.dv,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
/**
* @see Command.DataView.UpdateView
*/

View file

@ -108,6 +108,7 @@ class DataViewBlockTargetObjectSetTest : EditorPresentationTestSetup() {
isSelected = false,
icon = ObjectIcon.None,
decorations = listOf(BlockView.Decoration(style = BlockView.Decoration.Style.Card)),
isCollection = false
)
)
)

View file

@ -327,12 +327,14 @@ fun StubDataViewBlock(
targetObjectId: Id = "",
viewers: List<Block.Content.DataView.Viewer> = emptyList(),
relationsIndex: List<RelationLink> = emptyList(),
isCollection : Boolean = false
): Block = Block(
id = id,
content = Block.Content.DataView(
viewers = viewers,
relationLinks = relationsIndex,
targetObjectId = targetObjectId
targetObjectId = targetObjectId,
isCollection = isCollection
),
children = children,
fields = fields,

View file

@ -6,13 +6,15 @@ fun StubDataView(
id: Id = MockDataFactory.randomUuid(),
views: List<DVViewer> = emptyList(),
relationLinks: List<RelationLink> = emptyList(),
targetObjectId: Id = MockDataFactory.randomUuid()
targetObjectId: Id = MockDataFactory.randomUuid(),
isCollection: Boolean = false
): Block = Block(
id = id,
content = DV(
relationLinks = relationLinks,
viewers = views,
targetObjectId = targetObjectId
targetObjectId = targetObjectId,
isCollection = isCollection
),
children = emptyList(),
fields = Block.Fields.empty()