diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt index 433da47e82..992a4e076d 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt @@ -824,29 +824,28 @@ open class ObjectSetFragment : .launchIn(lifecycleScope) } - binding.objectHeader.root.findViewById(R.id.imageIcon).apply { - if (header.title.image != null) visible() else gone() + binding.objectHeader.root.findViewById(R.id.imageIcon).apply { jobs += this.clicks() .throttleFirst() .onEach { vm.onObjectIconClicked() } .launchIn(lifecycleScope) + + if (header.title.image != null) { + this.visible() + Glide + .with(this) + .load(header.title.image) + .centerCrop() + .into(this) + } else { + this.gone() + this.setImageDrawable(null) + } } binding.objectHeader.root.findViewById(R.id.emojiIcon) .setEmojiOrNull(header.title.emoji) - if (header.title.image != null) { - binding.objectHeader.root.findViewById(R.id.imageIcon).apply { - Glide - .with(this) - .load(header.title.image) - .centerCrop() - .into(this) - } - } else { - binding.objectHeader.root.findViewById(R.id.imageIcon).setImageDrawable(null) - } - setCover( coverColor = header.title.coverColor, coverGradient = header.title.coverGradient, diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt index 68cfeba72b..5390f19240 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt @@ -1339,7 +1339,8 @@ class BlockAdapter( bind( item = blocks[position] as BlockView.Title.Basic, onPageIconClicked = onPageIconClicked, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = onClickListener ) setTextInputClickListener { if (Build.VERSION.SDK_INT == N || Build.VERSION.SDK_INT == N_MR1) { @@ -1356,7 +1357,8 @@ class BlockAdapter( bind( item = blocks[position] as BlockView.Title.Todo, onPageIconClicked = onPageIconClicked, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = onClickListener ) setTextInputClickListener { if (Build.VERSION.SDK_INT == N || Build.VERSION.SDK_INT == N_MR1) { @@ -1373,7 +1375,8 @@ class BlockAdapter( bind( item = blocks[position] as BlockView.Title.Profile, onProfileIconClicked = onClickListener, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = onClickListener ) setTextInputClickListener { if (Build.VERSION.SDK_INT == N || Build.VERSION.SDK_INT == N_MR1) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt new file mode 100644 index 0000000000..d59bcdc7c4 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/CustomImageResizeTransformation.kt @@ -0,0 +1,71 @@ +package com.anytypeio.anytype.core_ui.features.editor.holders.other + +import android.graphics.Bitmap +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import java.security.MessageDigest +import timber.log.Timber + +class CustomImageResizeTransformation( + private val maxWidth: Int, + private val maxHeight: Int +) : BitmapTransformation() { + + override fun transform( + pool: BitmapPool, + toTransform: Bitmap, + outWidth: Int, + outHeight: Int + ): Bitmap { + return try { + val imageWidth = toTransform.width + val imageHeight = toTransform.height + val targetAspectRatio = maxWidth.toFloat() / maxHeight + + when { + imageWidth > maxWidth && imageHeight > maxHeight -> { + val imageAspectRatio = imageWidth.toFloat() / imageHeight + + if (imageAspectRatio > targetAspectRatio) { + val cropWidth = (imageHeight * targetAspectRatio).toInt() + val cropStartX = (imageWidth - cropWidth) / 2 + Bitmap.createBitmap(toTransform, cropStartX, 0, cropWidth, imageHeight) + } else { + val cropHeight = (imageWidth / targetAspectRatio).toInt() + val cropStartY = (imageHeight - cropHeight) / 2 + Bitmap.createBitmap(toTransform, 0, cropStartY, imageWidth, cropHeight) + } + } + imageWidth > maxWidth && imageHeight <= maxHeight -> { + val scaleFactor = maxWidth.toFloat() / imageWidth + val newHeight = (imageHeight * scaleFactor).toInt() + Bitmap.createScaledBitmap(toTransform, maxWidth, newHeight, true) + } + imageHeight > maxHeight && imageWidth <= maxWidth -> { + val cropHeight = (imageWidth / targetAspectRatio).toInt() + val cropStartY = (imageHeight - cropHeight) / 2 + Bitmap.createBitmap(toTransform, 0, cropStartY, imageWidth, cropHeight) + } + else -> toTransform + } + } catch (e: IllegalArgumentException) { + Timber.e( + e, + "Failed to transform bitmap: Invalid dimensions or parameters provided. Width: ${toTransform.width}, Height: ${toTransform.height}, MaxWidth: $maxWidth, MaxHeight: $maxHeight" + ) + toTransform + } catch (e: OutOfMemoryError) { + Timber.e( + e, + "Failed to transform bitmap: Insufficient memory to process the image." + ) + toTransform + } + } + + override fun equals(other: Any?) = other is CustomImageResizeTransformation + override fun hashCode() = "CustomImageResizeTransformation".hashCode() + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update("CustomImageResizeTransformation".toByteArray()) + } +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt index 442aa63fb7..12ab093d4f 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt @@ -1,6 +1,9 @@ package com.anytypeio.anytype.core_ui.features.editor.holders.other +import android.content.Context +import android.graphics.Bitmap import android.text.Spannable +import android.util.TypedValue import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout.LayoutParams @@ -38,6 +41,11 @@ import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.Target +import java.security.MessageDigest import timber.log.Timber sealed class Title(view: View) : BlockViewHolder(view), TextHolder { @@ -50,7 +58,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { setImage(item) applyTextColor(item) @@ -162,22 +171,34 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { } } } - open fun setImage(item: BlockView.Title) { + Timber.d("Setting image for ${item.id}, image=${item.image}") item.image?.let { url -> image.visible() - Glide - .with(image) - .load(url) - .centerCrop() - .into(image) - } ?: apply { image.setImageDrawable(null) } + loadImageWithCustomResize(image, url) + } ?: run { image.setImageDrawable(null) } } - private fun showKeyboard() { - content.postDelayed(16L) { - imm().showSoftInput(content, InputMethodManager.SHOW_IMPLICIT) - } + private fun loadImageWithCustomResize(imageView: ImageView, url: String) { + val context = imageView.context + val displayMetrics = context.resources.displayMetrics + val screenWidth = displayMetrics.widthPixels + val maxWidth = screenWidth - dpToPx(context, 40) + val maxHeight = dpToPx(context, 443) + + Glide.with(context) + .load(url) + .override(Target.SIZE_ORIGINAL) + .apply(RequestOptions().transform(CustomImageResizeTransformation(maxWidth, maxHeight))) + .into(imageView) + } + + private fun dpToPx(context: Context, dp: Int): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics + ).toInt() } open fun processPayloads( @@ -275,14 +296,25 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Basic, onPageIconClicked: () -> Unit, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { super.bind( item = item, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = click ) setEmoji(item) applySearchHighlights(item) + + image.setOnClickListener { + click( + ListenerType.Picture.TitleView( + item = item + ) + ) + } + if (item.mode == BlockView.Mode.EDIT) { icon.setOnClickListener { onPageIconClicked() } image.setOnClickListener { onPageIconClicked() } @@ -299,9 +331,11 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { topMargin = dimen(R.dimen.dp_10) } binding.imageIcon.updateLayoutParams { - topMargin = if (!item.hasCover) dimen(R.dimen.dp_51) else dimen(R.dimen.dp_102) + topMargin = + if (!item.hasCover) dimen(R.dimen.dp_51) else dimen(R.dimen.dp_102) } } + item.emoji != null -> { binding.imageIcon.gone() binding.docEmojiIconContainer.visible() @@ -309,9 +343,11 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { topMargin = dimen(R.dimen.dp_12) } binding.docEmojiIconContainer.updateLayoutParams { - topMargin = if (!item.hasCover) dimen(R.dimen.dp_60) else dimen(R.dimen.dp_120) + topMargin = + if (!item.hasCover) dimen(R.dimen.dp_60) else dimen(R.dimen.dp_120) } } + else -> { binding.imageIcon.gone() binding.docEmojiIconContainer.gone() @@ -397,9 +433,10 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { override val content: TextInputWidget = binding.title override val selectionView: View = itemView - private val gradientView : ComposeView get() = binding - .docProfileIconContainer - .findViewById(R.id.gradient) + private val gradientView: ComposeView + get() = binding + .docProfileIconContainer + .findViewById(R.id.gradient) private val iconText = binding.imageText private var hasImage = false @@ -411,11 +448,13 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Profile, onProfileIconClicked: (ListenerType) -> Unit, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { super.bind( item = item, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = click ) setupMargins(item) applySearchHighlights(item) @@ -512,11 +551,13 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Todo, onPageIconClicked: () -> Unit, - onCoverClicked: () -> Unit + onCoverClicked: () -> Unit, + click: (ListenerType) -> Unit ) { super.bind( item = item, - onCoverClicked = onCoverClicked + onCoverClicked = onCoverClicked, + click = click ) setLocked(item.mode) checkbox.isSelected = item.isChecked @@ -576,7 +617,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { ) { super.bind( item = item, - onCoverClicked = {} + onCoverClicked = {}, + click = {} ) icon.setIcon(item.icon) } 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 482afdbf79..e575ab68d7 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 @@ -392,4 +392,4 @@ class ObjectIconWidget @JvmOverloads constructor( this.width = emojiSize } } -} \ No newline at end of file +} diff --git a/core-ui/src/main/res/layout/item_block_title.xml b/core-ui/src/main/res/layout/item_block_title.xml index a4335ced61..8e97a2f12e 100644 --- a/core-ui/src/main/res/layout/item_block_title.xml +++ b/core-ui/src/main/res/layout/item_block_title.xml @@ -52,21 +52,24 @@ + tools:visibility="visible" /> 102dp 120dp 203dp + 443dp 4dp 1dp diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index d20d01ad70..80e9e5ef2e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -3843,6 +3843,25 @@ class EditorViewModel( else -> Unit } } + is ListenerType.Picture.TitleView -> { + when (mode) { + EditorMode.Edit, EditorMode.Locked, EditorMode.Read -> { + if (!clicked.item.image.isNullOrEmpty()){ + dispatch( + Command.OpenFullScreenImage( + target = "", + url = clicked.item.image + ) + ) + } else { + Timber.e("Can't proceed with opening full screen image") + sendToast("Something went wrong. Couldn't open image") + } + } + EditorMode.Select -> onBlockMultiSelectClicked(clicked.item.id) + else -> Unit + } + } is ListenerType.Picture.View -> { when (mode) { EditorMode.Edit, EditorMode.Locked, EditorMode.Read -> { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt index 967121370f..e75cbdd261 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/listener/ListenerType.kt @@ -23,6 +23,7 @@ sealed interface ListenerType { } sealed class Picture: ListenerType { + data class TitleView(val item: BlockView.Title.Basic) : Picture() data class View(val target: String) : Picture() data class Placeholder(val target: String) : Picture() data class Upload(val target: String) : Picture() 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 73e54b6c7c..f69226bb9a 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 @@ -1516,7 +1516,7 @@ class DefaultBlockViewRenderer @Inject constructor( text = fieldParser.getObjectName(currentObject), image = currentObject.iconImage?.let { image -> if (image.isNotBlank()) - urlBuilder.thumbnail(image) + urlBuilder.large(image) else null },