feat(app): SVG-Inline-Rendering via react-native-svg

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 18:33:30 +02:00
parent 6aae565541
commit b696b47feb
2 changed files with 36 additions and 24 deletions
+19 -8
View File
@@ -4,25 +4,24 @@
*
* - Markdown-Syntax `![alt](url)` und plain `https://...image.png` werden
* erkannt — die URL bleibt im Text sichtbar (klickbar via Linkify),
* zusaetzlich wird das Bild als <Image> drunter gerendert.
* zusaetzlich wird das Bild als <Image> oder <SvgUri> drunter gerendert.
* - Wir nutzen Androids dataDetectorType="all" (System macht Phone/URL/Email
* automatisch klickbar) und ein einzelnes <Text selectable> ohne nested
* <Text> mit eigenem onPress — Nested Text mit onPress fing die Long-Press-
* Geste ab, damit war Markieren+Kopieren defekt.
* - SVGs werden uebersprungen (React Native Image kann SVG nicht ohne Lib).
*/
import React, { useEffect, useState } from 'react';
import { View, Text, Image, TextStyle, StyleProp } from 'react-native';
import { SvgUri } from 'react-native-svg';
interface Props {
text: string;
style?: StyleProp<TextStyle>;
}
// Bild-URL-Pattern: http(s)://... endend auf jpg/png/gif/webp/bmp/ico (kein
// SVG — das kann RN Image ohne externe Lib nicht).
const IMG_URL_RE = /https?:\/\/[^\s)<"']+\.(?:jpe?g|png|gif|webp|bmp|ico)(?:\?[^\s)<"']*)?/gi;
// Bild-URL-Pattern: http(s)://... endend auf gaengige Bild-Endungen.
const IMG_URL_RE = /https?:\/\/[^\s)<"']+\.(?:jpe?g|png|gif|webp|bmp|ico|svg)(?:\?[^\s)<"']*)?/gi;
function extractImageUrls(text: string): string[] {
const urls = new Set<string>();
@@ -31,11 +30,16 @@ function extractImageUrls(text: string): string[] {
return Array.from(urls);
}
/** Image mit dynamischer Aspect-Ratio aus echten Bilddimensionen. */
const SVG_RE = /\.svg(?:\?|$)/i;
/** Image mit dynamischer Aspect-Ratio aus echten Bilddimensionen.
* SVGs werden ueber react-native-svg gerendert (kein Image.getSize). */
const InlineImage: React.FC<{ uri: string }> = ({ uri }) => {
const [aspectRatio, setAspectRatio] = useState<number>(16 / 9);
const isSvg = SVG_RE.test(uri);
const [aspectRatio, setAspectRatio] = useState<number>(1);
const [failed, setFailed] = useState(false);
useEffect(() => {
if (isSvg) return; // Image.getSize geht fuer SVG nicht
let cancelled = false;
Image.getSize(
uri,
@@ -43,8 +47,15 @@ const InlineImage: React.FC<{ uri: string }> = ({ uri }) => {
() => { if (!cancelled) setFailed(true); },
);
return () => { cancelled = true; };
}, [uri]);
}, [uri, isSvg]);
if (failed) return null;
if (isSvg) {
return (
<View style={{ marginTop: 8, width: 260, height: 260, backgroundColor: '#0D0D1A', borderRadius: 8, alignItems: 'center', justifyContent: 'center' }}>
<SvgUri uri={uri} width="100%" height="100%" onError={() => setFailed(true)} />
</View>
);
}
return (
<Image
source={{ uri }}