diff --git a/apps/mobile-app/components/items/ItemIcon.tsx b/apps/mobile-app/components/items/ItemIcon.tsx
index 2c305f21e..dfe567bc8 100644
--- a/apps/mobile-app/components/items/ItemIcon.tsx
+++ b/apps/mobile-app/components/items/ItemIcon.tsx
@@ -1,7 +1,7 @@
import { Buffer } from 'buffer';
import { Image, ImageStyle, StyleSheet, View } from 'react-native';
-import { SvgUri } from 'react-native-svg';
+import { SvgXml } from 'react-native-svg';
import type { Item } from '@/utils/dist/core/models/vault';
import {
@@ -123,7 +123,9 @@ function renderLogo(
style?: ImageStyle
): React.ReactNode {
/**
- * Get the logo source.
+ * Get the logo source. For SVGs, returns the raw XML string so SvgXml can
+ * render it safely with fallback/onError support. For other formats, returns
+ * a data URI for the Image component.
*/
const getLogoSource = (data: Uint8Array | number[] | string | null | undefined) : { type: 'image' | 'svg', source: string | number } => {
if (!data) {
@@ -134,20 +136,22 @@ function renderLogo(
// If logo is already a base64 string (from iOS SQLite query result)
if (typeof data === 'string') {
const mimeType = detectMimeTypeFromBase64(data);
- return {
- type: mimeType === 'image/svg+xml' ? 'svg' : 'image',
- source: `data:${mimeType};base64,${data}`
- };
+ if (mimeType === 'image/svg+xml') {
+ // Decode base64 to raw SVG XML for SvgXml component
+ return { type: 'svg', source: Buffer.from(data, 'base64').toString('utf-8') };
+ }
+ return { type: 'image', source: `data:${mimeType};base64,${data}` };
}
// Handle binary data (from Android or other sources)
const logoBytes = toUint8Array(data);
- const base64Logo = Buffer.from(logoBytes).toString('base64');
const mimeType = detectMimeType(logoBytes);
- return {
- type: mimeType === 'image/svg+xml' ? 'svg' : 'image',
- source: `data:${mimeType};base64,${base64Logo}`
- };
+ if (mimeType === 'image/svg+xml') {
+ // Decode bytes to raw SVG XML for SvgXml component
+ return { type: 'svg', source: new TextDecoder().decode(logoBytes) };
+ }
+ const base64Logo = Buffer.from(logoBytes).toString('base64');
+ return { type: 'image', source: `data:${mimeType};base64,${base64Logo}` };
} catch (error) {
console.error('Error converting logo:', error);
return { type: 'image', source: servicePlaceholder };
@@ -158,18 +162,47 @@ function renderLogo(
if (logoSource.type === 'svg') {
/*
- * SVGs are not supported in React Native Image component,
- * so we use SvgUri from react-native-svg.
+ * Use SvgXml instead of SvgUri to render SVG logos. SvgXml accepts raw XML
+ * and supports onError/fallback props, which lets us gracefully handle
+ * malformed SVGs that would otherwise crash the native renderer
+ * (e.g. zero-dimension SVGs triggering UIGraphicsBeginImageContext failures).
*/
+ console.log('logoSource', logoSource);
+ const svgWidth = Number(style?.width ?? styles.logo.width);
+ const svgHeight = Number(style?.height ?? styles.logo.height);
+
+ const svgXml = sanitizeSvg(logoSource.source as string, svgWidth, svgHeight);
+
+ // If sanitization failed (returned null), fall back to placeholder
+ if (!svgXml) {
+ return (
+
+ );
+ }
+
+ const fallback = (
+
+ );
+
return (
- {
+ console.warn('SvgXml failed to render SVG logo');
+ }}
+ fallback={fallback}
style={{
borderRadius: styles.logo.borderRadius,
- width: Number(style?.width ?? styles.logo.width),
- height: Number(style?.height ?? styles.logo.height),
+ width: svgWidth,
+ height: svgHeight,
marginLeft: Number(style?.marginLeft ?? 0),
marginRight: Number(style?.marginRight ?? 0),
marginTop: Number(style?.marginTop ?? 0),
@@ -188,6 +221,107 @@ function renderLogo(
);
}
+/**
+ * Sanitize SVG XML for react-native-svg compatibility.
+ *
+ * Addresses several crash vectors:
+ * 1. Zero/missing dimensions on the root