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

DROID-3367 Primitives | Object icons, part 2 (#2184)

This commit is contained in:
Konstantin Ivanov 2025-03-24 16:06:47 +01:00 committed by GitHub
parent b003a9e915
commit 4ce93478a0
Signed by: github
GPG key ID: B5690EEEBB952194
48 changed files with 587 additions and 699 deletions

View file

@ -199,6 +199,7 @@ dependencies {
implementation libs.glide
implementation libs.glideCompose
implementation libs.coilCompose
implementation libs.coilNetwork
implementation libs.dagger
implementation libs.timber
implementation libs.gson

View file

@ -2,7 +2,8 @@ package com.anytypeio.anytype.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
@ -10,7 +11,7 @@ import org.junit.runner.Description
@ExperimentalCoroutinesApi
class CoroutinesTestRule(
private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
private val testDispatcher: TestDispatcher = StandardTestDispatcher()
) : TestWatcher() {
override fun starting(description: Description) {
@ -21,7 +22,6 @@ class CoroutinesTestRule(
override fun finished(description: Description) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
fun advanceTime(millis: Long) {

View file

@ -49,7 +49,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper

View file

@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE
import com.anytypeio.anytype.core_ui.extensions.throttledClick

View file

@ -56,6 +56,7 @@ dependencies {
implementation libs.composeToolingPreview
debugImplementation libs.composeTooling
implementation libs.coilCompose
implementation libs.coilNetwork
implementation libs.composeConstraintLayout
implementation libs.composeReorderableLegacy

View file

@ -22,8 +22,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.graphics.toColorInt
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.SystemColor
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable

View file

@ -8,6 +8,8 @@ class DateSelectHolder(
val binding: ItemSlashWidgetSelectDateBinding
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.ivIcon.setIcon(ObjectIcon.Empty.Date)
binding.ivIcon.setIcon(
ObjectIcon.TypeIcon.Default.DATE
)
}
}

View file

@ -62,7 +62,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.ThemeColor

View file

@ -144,7 +144,7 @@ class ObjectItemViewHolder(view: View) : ObjectViewHolder(view) {
fun bindSelectDateItem() {
title.setText(R.string.select_date)
subtitle.gone()
icon.setIcon(ObjectIcon.Empty.Date)
icon.setIcon(ObjectIcon.TypeIcon.Default.DATE)
}
}

View file

@ -27,7 +27,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DEFAULT_DISABLED_ALPHA
import com.anytypeio.anytype.core_ui.common.DefaultPreviews

View file

@ -130,7 +130,7 @@ fun PreviewObjectListItem() {
typeName = "Some type",
createdBy = "Some user",
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Empty.Page,
icon = ObjectIcon.TypeIcon.Default.DEFAULT,
isPossibleToDelete = true
)
)

View file

@ -24,7 +24,7 @@ val StubVerticalItems = listOf(
typeName = "Page",
createdBy = "by Mike Long",
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Empty.Page
icon = ObjectIcon.TypeIcon.Default.DEFAULT
),
UiObjectsListItem.Item(
id = "3",

View file

@ -202,7 +202,7 @@ fun PreviewObjectItem() {
type = "Type",
typeName = "Type Name",
description = "Description",
icon = ObjectIcon.Basic.Emoji("\uD83D\uDCA1", emptyState = ObjectIcon.Empty.Page),
icon = ObjectIcon.Basic.Emoji("\uD83D\uDCA1", fallback = ObjectIcon.TypeIcon.Fallback.DEFAULT),
space = "space-1"
),
isSelected = true,

View file

@ -0,0 +1,19 @@
package com.anytypeio.anytype.core_ui.views.animations
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.Dp
import com.anytypeio.anytype.core_ui.R
@Composable
fun LoadingIndicator(
containerModifier: Modifier = Modifier,
containerSize: Dp,
colorStart: Color = colorResource(id = R.color.glyph_active),
colorEnd: Color = Color.Transparent,
withCircleBackground: Boolean = true
) {
//todo next PR
}

View file

@ -3,7 +3,6 @@ package com.anytypeio.anytype.core_ui.widgets
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -13,20 +12,16 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.extensions.getMimeIcon
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.widgets.objectIcon.AvatarIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.CustomIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.BookmarkIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.DeletedIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.EmojiIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.EmptyIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.ImageIconView
import com.anytypeio.anytype.core_ui.widgets.objectIcon.ObjectIconProfile
import com.anytypeio.anytype.core_ui.widgets.objectIcon.TypeIconView
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.integration.compose.placeholder
@Composable
fun ListWidgetObjectIcon(
@ -37,28 +32,48 @@ fun ListWidgetObjectIcon(
backgroundColor: Int = R.color.shape_tertiary
) {
when (icon) {
is ObjectIcon.Profile.Avatar -> {
AvatarIconView(
is ObjectIcon.Profile -> {
ObjectIconProfile(
modifier = modifier,
iconSize = iconSize,
icon = icon
)
}
is ObjectIcon.Profile.Image -> {
DefaultProfileIconImage(icon, modifier, iconSize)
}
is ObjectIcon.Basic.Emoji -> {
EmojiIconView(icon = icon, backgroundSize = iconSize, modifier = modifier, backgroundColor = backgroundColor)
EmojiIconView(
icon = icon,
backgroundSize = iconSize,
modifier = modifier,
backgroundColor = backgroundColor
)
}
is ObjectIcon.Basic.Image -> {
DefaultObjectImageIcon(icon.hash, modifier, iconSize, fallback = icon.emptyState)
ImageIconView(
icon = icon,
backgroundSize = iconSize,
modifier = modifier,
)
}
is ObjectIcon.Bookmark -> {
DefaultObjectBookmarkIcon(icon.image, modifier, iconSize)
BookmarkIconView(
modifier = modifier,
icon = icon,
backgroundSize = iconSize
)
}
is ObjectIcon.Task -> {
DefaultTaskObjectIcon(modifier, iconSize, icon, onTaskIconClicked)
DefaultTaskObjectIcon(
modifier = modifier,
iconSize = iconSize,
icon = icon,
onIconClicked = onTaskIconClicked
)
}
is ObjectIcon.File -> {
DefaultFileObjectImageIcon(
fileName = icon.fileName.orEmpty(),
@ -68,28 +83,27 @@ fun ListWidgetObjectIcon(
extension = icon.extensions
)
}
is ObjectIcon.Checkbox -> {}
ObjectIcon.Deleted -> {
DeletedIconView(
modifier = modifier,
backgroundSize = iconSize
)
}
is ObjectIcon.Empty -> {
EmptyIconView(
modifier = modifier,
emptyType = icon,
is ObjectIcon.TypeIcon ->
TypeIconView(
icon = icon,
backgroundSize = iconSize,
modifier = modifier,
backgroundColor = backgroundColor
)
is ObjectIcon.Checkbox -> {
//do nothing
}
ObjectIcon.None -> {}
is ObjectIcon.ObjectType -> {
CustomIconView(
icon = icon,
modifier = modifier,
iconSize = iconSize
)
ObjectIcon.None -> {
//do nothing
}
}
}
@ -119,61 +133,6 @@ fun DefaultTaskObjectIcon(
}
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun DefaultObjectImageIcon(
url: Url,
modifier: Modifier,
iconSize: Dp,
fallback: ObjectIcon.Empty
) {
GlideImage(
model = url,
contentDescription = "Icon from URI",
contentScale = ContentScale.Crop,
modifier = modifier
.size(iconSize)
.clip(RoundedCornerShape(2.dp)),
failure = placeholder(resourceId = imageAsset(fallback)),
loading = placeholder(resourceId = R.drawable.ic_icon_loading)
)
}
@Composable
fun DefaultObjectBookmarkIcon(
url: Url,
modifier: Modifier,
iconSize: Dp
) {
Box(modifier = modifier.size(iconSize)) {
Image(
painter = rememberAsyncImagePainter(url),
contentDescription = "Icon from URI",
modifier = Modifier
.align(Alignment.Center)
.size(24.dp)
)
}
}
@Composable
fun DefaultProfileIconImage(
icon: ObjectIcon.Profile.Image,
modifier: Modifier,
iconSize: Dp
) {
Image(
painter = rememberAsyncImagePainter(icon.hash),
contentDescription = "Icon from URI",
modifier = modifier
.size(iconSize)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
}
@Composable
fun DefaultFileObjectImageIcon(
fileName: String,
@ -202,15 +161,4 @@ fun cornerRadius(size: Dp): Dp {
in 64.dp..79.dp -> 8.dp
else -> 12.dp
}
}
fun imageAsset(emptyType: ObjectIcon.Empty): Int {
return when (emptyType) {
ObjectIcon.Empty.Bookmark -> R.drawable.ic_empty_state_link
ObjectIcon.Empty.Chat -> R.drawable.ic_empty_state_chat
ObjectIcon.Empty.List -> R.drawable.ic_empty_state_list
ObjectIcon.Empty.ObjectType -> R.drawable.ic_empty_state_type
ObjectIcon.Empty.Page -> R.drawable.ic_empty_state_page
ObjectIcon.Empty.Date -> R.drawable.ic_obj_date_24
}
}

View file

@ -151,8 +151,7 @@ class ObjectIconWidget @JvmOverloads constructor(
)
ObjectIcon.Deleted -> setDeletedIcon()
is ObjectIcon.Checkbox -> setCheckbox(icon.isChecked)
is ObjectIcon.Empty -> icon.setEmptyIcon()
is ObjectIcon.ObjectType -> setCustomIcon(icon)
is ObjectIcon.TypeIcon -> setTypeIcon(icon)
}
}
@ -350,8 +349,23 @@ class ObjectIconWidget @JvmOverloads constructor(
}
}
private fun setCustomIcon(icon: ObjectIcon.ObjectType) {
val resId = context.resources.getIdentifier(icon.icon.drawableResId, DRAWABLE_DIR, context.packageName)
private fun setTypeIcon(icon: ObjectIcon.TypeIcon) {
//todo next PR
val (resId, tint) = when (icon) {
is ObjectIcon.TypeIcon.Default -> {
val resId = context.resources.getIdentifier(icon.drawableResId, DRAWABLE_DIR, context.packageName)
if (resId != 0) {
resId to context.getColor(icon.color.colorRes())
} else {
0 to 0
}
}
ObjectIcon.TypeIcon.Deleted -> 0 to 0
is ObjectIcon.TypeIcon.Emoji -> 0 to 0
is ObjectIcon.TypeIcon.Fallback -> 0 to 0
}
with(binding) {
ivCheckbox.invisible()
initialContainer.invisible()
@ -362,7 +376,6 @@ class ObjectIconWidget @JvmOverloads constructor(
}
try {
if (resId != 0) {
val tint = context.getColor(icon.icon.color.colorRes())
binding.tvEmojiFallback.gone()
binding.ivEmoji.setImageResource(resId)
binding.ivEmoji.imageTintList = ColorStateList.valueOf(tint)
@ -375,37 +388,4 @@ class ObjectIconWidget @JvmOverloads constructor(
Timber.w(e, "Error while setting object type icon for")
}
}
private fun ObjectIcon.Empty.setEmptyIcon() {
val (drawable, containerBackground) = when (this) {
ObjectIcon.Empty.Bookmark -> R.drawable.ic_empty_state_link to true
ObjectIcon.Empty.Chat -> R.drawable.ic_empty_state_chat to true
ObjectIcon.Empty.List -> R.drawable.ic_empty_state_list to true
ObjectIcon.Empty.ObjectType -> R.drawable.ic_empty_state_type to true
ObjectIcon.Empty.Page -> R.drawable.ic_empty_state_page to true
ObjectIcon.Empty.Date -> R.drawable.ic_obj_date_24 to false
}
val icon = context.drawable(drawable)
with(binding) {
ivEmoji.setImageDrawable(icon)
ivCheckbox.invisible()
initialContainer.invisible()
ivImage.invisible()
ivBookmark.setImageDrawable(null)
ivBookmark.gone()
if (containerBackground) {
emojiContainer.visible()
} else {
emojiContainer.visible()
emojiContainer.setBackgroundResource(0)
}
}
}
fun setIvEmojiSize(emojiSize: Int) {
binding.ivEmoji.updateLayoutParams<LayoutParams> {
this.height = emojiSize
this.width = emojiSize
}
}
}

View file

@ -9,7 +9,6 @@ import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIcon
import com.anytypeio.anytype.presentation.templates.TemplateObjectTypeView
import com.anytypeio.anytype.presentation.templates.TemplateView
import com.anytypeio.anytype.presentation.templates.TemplateView.Companion.DEFAULT_TEMPLATE_ID_BLANK
@ -53,11 +52,7 @@ fun TypeTemplatesWidgetPreview() {
type = ObjectWrapper.Type(
map = mapOf(Relations.ID to "123", Relations.NAME to "Page"),
),
icon = ObjectIcon.ObjectType(
icon = CustomIcon(
rawValue = "batteryCharging"
)
)
icon = ObjectIcon.TypeIcon.Default.DEFAULT
)
),
viewerId = "",

View file

@ -78,7 +78,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_ui.R

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
@ -7,16 +8,22 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.views.AvatarTitle
import com.anytypeio.anytype.core_ui.views.animations.LoadingIndicator
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@ -24,7 +31,35 @@ val avatarBackgroundColor = R.color.shape_tertiary
val avatarTextColor = R.color.glyph_active
@Composable
fun AvatarIconView(
fun ObjectIconProfile(
modifier: Modifier,
iconSize: Dp,
icon: ObjectIcon.Profile,
isCircleShape: Boolean = true
) {
when (icon) {
is ObjectIcon.Profile.Avatar -> {
ProfileAvatarView(
modifier = modifier,
iconSize = iconSize,
icon = icon,
isCircleShape = isCircleShape
)
}
is ObjectIcon.Profile.Image -> {
ProfileImageView(
modifier = modifier,
iconSize = iconSize,
icon = icon
)
}
}
}
@Composable
private fun ProfileAvatarView(
modifier: Modifier,
iconSize: Dp,
icon: ObjectIcon.Profile.Avatar,
@ -58,6 +93,44 @@ fun AvatarIconView(
}
}
@Composable
fun ProfileImageView(
icon: ObjectIcon.Profile.Image,
modifier: Modifier,
iconSize: Dp
) {
val painter = rememberAsyncImagePainter(icon.hash)
val state by painter.state.collectAsState()
when (state) {
AsyncImagePainter.State.Empty,
is AsyncImagePainter.State.Loading -> {
LoadingIndicator(
containerSize = iconSize
)
}
is AsyncImagePainter.State.Error -> {
ProfileAvatarView(
modifier = modifier,
iconSize = iconSize,
icon = ObjectIcon.Profile.Avatar(name = icon.name)
)
}
is AsyncImagePainter.State.Success -> {
Image(
painter = painter,
contentDescription = "Icon from URI",
modifier = modifier
.size(iconSize)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
}
}
}
private fun getAvatarIconParams(size: Dp): Pair<Int, Int> {
return when (size) {
in 0.dp..16.dp -> 2 to 11
@ -71,44 +144,4 @@ private fun getAvatarIconParams(size: Dp): Pair<Int, Int> {
in 81.dp..96.dp -> 12 to 64
else -> 12 to 64
}
}
@DefaultPreviews
@Composable
fun Avatar20IconViewPreview() {
AvatarIconView(
iconSize = 20.dp,
icon = ObjectIcon.Profile.Avatar("John Doe"),
modifier = Modifier
)
}
@DefaultPreviews
@Composable
fun Avatar32IconViewPreview() {
AvatarIconView(
iconSize = 32.dp,
icon = ObjectIcon.Profile.Avatar("John Doe"),
modifier = Modifier
)
}
@DefaultPreviews
@Composable
fun Avatar48IconViewPreview() {
AvatarIconView(
iconSize = 48.dp,
icon = ObjectIcon.Profile.Avatar("John Doe"),
modifier = Modifier
)
}
@DefaultPreviews
@Composable
fun Avatar64IconViewPreview() {
AvatarIconView(
iconSize = 64.dp,
icon = ObjectIcon.Profile.Avatar("John Doe"),
modifier = Modifier
)
}

View file

@ -0,0 +1,54 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.views.animations.LoadingIndicator
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@Composable
fun BookmarkIconView(
modifier: Modifier = Modifier,
icon: ObjectIcon.Bookmark,
backgroundSize: Dp,
) {
val painter = rememberAsyncImagePainter(model = icon.image)
val painterState by painter.state.collectAsState()
Crossfade(targetState = painterState) { state ->
when (state) {
AsyncImagePainter.State.Empty,
is AsyncImagePainter.State.Loading -> {
LoadingIndicator(containerSize = backgroundSize)
}
is AsyncImagePainter.State.Error -> {
TypeIconView(
modifier = modifier,
icon = icon.fallback,
backgroundSize = backgroundSize
)
}
is AsyncImagePainter.State.Success -> {
Image(
painter = painter,
contentDescription = "Icon from URI",
modifier = modifier
.size(backgroundSize)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
}
}
}
}

View file

@ -1,63 +0,0 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.colorRes
import com.anytypeio.anytype.core_ui.widgets.objectIcon.custom_icons.CustomIcons
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIcon
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIconColor
import com.anytypeio.anytype.core_ui.R
@Composable
fun CustomIconView(
modifier: Modifier = Modifier,
icon: ObjectIcon.ObjectType,
iconSize: Dp
) {
val tint = colorResource(id = icon.icon.color.colorRes())
val imageVector = CustomIcons.getImageVector(icon.icon)
Box(modifier = modifier) {
if (imageVector != null) {
Image(
modifier = Modifier.size(iconSize),
imageVector = imageVector,
contentDescription = "Object Type icon",
colorFilter = ColorFilter.tint(tint),
)
} else {
Image(
modifier = Modifier.size(iconSize),
painter = painterResource(id = R.drawable.ic_empty_state_page),
contentDescription = "Object Type icon",
colorFilter = ColorFilter.tint(tint),
)
}
}
}
@Composable
@DefaultPreviews
fun CustomIconViewPreview() {
CustomIconView(
icon = ObjectIcon.ObjectType(
icon = CustomIcon(
rawValue = "batteryCharging",
color = CustomIconColor.Yellow
),
),
modifier = Modifier,
iconSize = 18.dp
)
}

View file

@ -9,14 +9,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.widgets.cornerRadius
import com.anytypeio.anytype.core_ui.widgets.imageAsset
import com.anytypeio.anytype.emojifier.Emojifier
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@ -40,38 +37,30 @@ fun EmojiIconView(
height = backgroundSize * imageMultiplier
)
} else {
modifier.size(backgroundSize) to Modifier
modifier.size(backgroundSize) to Modifier.size(
width = backgroundSize * imageMultiplier,
height = backgroundSize * imageMultiplier
)
}
Box(
modifier = containerModifier,
contentAlignment = Alignment.Center
) {
val emoji = Emojifier.safeUri(icon.unicode)
val emoji = Emojifier.safeUri(icon.unicode)
if (emoji != Emojifier.Config.EMPTY_URI) {
if (emoji != Emojifier.Config.EMPTY_URI) {
Box(
modifier = containerModifier,
contentAlignment = Alignment.Center
) {
Image(
painter = rememberAsyncImagePainter(emoji),
contentDescription = "Icon from URI",
contentDescription = "Emoji object icon",
modifier = iconModifier
)
} else {
val imageAsset = imageAsset(icon.emptyState)
Image(
painter = painterResource(id = imageAsset),
contentDescription = "Empty Object Icon",
modifier = iconModifier
)
}
} else {
TypeIconView(
modifier = modifier,
icon = icon.fallback,
backgroundSize = backgroundSize
)
}
}
@DefaultPreviews
@Composable
fun Emoji20ObjectIconViewPreview() {
EmojiIconView(
icon = ObjectIcon.Basic.Emoji("😀", ObjectIcon.Empty.Page),
backgroundSize = 20.dp
)
}

View file

@ -1,102 +0,0 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.widgets.cornerRadius
import com.anytypeio.anytype.core_ui.widgets.imageAsset
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@Composable
fun EmptyIconView(
modifier: Modifier = Modifier,
backgroundSize: Dp,
emptyType: ObjectIcon.Empty,
iconWithoutBackgroundMaxSize: Dp = 20.dp,
imageMultiplier: Float = 0.625f,
backgroundColor: Int = R.color.shape_tertiary
) {
val (containerModifier, iconModifier) = if (backgroundSize > iconWithoutBackgroundMaxSize) {
modifier
.size(backgroundSize)
.background(
color = colorResource(backgroundColor),
shape = RoundedCornerShape(size = cornerRadius(backgroundSize))
) to Modifier.size(
width = backgroundSize * imageMultiplier,
height = backgroundSize * imageMultiplier
)
} else {
modifier.size(backgroundSize) to Modifier
}
Box(
modifier = containerModifier,
contentAlignment = Alignment.Center
) {
val imageAsset = imageAsset(emptyType)
Image(
painter = painterResource(id = imageAsset),
contentDescription = "Empty Object Icon",
modifier = iconModifier
)
}
}
@DefaultPreviews
@Composable
fun Empty20ObjectIconViewPreview() {
EmptyIconView(
emptyType = ObjectIcon.Empty.Page,
backgroundSize = 20.dp,
)
}
@DefaultPreviews
@Composable
fun Empty32ObjectIconViewPreview() {
EmptyIconView(
emptyType = ObjectIcon.Empty.Page,
backgroundSize = 32.dp,
)
}
@DefaultPreviews
@Composable
fun Empty48ObjectIconViewPreview() {
EmptyIconView(
emptyType = ObjectIcon.Empty.Page,
backgroundSize = 48.dp
)
}
@DefaultPreviews
@Composable
fun Empty64ObjectIconViewPreview() {
EmptyIconView(
emptyType = ObjectIcon.Empty.Page,
backgroundSize = 64.dp
)
}
@DefaultPreviews
@Composable
fun Empty112ObjectIconViewPreview() {
EmptyIconView(
emptyType = ObjectIcon.Empty.Page,
backgroundSize = 112.dp
)
}

View file

@ -0,0 +1,57 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.views.animations.LoadingIndicator
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun ImageIconView(
modifier: Modifier = Modifier,
icon: ObjectIcon.Basic.Image,
backgroundSize: Dp
) {
val painter = rememberAsyncImagePainter(icon.hash)
val state by painter.state.collectAsState()
when (state) {
AsyncImagePainter.State.Empty,
is AsyncImagePainter.State.Loading -> {
LoadingIndicator(
containerSize = backgroundSize
)
}
is AsyncImagePainter.State.Error -> {
TypeIconView(
modifier = modifier,
icon = icon.fallback,
backgroundSize = backgroundSize
)
}
is AsyncImagePainter.State.Success -> {
Image(
painter = painter,
contentDescription = "Icon from URI",
modifier = modifier
.size(backgroundSize)
.clip(CircleShape),
contentScale = ContentScale.Crop,
)
}
}
}

View file

@ -0,0 +1,19 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@Composable
fun TypeIconView(
modifier: Modifier = Modifier,
icon: ObjectIcon.TypeIcon,
backgroundSize: Dp,
iconWithoutBackgroundMaxSize: Dp = 20.dp,
backgroundColor: Int = R.color.shape_tertiary
) {
//todo next PR
}

View file

@ -1,14 +1,9 @@
package com.anytypeio.anytype.core_ui.widgets.objectIcon.custom_icons
import androidx.compose.ui.graphics.vector.ImageVector
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIcon
object CustomIcons {
fun getImageVector(icon: CustomIcon): ImageVector? {
return iconsMap[icon.rawValue]
}
fun getImageVector(name: String): ImageVector? {
return iconsMap[name]
}

View file

@ -23,7 +23,7 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.feature_chats.R

View file

@ -50,8 +50,9 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.foundation.AlertConfig
import com.anytypeio.anytype.core_ui.foundation.BUTTON_SECONDARY

View file

@ -671,7 +671,7 @@ fun ItemDropDownMenu(
fun PreviewTypeFieldsMainScreen() {
FieldsMainScreen(
uiTitleState = UiTitleState(title = "Page", isEditable = false),
uiIconState = UiIconState(icon = ObjectIcon.Empty.ObjectType, isEditable = false),
uiIconState = UiIconState(icon = ObjectIcon.TypeIcon.Default.DEFAULT, isEditable = false),
uiFieldsListState = UiFieldsListState(
items = listOf(
UiFieldsListItem.Section.Header(),

View file

@ -238,7 +238,7 @@ fun ObjectTypeMainScreenPreview() {
)
),
uiSyncStatusState = SyncStatusWidgetState.Hidden,
uiIconState = UiIconState(icon = ObjectIcon.Empty.Page, isEditable = true),
uiIconState = UiIconState(icon = ObjectIcon.TypeIcon.Default.DEFAULT, isEditable = true),
uiTitleState = UiTitleState(title = "title", isEditable = true),
uiFieldsButtonState = UiFieldsButtonState.Visible(4),
uiLayoutButtonState = UiLayoutButtonState.Visible(layout = ObjectType.Layout.VIDEO),

View file

@ -50,7 +50,7 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.foundation.Option

View file

@ -33,7 +33,7 @@ fun NewSpaceSettingsScreenPreview() {
UiSpaceSettingsItem.DefaultObjectType(
id = "some id",
name = "Taskwithveryverlylongname",
icon = ObjectIcon.Empty.ObjectType
icon = ObjectIcon.TypeIcon.Default.DEFAULT,
),
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.Wallpapers(null),

View file

@ -43,7 +43,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil3.compose.AsyncImage
import com.anytypeio.anytype.core_models.ManifestInfo
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.views.ButtonPrimaryLoading

View file

@ -32,7 +32,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt
import coil.compose.rememberAsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews

View file

@ -61,7 +61,7 @@ timberVersion = '5.0.1'
roomVersion = '2.6.1'
dataStoreVersion = '1.1.3'
amplitudeVersion = '3.35.1'
coilComposeVersion = '2.6.0'
coilComposeVersion = '3.1.0'
sentryVersion = '7.13.0'
composeQrCodeVersion = '1.0.1'
@ -98,7 +98,8 @@ gsonWire = { module = "com.squareup.wire:wire-gson-support", version.ref = "wire
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glideVersion" }
glideCompiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glideVersion" }
glideCompose = { module = "com.github.bumptech.glide:compose", version.ref = "glideComposeVersion" }
coilCompose = { module = "io.coil-kt:coil-compose", version.ref = "coilComposeVersion" }
coilCompose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilComposeVersion" }
coilNetwork = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilComposeVersion" }
mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlinVersion" }
junit = { module = "junit:junit", version.ref = "junitVersion" }
androidJUnit = { module = "androidx.test.ext:junit", version.ref = "androidJunitVersion" }

View file

@ -1141,16 +1141,13 @@ class DefaultBlockViewRenderer @Inject constructor(
val icon = when {
!iconImage.isNullOrBlank() ->
ObjectIcon.Basic.Image(
hash = urlBuilder.thumbnail(iconImage),
emptyState = ObjectIcon.Empty.Page
hash = urlBuilder.thumbnail(iconImage)
)
!iconEmoji.isNullOrBlank() -> ObjectIcon.Basic.Emoji(
unicode = iconEmoji,
emptyState = ObjectIcon.Empty.Page
)
else -> ObjectIcon.Basic.Emoji(
unicode = "💡",
emptyState = ObjectIcon.Empty.Page
)
}
return BlockView.Text.Callout(

View file

@ -5,200 +5,142 @@ import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.ObjectIcon.Basic
import com.anytypeio.anytype.core_models.SupportedLayouts
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIcon
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIconColor
fun ObjectWrapper.Basic.objectIcon(builder: UrlBuilder, objType: ObjectWrapper.Type? = null): ObjectIcon {
fun ObjectWrapper.Basic.objectIcon(
builder: UrlBuilder,
objType: ObjectWrapper.Type?
): ObjectIcon {
if (isDeleted == true) {
val obj = this
if (obj.isDeleted == true) {
return ObjectIcon.Deleted
}
val objectIcon = layout?.icon(
image = iconImage,
emoji = iconEmoji,
builder = builder,
name = name.orEmpty(),
iconName = iconName,
iconOption = iconOption?.toInt()
)
val objImage = obj.iconImage
val objEmoji = obj.iconEmoji
val objName = obj.name.orEmpty()
if (objectIcon != null) {
return objectIcon
}
if (SupportedLayouts.fileLayouts.contains(layout)) {
return fileIcon(
mime = fileMimeType,
name = name,
extensions = fileExt
)
}
if (layout == ObjectType.Layout.TODO) {
return taskIcon(isChecked = done == true)
}
return layout.emptyType()
}
fun ObjectWrapper.Type.objectIcon(builder: UrlBuilder): ObjectIcon {
if (isDeleted == true) {
return ObjectIcon.Deleted
}
val objectIcon = layout?.icon(
image = null,
emoji = iconEmoji,
builder = builder,
name = name.orEmpty(),
iconName = iconName,
iconOption = iconOption?.toInt()
)
if (objectIcon != null) {
return objectIcon
}
return layout.emptyType()
}
fun ObjectWrapper.Type.objectIcon(): ObjectIcon {
//todo next pr
return ObjectIcon.None
}
fun ObjectType.Layout?.emptyType(): ObjectIcon.Empty {
if (this == null) {
return ObjectIcon.Empty.Page
}
return when (this) {
ObjectType.Layout.SET, ObjectType.Layout.COLLECTION -> ObjectIcon.Empty.List
ObjectType.Layout.OBJECT_TYPE -> ObjectIcon.Empty.ObjectType
ObjectType.Layout.BOOKMARK -> ObjectIcon.Empty.Bookmark
ObjectType.Layout.CHAT, ObjectType.Layout.CHAT_DERIVED -> ObjectIcon.Empty.Chat
ObjectType.Layout.DATE -> ObjectIcon.Empty.Date
else -> ObjectIcon.Empty.Page
}
}
fun ObjectType.Layout.icon(
image: String?,
emoji: String?,
iconName: String?,
iconOption: Int?,
name: String,
builder: UrlBuilder
): ObjectIcon? {
return when (this) {
ObjectType.Layout.OBJECT_TYPE -> handleObjectTypeIcon(
emoji = emoji,
iconName = iconName,
iconOption = iconOption
)
return when (obj.layout) {
ObjectType.Layout.OBJECT_TYPE -> {
val asType = ObjectWrapper.Type(map = obj.map)
asType.objectIcon()
}
ObjectType.Layout.BASIC,
ObjectType.Layout.IMAGE,
ObjectType.Layout.SET,
ObjectType.Layout.COLLECTION,
ObjectType.Layout.IMAGE -> basicIcon(
image = image,
emoji = emoji,
builder = builder,
layout = this
)
ObjectType.Layout.COLLECTION -> {
val fallback = objType?.objectFallbackIcon() ?: ObjectIcon.TypeIcon.Fallback.DEFAULT
when {
!objImage.isNullOrBlank() -> Basic.Image(
hash = builder.thumbnail(objImage),
fallback = fallback
)
ObjectType.Layout.PROFILE,
ObjectType.Layout.PARTICIPANT -> profileIcon(
image = image,
name = name,
builder = builder
)
!objEmoji.isNullOrBlank() -> Basic.Emoji(
unicode = objEmoji,
fallback = fallback
)
ObjectType.Layout.BOOKMARK -> bookmarkIcon(
iconImage = image,
builder = builder
)
else -> fallback
}
}
ObjectType.Layout.DATE -> emptyType()
ObjectType.Layout.PARTICIPANT,
ObjectType.Layout.PROFILE -> {
when {
!objImage.isNullOrBlank() -> ObjectIcon.Profile.Image(
hash = builder.thumbnail(objImage),
name = objName
)
else -> null
}
}
else -> ObjectIcon.Profile.Avatar(name = objName)
}
}
/**
* Handles icons for OBJECT_TYPE layout.
*/
private fun handleObjectTypeIcon(
emoji: String?,
iconName: String?,
iconOption: Int?,
): ObjectIcon? {
return when {
!emoji.isNullOrEmpty() -> {
Basic.Emoji(
unicode = emoji,
emptyState = ObjectType.Layout.OBJECT_TYPE.emptyType()
ObjectType.Layout.TODO -> {
ObjectIcon.Task(isChecked = obj.done == true)
}
ObjectType.Layout.FILE,
ObjectType.Layout.VIDEO,
ObjectType.Layout.AUDIO,
ObjectType.Layout.PDF -> {
ObjectIcon.File(
mime = obj.fileMimeType,
fileName = objName,
extensions = obj.fileExt
)
}
iconName.isNullOrEmpty() -> ObjectIcon.Empty.ObjectType
else -> ObjectIcon.ObjectType(
icon = CustomIcon(
rawValue = iconName,
color = CustomIconColor.fromIconOption(iconOption)
),
ObjectType.Layout.BOOKMARK -> {
val fallback = objType?.objectFallbackIcon()
?: ObjectIcon.TypeIcon.Fallback.DEFAULT
when {
!objImage.isNullOrBlank() -> ObjectIcon.Bookmark(
image = builder.thumbnail(objImage),
fallback = fallback
)
else -> fallback
}
}
else -> {
objType?.objectFallbackIcon() ?: ObjectIcon.TypeIcon.Fallback.DEFAULT
}
}
}
fun ObjectWrapper.Type.objectIcon(): ObjectIcon.TypeIcon {
if (isDeleted == true) {
return ObjectIcon.TypeIcon.Deleted
}
val objEmoji = iconEmoji
val objIconName = iconName
val objIconOption = iconOption
return when {
!objEmoji.isNullOrEmpty() -> {
ObjectIcon.TypeIcon.Emoji(
unicode = objEmoji,
rawValue = objIconName.orEmpty(),
color = CustomIconColor.fromIconOption(objIconOption?.toInt())
)
}
!objIconName.isNullOrEmpty() -> ObjectIcon.TypeIcon.Default(
rawValue = objIconName,
color = CustomIconColor.fromIconOption(objIconOption?.toInt())
)
else -> ObjectIcon.TypeIcon.Default(
rawValue = ObjectIcon.TypeIcon.Default.DEFAULT_CUSTOM_ICON,
color = CustomIconColor.DEFAULT
)
}
}
private fun basicIcon(
image: String?,
emoji: String?,
builder: UrlBuilder,
layout: ObjectType.Layout
): ObjectIcon? {
private fun ObjectWrapper.Type.objectFallbackIcon(): ObjectIcon.TypeIcon.Fallback {
val objIconName = iconName
return when {
!image.isNullOrBlank() -> Basic.Image(
hash = builder.thumbnail(image),
emptyState = layout.emptyType()
!objIconName.isNullOrEmpty() -> ObjectIcon.TypeIcon.Fallback(
rawValue = objIconName
)
!emoji.isNullOrBlank() -> Basic.Emoji(
unicode = emoji,
emptyState = layout.emptyType()
)
else -> null
else -> {
ObjectIcon.TypeIcon.Fallback.DEFAULT
}
}
}
private fun profileIcon(image: String?, name: String, builder: UrlBuilder): ObjectIcon? {
return when {
!image.isNullOrBlank() -> ObjectIcon.Profile.Image(hash = builder.thumbnail(image))
else -> ObjectIcon.Profile.Avatar(name = name)
}
}
private fun bookmarkIcon(iconImage: String?, builder: UrlBuilder): ObjectIcon? {
return when {
!iconImage.isNullOrBlank() -> ObjectIcon.Bookmark(image = builder.thumbnail(iconImage))
else -> null
}
}
private fun fileIcon(
mime: String?,
name: String?,
extensions: String?
): ObjectIcon {
return ObjectIcon.File(
mime = mime,
fileName = name,
extensions = extensions
)
}
private fun taskIcon(isChecked: Boolean): ObjectIcon {
return ObjectIcon.Task(isChecked = isChecked)
@Deprecated("Use ObjectWrapper.Basic.icon(builder, objType) instead")
fun ObjectWrapper.Basic.objectIcon(builder: UrlBuilder): ObjectIcon {
return ObjectIcon.None
}

View file

@ -4,35 +4,35 @@ import com.anytypeio.anytype.core_models.Hash
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIcon
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIconColor
sealed class ObjectIcon {
data object None :ObjectIcon()
sealed class Empty : ObjectIcon() {
data object Page : Empty()
data object List : Empty()
data object Bookmark : Empty()
data object Chat : Empty()
data object ObjectType : Empty()
data object Date : Empty()
}
data object None : ObjectIcon()
sealed class Basic : ObjectIcon() {
data class Image(val hash: Hash, val emptyState: Empty = Empty.Page) : Basic()
data class Emoji(val unicode: String, val emptyState: Empty = Empty.Page) : Basic()
data class Image(
val hash: Hash,
val fallback: TypeIcon.Fallback = TypeIcon.Fallback.DEFAULT
) : Basic()
data class Emoji(
val unicode: String,
val fallback: TypeIcon.Fallback = TypeIcon.Fallback.DEFAULT
) : Basic()
}
sealed class Profile : ObjectIcon() {
data class Avatar(val name: String) : Profile()
data class Image(val hash: Hash) : Profile()
data class Image(val hash: Hash, val name: String) : Profile()
}
data class Task(val isChecked: Boolean) : ObjectIcon()
data class Bookmark(val image: Url) : ObjectIcon()
data class Bookmark(
val image: Url,
val fallback: TypeIcon
) : ObjectIcon()
data class File(
val mime: String?,
@ -43,7 +43,59 @@ sealed class ObjectIcon {
data object Deleted : ObjectIcon()
data class Checkbox(val isChecked: Boolean) : ObjectIcon()
data class ObjectType(val icon: CustomIcon) : ObjectIcon()
sealed class TypeIcon : ObjectIcon() {
data object Deleted : TypeIcon()
data class Emoji(
val unicode: String,
val rawValue: String,
val color: CustomIconColor,
) : TypeIcon()
data class Default(
val rawValue: String,
val color: CustomIconColor,
) : TypeIcon() {
/**
* Returns the drawable resource name for this icon.
*
* The drawable name is generated by converting [rawValue] from kebab-case
* (e.g., "battery-dead") to snake_case ("battery_dead") and prefixing it with "ci_".
*/
val drawableResId: String
get() = DEFAULT_ICON_PREFIX + rawValue.toSnakeCase()
/**
* Extension function that converts a kebab-case string to snake_case.
*
* Simply replaces all occurrences of '-' with '_'.
*
* @receiver String to be converted.
* @return The snake_case version of the string.
*/
private fun String.toSnakeCase(): String = replace("-", "_")
companion object {
const val DEFAULT_ICON_PREFIX = "ci_"
const val DEFAULT_CUSTOM_ICON = "extension-puzzle"
val DEFAULT = Default(DEFAULT_CUSTOM_ICON, CustomIconColor.DEFAULT)
val DATE = Default("calendar", CustomIconColor.DEFAULT)
}
}
//we use this icon when we can't find the emoji for object or image icon can't be loaded
data class Fallback(val rawValue: String) : TypeIcon() {
companion object {
const val DEFAULT_FALLBACK_ICON = "extension-puzzle"
val DEFAULT = Fallback(DEFAULT_FALLBACK_ICON)
}
}
}
}
sealed class SpaceMemberIconView {

View file

@ -1,34 +0,0 @@
package com.anytypeio.anytype.presentation.objects.custom_icon
/**
* Data class representing a custom icon.
* @property rawValue The raw string representing the icon.
* @property color The color of the icon, if any.
*/
data class CustomIcon(
val rawValue: String,
val color: CustomIconColor = CustomIconColor.DEFAULT
) {
/**
* Returns the drawable resource name for this icon.
*
* The drawable name is generated by converting [rawValue] from kebab-case
* (e.g., "battery-dead") to snake_case ("battery_dead") and prefixing it with "ci_".
*/
val drawableResId: String
get() = DEFAULT_ICON_PREFIX + rawValue.toSnakeCase()
companion object {
const val DEFAULT_ICON_PREFIX = "ci_"
}
}
/**
* Extension function that converts a kebab-case string to snake_case.
*
* Simply replaces all occurrences of '-' with '_'.
*
* @receiver String to be converted.
* @return The snake_case version of the string.
*/
private fun String.toSnakeCase(): String = replace("-", "_")

View file

@ -128,7 +128,7 @@ class SpaceSettingsViewModel(
defaultObjectTypeSettingItem = UiSpaceSettingsItem.DefaultObjectType(
id = defaultType?.id,
name = defaultType?.name.orEmpty(),
icon = ObjectIcon.Empty.ObjectType
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT
)
} else {
defaultObjectTypeSettingItem = UiSpaceSettingsItem.DefaultObjectType(
@ -537,7 +537,7 @@ class SpaceSettingsViewModel(
UiSpaceSettingsItem.DefaultObjectType(
id = type.id,
name = type.name.orEmpty(),
icon = ObjectIcon.Empty.ObjectType
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT
)
} else {
item

View file

@ -134,7 +134,7 @@ class CollectionAddRelationTest : ObjectSetViewModelTestSetup() {
name = objectCollection.obj1.name.orEmpty(),
description = objectCollection.obj1.description,
hideIcon = false,
icon = ObjectIcon.Empty.Page,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
relations = listOf(
DefaultObjectRelationValueView.Empty(
objectId = objectCollection.obj1.id,
@ -155,7 +155,7 @@ class CollectionAddRelationTest : ObjectSetViewModelTestSetup() {
name = objectCollection.obj2.name.orEmpty(),
description = objectCollection.obj2.description,
hideIcon = false,
icon = ObjectIcon.Empty.Page,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
relations = listOf(
DefaultObjectRelationValueView.Empty(
objectId = objectCollection.obj2.id,

View file

@ -152,7 +152,7 @@ class DataViewBlockTargetObjectSetTest : EditorPresentationTestSetup() {
title = null,
background = ThemeColor.DEFAULT,
isSelected = false,
icon = ObjectIcon.Empty.List,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
decorations = listOf(BlockView.Decoration(style = BlockView.Decoration.Style.Card)),
isCollection = false
)
@ -215,7 +215,7 @@ class DataViewBlockTargetObjectSetTest : EditorPresentationTestSetup() {
title = null,
background = ThemeColor.DEFAULT,
isSelected = false,
icon = ObjectIcon.Empty.List,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
decorations = listOf(BlockView.Decoration(style = BlockView.Decoration.Style.Card)),
isCollection = false
)
@ -278,7 +278,7 @@ class DataViewBlockTargetObjectSetTest : EditorPresentationTestSetup() {
title = null,
background = ThemeColor.DEFAULT,
isSelected = false,
icon = ObjectIcon.Empty.List,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
decorations = listOf(BlockView.Decoration(style = BlockView.Decoration.Style.Card)),
isCollection = false
)
@ -340,7 +340,7 @@ class DataViewBlockTargetObjectSetTest : EditorPresentationTestSetup() {
title = null,
background = ThemeColor.DEFAULT,
isSelected = false,
icon = ObjectIcon.Empty.List,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
decorations = listOf(BlockView.Decoration(style = BlockView.Decoration.Style.Card)),
isCollection = true
)
@ -482,7 +482,7 @@ class DataViewBlockTargetObjectSetTest : EditorPresentationTestSetup() {
title = null,
background = ThemeColor.DEFAULT,
isSelected = false,
icon = ObjectIcon.Empty.Page,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
decorations = listOf(BlockView.Decoration(style = BlockView.Decoration.Style.Card)),
isCollection = false
)

View file

@ -312,7 +312,7 @@ class EditorLockPageTest : EditorPresentationTestSetup() {
),
BlockView.LinkToObject.Default.Text(
id = link.id,
icon = ObjectIcon.Empty.Page,
icon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
decorations = listOf(
BlockView.Decoration(
background = link.parseThemeBackgroundColor()

View file

@ -17,6 +17,7 @@ import com.anytypeio.anytype.presentation.editor.editor.slash.SlashWidgetState
import com.anytypeio.anytype.presentation.number.NumberParser
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.ObjectTypeView
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIconColor
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
@ -369,11 +370,11 @@ class EditorSlashWidgetClicksTest: EditorPresentationTestSetup() {
id = type1.id,
key = type1.uniqueKey,
name = type1.name.orEmpty(),
icon = ObjectIcon.None,
// ObjectIcon.Basic.Emoji(
// unicode = type1.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type1.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
description = type1.description,
)
),
@ -383,11 +384,11 @@ class EditorSlashWidgetClicksTest: EditorPresentationTestSetup() {
key = type2.uniqueKey,
name = type2.name.orEmpty(),
description = type2.description,
icon = ObjectIcon.None,
// ObjectIcon.Basic.Emoji(
// unicode = type2.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type2.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
),
SlashItem.ObjectType(
@ -396,11 +397,11 @@ class EditorSlashWidgetClicksTest: EditorPresentationTestSetup() {
key = type3.uniqueKey,
name = type3.name.orEmpty(),
description = type3.description,
icon = ObjectIcon.None,
// ObjectIcon.Basic.Emoji(
// unicode = type3.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type3.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
)
)

View file

@ -21,6 +21,7 @@ import com.anytypeio.anytype.presentation.editor.editor.slash.SlashRelationView
import com.anytypeio.anytype.presentation.editor.editor.slash.SlashWidgetState
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.ObjectTypeView
import com.anytypeio.anytype.presentation.objects.custom_icon.CustomIconColor
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
@ -471,11 +472,11 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
key = type1.uniqueKey,
name = type1.name.orEmpty(),
description = type1.description,
icon = ObjectIcon.None
// ObjectIcon.Basic.Emoji(
// unicode = type1.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type1.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
),
SlashItem.ObjectType(
@ -484,11 +485,11 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
key = type2.uniqueKey,
name = type2.name.orEmpty(),
description = type2.description,
icon = ObjectIcon.None
// ObjectIcon.Basic.Emoji(
// unicode = type2.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type2.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
)
)
@ -1517,11 +1518,11 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
key = type1.uniqueKey.orEmpty(),
name = type1.name.orEmpty(),
description = type1.description,
icon = ObjectIcon.None
// ObjectIcon.Basic.Emoji(
// unicode = type1.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type1.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
),
SlashItem.ObjectType(
@ -1530,11 +1531,11 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
key = type2.uniqueKey.orEmpty(),
name = type2.name.orEmpty(),
description = type2.description,
icon = ObjectIcon.None
// ObjectIcon.Basic.Emoji(
// unicode = type2.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type2.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
),
SlashItem.ObjectType(
@ -1543,11 +1544,11 @@ class EditorSlashWidgetFilterTest : EditorPresentationTestSetup() {
key = type3.uniqueKey.orEmpty(),
name = type3.name.orEmpty(),
description = type3.description,
icon = ObjectIcon.None
// ObjectIcon.Basic.Emoji(
// unicode = type3.iconEmoji.orEmpty(),
// emptyState = ObjectIcon.Empty.ObjectType
// ),
icon = ObjectIcon.TypeIcon.Emoji(
unicode = type3.iconEmoji.orEmpty(),
rawValue = "",
color = CustomIconColor.DEFAULT
),
)
)
)

View file

@ -653,7 +653,7 @@ class HomeScreenViewModelTest {
id = firstLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = firstLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = widgetBlock.id + "/" + sourceObject.id + "/" + firstLink.id,
name = WidgetView.Name.Default(
@ -664,7 +664,7 @@ class HomeScreenViewModelTest {
id = secondLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = secondLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = widgetBlock.id + "/" + sourceObject.id + "/" + secondLink.id,
name = WidgetView.Name.Default(
@ -1127,7 +1127,7 @@ class HomeScreenViewModelTest {
id = firstLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = firstLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = favoriteWidgetBlock.id + "/" + favoriteSource.id + "/" + firstLink.id,
name = WidgetView.Name.Default(
@ -1138,7 +1138,7 @@ class HomeScreenViewModelTest {
id = secondLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = secondLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = favoriteWidgetBlock.id + "/" + favoriteSource.id + "/" + secondLink.id,
name = WidgetView.Name.Default(
@ -1161,7 +1161,7 @@ class HomeScreenViewModelTest {
id = firstLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = firstLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = recentWidgetBlock.id + "/" + recentSource.id + "/" + firstLink.id,
name = WidgetView.Name.Default(
@ -1172,7 +1172,7 @@ class HomeScreenViewModelTest {
id = secondLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = secondLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = recentWidgetBlock.id + "/" + recentSource.id + "/" + secondLink.id,
name = WidgetView.Name.Default(
@ -1195,7 +1195,7 @@ class HomeScreenViewModelTest {
id = firstLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = firstLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = setsWidgetBlock.id + "/" + setsSource.id + "/" + firstLink.id,
name = WidgetView.Name.Default(
@ -1206,7 +1206,7 @@ class HomeScreenViewModelTest {
id = secondLink.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
obj = secondLink,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
indent = 0,
path = setsWidgetBlock.id + "/" + setsSource.id + "/" + secondLink.id,
name = WidgetView.Name.Default(

View file

@ -337,7 +337,7 @@ class TreeWidgetContainerTest {
obj = sourceLinks[0],
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id,
elementIcon = WidgetView.Tree.ElementIcon.Branch(isExpanded = false),
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(sourceLinks[0])
)
@ -348,7 +348,7 @@ class TreeWidgetContainerTest {
obj = sourceLinks[1],
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[1].id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(sourceLinks[1])
)
@ -359,7 +359,7 @@ class TreeWidgetContainerTest {
obj = sourceLinks[2],
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[2].id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(sourceLinks[2])
)
@ -386,7 +386,7 @@ class TreeWidgetContainerTest {
obj = sourceLinks[0],
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id,
elementIcon = WidgetView.Tree.ElementIcon.Branch(isExpanded = true),
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(sourceLinks[0])
)
@ -397,7 +397,7 @@ class TreeWidgetContainerTest {
obj = linkA1,
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id + "/" + linkA1.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(linkA1)
)
@ -408,7 +408,7 @@ class TreeWidgetContainerTest {
obj = linkA2,
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id + "/" + linkA2.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(linkA2)
)
@ -419,7 +419,7 @@ class TreeWidgetContainerTest {
obj = linkA3,
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[0].id + "/" + linkA3.id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(linkA3)
)
@ -430,7 +430,7 @@ class TreeWidgetContainerTest {
obj = sourceLinks[1],
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[1].id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(sourceLinks[1])
)
@ -441,7 +441,7 @@ class TreeWidgetContainerTest {
obj = sourceLinks[2],
path = widget.id + "/" + widget.source.id + "/" + sourceLinks[2].id,
elementIcon = WidgetView.Tree.ElementIcon.Leaf,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
name = WidgetView.Name.Default(
prettyPrintName = fieldParser.getObjectName(sourceLinks[2])
)

View file

@ -169,7 +169,7 @@ class TagAndStatusTests {
name = "Untitled",
type = "Type111",
showIcon = false,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
cells = listOf(
CellView.Description(
id = recordId,
@ -307,7 +307,7 @@ class TagAndStatusTests {
name = "Untitled",
type = "Type111",
showIcon = false,
objectIcon = ObjectIcon.Empty.Page,
objectIcon = ObjectIcon.TypeIcon.Fallback.DEFAULT,
cells = listOf(
CellView.Description(
id = recordId,