From 807f95e853f17222c59bbd19a64b943a1b4f784d Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 18 Aug 2022 14:34:15 +0300 Subject: [PATCH] DROID-340 Objects | Design | New design for bookmark object icon on dashboard and in lists (search, move-to, link-to, etc.) (#2532) --- .../anytype/ui/dashboard/DashboardAdapter.kt | 104 ++++++++++++++++++ .../anytype/ui/dashboard/DesktopDiffUtil.kt | 1 + .../main/res/layout/item_desktop_bookmark.xml | 85 ++++++++++++++ .../core_ui/widgets/ObjectIconWidget.kt | 23 +++- .../main/res/layout/widget_object_icon.xml | 12 +- core-ui/src/main/res/values/strings.xml | 1 + .../editor/render/DefaultBlockViewRenderer.kt | 63 +++++------ .../presentation/objects/ObjectIcon.kt | 5 +- 8 files changed, 259 insertions(+), 35 deletions(-) create mode 100644 app/src/main/res/layout/item_desktop_bookmark.xml diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt index 1d38f5b14b..67bcae423f 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardAdapter.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_ui.tools.SupportDragAndDropBehavior +import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.invisible import com.anytypeio.anytype.core_utils.ext.shift import com.anytypeio.anytype.core_utils.ext.typeOf @@ -20,6 +21,7 @@ import com.anytypeio.anytype.databinding.ItemDesktopArchiveBinding import com.anytypeio.anytype.databinding.ItemDesktopSetWithoutIconBinding import com.anytypeio.anytype.presentation.dashboard.DashboardView import com.anytypeio.anytype.presentation.objects.ObjectIcon +import com.bumptech.glide.Glide import com.facebook.shimmer.ShimmerFrameLayout import timber.log.Timber @@ -38,6 +40,7 @@ class DashboardAdapter( const val VIEW_TYPE_SET = 4 const val VIEW_TYPE_SET_WITHOUT_ICON = 5 const val VIEW_TYPE_DOCUMENT_NOTE = 6 + const val VIEW_TYPE_DOCUMENT_BOOKMARK = 7 const val UNEXPECTED_TYPE_ERROR_MESSAGE = "Unexpected type" } @@ -86,6 +89,19 @@ class DashboardAdapter( } } } + VIEW_TYPE_DOCUMENT_BOOKMARK -> { + ViewHolder.DocumentBookmarkViewHolder(parent).apply { + itemView.setOnClickListener { + val pos = bindingAdapterPosition + if (pos != RecyclerView.NO_POSITION) { + val item = data[pos] + if (item is DashboardView.Document) { + onDocumentClicked(item.target, item.isLoading) + } + } + } + } + } VIEW_TYPE_ARCHIVE -> { ViewHolder.ArchiveHolder( ItemDesktopArchiveBinding.inflate(inflater, parent, false) @@ -155,6 +171,7 @@ class DashboardAdapter( is DashboardView.Document -> when { item.layout == ObjectType.Layout.TODO -> VIEW_TYPE_DOCUMENT_TASK item.layout == ObjectType.Layout.NOTE -> VIEW_TYPE_DOCUMENT_NOTE + item.layout == ObjectType.Layout.BOOKMARK -> VIEW_TYPE_DOCUMENT_BOOKMARK item.hasIcon || item.layout == ObjectType.Layout.PROFILE -> VIEW_TYPE_DOCUMENT else -> VIEW_TYPE_DOCUMENT_WITHOUT_ICON } @@ -193,6 +210,13 @@ class DashboardAdapter( holder.bindLoading(item.isLoading) holder.bindDone(item.done) } + is ViewHolder.DocumentBookmarkViewHolder -> { + val item = data[position] as DashboardView.Document + holder.bindTitle(item.title) + holder.bindSubtitle(item.typeName) + holder.bindLoading(item.isLoading) + holder.bindImage(item.image) + } is ViewHolder.ObjectSetHolder -> { with(holder) { val item = data[position] as DashboardView.ObjectSet @@ -246,6 +270,9 @@ class DashboardAdapter( is ViewHolder.DocumentTaskViewHolder -> { bindByPayloads(holder, position, payload) } + is ViewHolder.DocumentBookmarkViewHolder -> { + bindByPayloads(holder, position, payload) + } else -> Timber.d("Skipping payload update.") } } @@ -354,6 +381,28 @@ class DashboardAdapter( } } + private fun bindByPayloads( + holder: ViewHolder.DocumentBookmarkViewHolder, + position: Int, + payload: DesktopDiffUtil.Payload + ) { + with(holder) { + val item = data[position] as DashboardView.Document + if (payload.titleChanged()) { + bindTitle(item.title) + } + if (payload.isLoadingChanged) { + bindLoading(item.isLoading) + } + if (payload.isSelectionChanged) { + bindSelection(item.isSelected) + } + if (payload.isImageChanged) { + bindImage(item.image) + } + } + } + sealed class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract fun bindSelection(isSelected: Boolean) @@ -492,6 +541,61 @@ class DashboardAdapter( } } + class DocumentBookmarkViewHolder(parent: ViewGroup) : ViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.item_desktop_bookmark, + parent, + false + ) + ) { + + private val tvTitle = itemView.findViewById(R.id.tvDocTitle) + private val tvSubtitle = itemView.findViewById(R.id.tvDocTypeName) + private val icon = itemView.findViewById(R.id.ivIcon) + private val shimmer = itemView.findViewById(R.id.shimmer) + private val selection = itemView.findViewById(R.id.ivSelection) + + fun bindTitle(title: String?) { + tvTitle.text = title + } + + fun bindSubtitle(type: String?) { + tvSubtitle.text = type + } + + fun bindLoading(isLoading: Boolean) { + if (isLoading) { + tvTitle.invisible() + tvSubtitle.invisible() + shimmer.startShimmer() + shimmer.visible() + } else { + shimmer.stopShimmer() + shimmer.invisible() + tvTitle.visible() + tvSubtitle.visible() + } + } + + fun bindImage(iconImage: String?) { + if (iconImage != null) { + Glide + .with(icon) + .load(iconImage) + .centerInside() + .into(icon) + icon.visible() + } else { + icon.setImageDrawable(null) + icon.gone() + } + } + + override fun bindSelection(isSelected: Boolean) { + if (isSelected) selection.visible() else selection.invisible() + } + } + class DocumentTaskViewHolder(parent: ViewGroup) : ViewHolder( LayoutInflater.from(parent.context).inflate( R.layout.item_desktop_page_task, diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt index 5bc5cb9b1e..42a599561a 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt @@ -79,6 +79,7 @@ class DesktopDiffUtil( val isLoadingChanged: Boolean = changes.contains(LOADING_STATE_CHANGED) val isSelectionChanged: Boolean = changes.contains(SELECTION_CHANGED) val isDoneChanged: Boolean = changes.contains(DONE_CHANGED) + val isImageChanged: Boolean = changes.contains(IMAGE_CHANGED) fun targetChanged() = changes.contains(TARGET_CHANGED) fun titleChanged() = changes.contains(TITLE_CHANGED) diff --git a/app/src/main/res/layout/item_desktop_bookmark.xml b/app/src/main/res/layout/item_desktop_bookmark.xml new file mode 100644 index 0000000000..195eb97387 --- /dev/null +++ b/app/src/main/res/layout/item_desktop_bookmark.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt index 0e135c002d..75f53b44d4 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.core_ui.databinding.WidgetObjectIconBinding import com.anytypeio.anytype.core_ui.extensions.color import com.anytypeio.anytype.core_ui.extensions.setCircularShape import com.anytypeio.anytype.core_ui.extensions.setCorneredShape +import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.invisible import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.emojifier.Emojifier @@ -119,7 +120,8 @@ class ObjectIconWidget @JvmOverloads constructor( is ObjectIcon.Profile.Avatar -> setProfileInitials(icon.name) is ObjectIcon.Profile.Image -> setCircularImage(icon.hash) is ObjectIcon.Task -> setCheckbox(icon.isChecked) - ObjectIcon.None -> removeIcon() + is ObjectIcon.Bookmark -> setBookmark(icon.image) + is ObjectIcon.None -> removeIcon() } } @@ -218,6 +220,7 @@ class ObjectIconWidget @JvmOverloads constructor( ivCheckbox.invisible() initialContainer.invisible() emojiContainer.invisible() + ivBookmark.gone() ivImage.visible() ivImage.setCorneredShape(imageCornerRadius) } @@ -237,6 +240,7 @@ class ObjectIconWidget @JvmOverloads constructor( initialContainer.invisible() ivImage.invisible() emojiContainer.visible() + ivBookmark.gone() ivEmoji.setImageDrawable(drawable) } } @@ -247,14 +251,31 @@ class ObjectIconWidget @JvmOverloads constructor( ivCheckbox.isSelected = isChecked ?: false initialContainer.invisible() emojiContainer.invisible() + ivBookmark.gone() ivImage.invisible() } } + private fun setBookmark(image: Url) { + with(binding) { + ivCheckbox.invisible() + initialContainer.invisible() + emojiContainer.invisible() + ivImage.invisible() + ivBookmark.visible() + Glide + .with(binding.ivBookmark) + .load(image) + .centerCrop() + .into(binding.ivBookmark) + } + } + private fun removeIcon() { with(binding) { ivEmoji.setImageDrawable(null) ivImage.setImageDrawable(null) + ivBookmark.setImageDrawable(null) ivCheckbox.invisible() } } diff --git a/core-ui/src/main/res/layout/widget_object_icon.xml b/core-ui/src/main/res/layout/widget_object_icon.xml index 3e2de7d8ff..f69d3e66d3 100644 --- a/core-ui/src/main/res/layout/widget_object_icon.xml +++ b/core-ui/src/main/res/layout/widget_object_icon.xml @@ -1,6 +1,5 @@ - + + + \ 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 514d72d027..65a0bb4c30 100644 --- a/core-ui/src/main/res/values/strings.xml +++ b/core-ui/src/main/res/values/strings.xml @@ -552,5 +552,6 @@ Copy phone number Send email Call phone number + Bookmark icon 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 d3627a6eb5..3680e49365 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 @@ -57,7 +57,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection: Set, count: Int, objectTypes: List, - parentSchema: NestedDecorationData, + parentScheme: NestedDecorationData, onRenderFlag: (BlockViewRenderer.RenderFlag) -> Unit, ): List { @@ -105,7 +105,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) result.add( paragraph( @@ -133,7 +133,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -149,7 +149,7 @@ class DefaultBlockViewRenderer @Inject constructor( val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) result.add( @@ -179,7 +179,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -188,7 +188,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) result.add( toggle( @@ -216,7 +216,7 @@ class DefaultBlockViewRenderer @Inject constructor( restrictions = restrictions, selection = selection, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme, + parentScheme = blockDecorationScheme, objectTypes = objectTypes ) ) @@ -226,7 +226,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Header.H1, background = block.parseThemeBackgroundColor() @@ -258,7 +258,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -267,7 +267,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Header.H2, background = block.parseThemeBackgroundColor() @@ -299,7 +299,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -308,7 +308,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Header.H3, background = block.parseThemeBackgroundColor() @@ -340,7 +340,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -350,7 +350,7 @@ class DefaultBlockViewRenderer @Inject constructor( val normalized: NestedDecorationData = if (NESTED_DECORATION_ENABLED) { normalizeNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) } else { emptyList() @@ -388,7 +388,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = if (NESTED_DECORATION_ENABLED) + parentScheme = if (NESTED_DECORATION_ENABLED) (normalized + current) else emptyList() @@ -400,7 +400,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) result.add( bulleted( @@ -428,7 +428,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -453,7 +453,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) result.add( checkbox( @@ -481,7 +481,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -490,7 +490,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Code, background = block.parseThemeBackgroundColor() @@ -521,7 +521,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -531,7 +531,7 @@ class DefaultBlockViewRenderer @Inject constructor( val blockDecorationScheme: NestedDecorationData = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Callout( start = block.id, @@ -566,7 +566,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, objectTypes = objectTypes, onRenderFlag = onRenderFlag, - parentSchema = blockDecorationScheme + parentScheme = blockDecorationScheme ) ) } @@ -577,7 +577,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Card, background = block.parseThemeBackgroundColor() @@ -602,7 +602,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.None, background = block.parseThemeBackgroundColor() @@ -633,7 +633,7 @@ class DefaultBlockViewRenderer @Inject constructor( selection = selection, isPreviousBlockMedia = isPreviousBlockMedia, objectTypes = objectTypes, - parentSchema = parentSchema + parentSchema = parentScheme ) result.add(link) isPreviousBlockMedia = link is BlockView.LinkToObject.Default.Card @@ -642,7 +642,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema, + parentScheme = parentScheme, currentDecoration = DecorationData( style = DecorationData.Style.Card, background = block.parseThemeBackgroundColor() @@ -693,7 +693,7 @@ class DefaultBlockViewRenderer @Inject constructor( mCounter = 0 val blockDecorationScheme = buildNestedDecorationData( block = block, - parentScheme = parentSchema + parentScheme = parentScheme ) result.add( relation( @@ -1569,9 +1569,9 @@ class DefaultBlockViewRenderer @Inject constructor( emoji = details.details[root.id]?.iconEmoji?.let { name -> name.ifEmpty { null } }, - image = details.details[root.id]?.iconImage?.let { name -> - if (name.isNotEmpty()) - urlBuilder.thumbnail(name) + image = details.details[root.id]?.iconImage?.let { image -> + if (image.isNotEmpty() && layout != ObjectType.Layout.BOOKMARK) + urlBuilder.thumbnail(image) else null }, @@ -1584,6 +1584,7 @@ class DefaultBlockViewRenderer @Inject constructor( color = block.textColor() ) } + else -> throw IllegalStateException("Unexpected layout: $layout") } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt index db9bc18cfc..1bd18cc060 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/ObjectIcon.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.objects import com.anytypeio.anytype.core_models.Hash import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.domain.misc.UrlBuilder sealed class ObjectIcon { @@ -20,6 +21,8 @@ sealed class ObjectIcon { data class Task(val isChecked: Boolean) : ObjectIcon() + data class Bookmark(val image: Url) : ObjectIcon() + companion object { fun from( obj: ObjectWrapper.Basic, @@ -60,7 +63,7 @@ sealed class ObjectIcon { ObjectType.Layout.NOTE -> Basic.Avatar(obj.snippet.orEmpty()) ObjectType.Layout.FILE -> Basic.Avatar(obj.name.orEmpty()) ObjectType.Layout.BOOKMARK -> when { - !img.isNullOrBlank() -> Basic.Image(hash = builder.thumbnail(img)) + !img.isNullOrBlank() -> Bookmark(image = builder.thumbnail(img)) !emoji.isNullOrBlank() -> Basic.Emoji(unicode = emoji) else -> Basic.Avatar(obj.name.orEmpty()) }