From af723f69482c229c9cc4fc018b3b5570e3346c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Tue, 25 Nov 2025 10:20:32 +0100 Subject: [PATCH 01/14] feat: add dimensions to images on Android --- .../enriched/EnrichedTextInputView.kt | 4 +- .../enriched/EnrichedTextInputViewManager.kt | 4 +- .../enriched/spans/EnrichedImageSpan.kt | 9 +- .../swmansion/enriched/styles/HtmlStyle.kt | 12 +-- .../enriched/styles/ParametrizedStyles.kt | 8 +- example/src/App.tsx | 22 +++- example/src/components/ImageModal.tsx | 100 ++++++++++++++++++ src/EnrichedTextInput.tsx | 11 +- src/EnrichedTextInputNativeComponent.ts | 7 +- 9 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 example/src/components/ImageModal.tsx diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt index 086b5d8e9..130ba1d61 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt @@ -527,11 +527,11 @@ class EnrichedTextInputView : AppCompatEditText { parametrizedStyles?.setLinkSpan(start, end, text, url) } - fun addImage(src: String) { + fun addImage(src: String, width: Float?, height: Float?) { val isValid = verifyStyle(EnrichedSpans.IMAGE) if (!isValid) return - parametrizedStyles?.setImageSpan(src) + parametrizedStyles?.setImageSpan(src, width, height) layoutManager.invalidateLayout() } diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt index 286a19b7b..439b4e87c 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt @@ -255,8 +255,8 @@ class EnrichedTextInputViewManager : SimpleViewManager(), view?.addLink(start, end, text, url) } - override fun addImage(view: EnrichedTextInputView?, src: String) { - view?.addImage(src) + override fun addImage(view: EnrichedTextInputView?, src: String, width: Float, height: Float) { + view?.addImage(src, width, height) } override fun startMention(view: EnrichedTextInputView?, indicator: String) { diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt index 708050d62..6e2092402 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt @@ -11,10 +11,13 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle class EnrichedImageSpan : ImageSpan, EnrichedInlineSpan { + private var width: Float = 0F + private var height: Float = 0F private var htmlStyle: HtmlStyle? = null - constructor(context: Context, uri: Uri, htmlStyle: HtmlStyle, ) : super(context, uri, ALIGN_BASELINE) { - this.htmlStyle = htmlStyle + constructor(context: Context, uri: Uri, width: Float, height: Float) : super(context, uri, ALIGN_BASELINE) { + this.width = width + this.height = height } constructor(drawable: Drawable, source: String, htmlStyle: HtmlStyle) : super(drawable, source, ALIGN_BASELINE) { @@ -35,7 +38,7 @@ class EnrichedImageSpan : ImageSpan, EnrichedInlineSpan { override fun getDrawable(): Drawable { val drawable = super.getDrawable() - drawable.setBounds(0, 0, htmlStyle!!.imgWidth, htmlStyle!!.imgHeight) + drawable.setBounds(0, 0, width.toInt(), height.toInt()) return drawable } } diff --git a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt index 8a4b9088f..df473422d 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt @@ -43,8 +43,8 @@ class HtmlStyle { var ulBulletSize: Int = 8 var ulBulletColor: Int = Color.BLACK - var imgWidth: Int = 200 - var imgHeight: Int = 200 + var imgWidth: Float = 200F + var imgHeight: Float = 200F var aColor: Int = Color.BLACK var aUnderline: Boolean = true @@ -101,8 +101,8 @@ class HtmlStyle { ulBulletSize = parseFloat(ulStyle, "bulletSize").toInt() val imgStyle = style.getMap("img") - imgWidth = parseFloat(imgStyle, "width").toInt() - imgHeight = parseFloat(imgStyle, "height").toInt() + imgWidth = parseFloat(imgStyle, "width") + imgHeight = parseFloat(imgStyle, "height") val aStyle = style.getMap("a") aColor = parseColor(aStyle, "color") @@ -124,8 +124,8 @@ class HtmlStyle { private fun parseFloat(map: ReadableMap?, key: String): Float { val safeMap = ensureValueIsSet(map, key) - val fontSize = safeMap.getDouble(key) - return ceil(PixelUtil.toPixelFromSP(fontSize)) + val value = safeMap.getDouble(key) + return ceil(PixelUtil.toPixelFromSP(value)) } private fun parseColorWithOpacity(map: ReadableMap?, key: String, opacity: Int): Int { diff --git a/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt b/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt index 9b8665606..7ed5a88d2 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt @@ -170,7 +170,7 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { } } - fun setImageSpan(src: String) { + fun setImageSpan(src: String, width: Float?, height: Float?) { if (view.selection == null) return val spannable = view.text as SpannableStringBuilder @@ -187,7 +187,11 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { } val uri = Uri.fromFile(File(src)) - val span = EnrichedImageSpan(view.context, uri, view.htmlStyle) + + val imageWidth = width ?: view.htmlStyle.imgWidth + val imageHeight = height ?: view.htmlStyle.imgHeight + + val span = EnrichedImageSpan(view.context, uri, imageWidth, imageHeight) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end) spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } diff --git a/example/src/App.tsx b/example/src/App.tsx index 3c29531fe..9994274b9 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -27,6 +27,7 @@ import { type MentionItem, MentionPopup } from './components/MentionPopup'; import { useUserMention } from './useUserMention'; import { useChannelMention } from './useChannelMention'; import { HtmlSection } from './components/HtmlSection'; +import { ImageModal } from './components/ImageModal'; type StylesState = OnChangeStateEvent; @@ -75,6 +76,7 @@ export default function App() { const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false); const [isUserPopupOpen, setIsUserPopupOpen] = useState(false); const [isLinkModalOpen, setIsLinkModalOpen] = useState(false); + const [isImageModalOpen, setIsImageModalOpen] = useState(false); const [isValueModalOpen, setIsValueModalOpen] = useState(false); const [currentHtml, setCurrentHtml] = useState(''); @@ -125,6 +127,14 @@ export default function App() { setIsLinkModalOpen(false); }; + const openImageModal = () => { + setIsImageModalOpen(true); + }; + + const closeImageModal = () => { + setIsImageModalOpen(false); + }; + const openUserMentionPopup = () => { setIsUserPopupOpen(true); }; @@ -197,7 +207,8 @@ export default function App() { closeValueModal(); }; - const selectImage = async () => { + const selectImage = async (width: number, height: number) => { + closeImageModal(); const response = await launchImageLibrary({ mediaType: 'photo', selectionLimit: 1, @@ -210,7 +221,7 @@ export default function App() { if (!imageUri) return; - ref.current?.setImage(imageUri); + ref.current?.setImage(imageUri, width, height); }; const handleChangeMention = ({ indicator, text }: OnChangeMentionEvent) => { @@ -294,7 +305,7 @@ export default function App() { stylesState={stylesState} editorRef={ref} onOpenLinkModal={openLinkModal} - onSelectImage={selectImage} + onSelectImage={openImageModal} /> @@ -318,6 +329,11 @@ export default function App() { onSubmit={submitLink} onClose={closeLinkModal} /> + void; + onSubmit: (width: number, height: number) => void; +} + +export const ImageModal: FC = ({ + isOpen, + onClose, + onSubmit, +}) => { + const [width, setWidth] = useState(''); + const [height, setHeight] = useState(''); + + const handleSave = () => { + const parsedWidth = parseFloat(width); + const parsedHeight = parseFloat(height); + const finalWidth = isNaN(parsedWidth) ? 0 : parsedWidth; + const finalHeight = isNaN(parsedHeight) ? 0 : parsedHeight; + + onSubmit(finalWidth, finalHeight); + }; + + return ( + + + + + + + + + + + +