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

DROID-340 Objects | Design | New design for bookmark object icon on dashboard and in lists (search, move-to, link-to, etc.) (#2532)

This commit is contained in:
Evgenii Kozlov 2022-08-18 14:34:15 +03:00 committed by GitHub
parent 80c69576a4
commit 807f95e853
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 259 additions and 35 deletions

View file

@ -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<TextView>(R.id.tvDocTitle)
private val tvSubtitle = itemView.findViewById<TextView>(R.id.tvDocTypeName)
private val icon = itemView.findViewById<ImageView>(R.id.ivIcon)
private val shimmer = itemView.findViewById<ShimmerFrameLayout>(R.id.shimmer)
private val selection = itemView.findViewById<ImageView>(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,

View file

@ -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)

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:layout_width="match_parent"
android:layout_height="@dimen/default_dashboard_card_height"
android:clickable="true"
app:cardBackgroundColor="@color/dashboard_card_background"
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="10dp"
android:layout_marginTop="17dp" />
<TextView
android:id="@+id/tvDocTitle"
style="@style/DashboardDocAlternativeTitleStyle"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginStart="36dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:maxLines="4"
tools:text="@string/default_text_placeholder" />
<TextView
android:id="@+id/tvDocTypeName"
style="@style/DashboardDocumentSubtitleStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="98dp"
android:layout_marginEnd="16dp"
android:hint="@string/unknown_type"
tools:text="Task" />
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:shimmer_auto_start="false"
app:shimmer_duration="2000"
tools:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/iconSkeleton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/circle_default" />
<View
android:id="@+id/txtSkeleton"
android:layout_width="match_parent"
android:layout_height="22dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rectangle_dashboard_text_skeleton" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<ImageView
android:id="@+id/ivSelection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:paddingEnd="10dp"
android:paddingBottom="10dp"
android:src="@drawable/ic_bin_selection"
android:visibility="invisible" />
</androidx.cardview.widget.CardView>

View file

@ -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()
}
}

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="@+id/emojiContainer"
@ -47,4 +46,13 @@
android:src="@drawable/ic_data_view_grid_checkbox_selector"
android:visibility="invisible" />
<ImageView
android:id="@+id/ivBookmark"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:contentDescription="@string/bookmark_icon"
android:src="@drawable/ic_data_view_grid_checkbox_selector"
android:visibility="gone" />
</merge>

View file

@ -552,5 +552,6 @@
<string name="copy_phone_number">Copy phone number</string>
<string name="send_email">Send email</string>
<string name="call_phone_number">Call phone number</string>
<string name="bookmark_icon">Bookmark icon</string>
</resources>

View file

@ -57,7 +57,7 @@ class DefaultBlockViewRenderer @Inject constructor(
selection: Set<Id>,
count: Int,
objectTypes: List<ObjectType>,
parentSchema: NestedDecorationData,
parentScheme: NestedDecorationData,
onRenderFlag: (BlockViewRenderer.RenderFlag) -> Unit,
): List<BlockView> {
@ -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")
}
}

View file

@ -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())
}