authorgravatar for 76637177+SunkenInTime@users.noreply.github.comDara Adedeji <76637177+SunkenInTime@users.noreply.github.com> 2026-08-23 10:38:06-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-23 13:38:06-04:00
log55c9093344a5fdbc390d713675429599fff27dbd
tree2635b77df532cd9ef1f784376c161cbe10febd14
parentb1670ac7d9b5b7bb9d7ebd969f27384daee22813
signature Signed by PGP key B5690EEEBB952194

fix(mobile): isolate markdown image requests (#7942)


1 files changed, 36 insertions(+), 17 deletions(-)

apps/mobile/src/features/threads/ThreadFeed.tsx+36-17
......@@ -209,8 +209,6 @@ function ThreadMarkdownImageView(props: {
209209 const [availableWidth, setAvailableWidth] = useState(0);
210210 const [sourceSize, setSourceSize] = useState<{ width: number; height: number } | null>(null);
211211 const [failedUri, setFailedUri] = useState<string | null>(null);
212 const activeUriRef = useRef(props.uri);
213 activeUriRef.current = props.uri;
214212
215213 useEffect(() => {
216214 setSourceSize(null);
......@@ -272,23 +270,12 @@ function ThreadMarkdownImageView(props: {
272270 overflow: "hidden",
273271 }}
274272 >
275 <Image
276 source={{ uri: props.uri }}
277 resizeMode="contain"
278 accessible={false}
279 onLoad={(event) => {
280 if (activeUriRef.current !== props.uri) return;
281 const { width, height } = event.nativeEvent.source;
282 setSourceSize({ width, height });
283 }}
273 <ThreadMarkdownImageRequest
274 key={props.uri}
275 uri={props.uri}
276 onLoad={setSourceSize}
284277 onError={() => setFailedUri(props.uri)}
285 style={{
286 width: "100%",
287 height: "100%",
288 opacity: displaySize === null ? 0 : 1,
289 }}
290278 />
291 {displaySize === null ? <ActivityIndicator style={StyleSheet.absoluteFill} /> : null}
292279 </View>
293280 </TouchableOpacity>
294281 )}
......@@ -301,6 +288,38 @@ function ThreadMarkdownImageView(props: {
301288 );
302289}
303290
291function ThreadMarkdownImageRequest(props: {
292 readonly uri: string;
293 readonly onLoad: (sourceSize: { width: number; height: number }) => void;
294 readonly onError: () => void;
295}) {
296 const [loaded, setLoaded] = useState(false);
297
298 return (
299 <>
300 <Image
301 source={{ uri: props.uri }}
302 resizeMode="contain"
303 accessible={false}
304 onLoad={(event) => {
305 setLoaded(true);
306 props.onLoad(event.nativeEvent.source);
307 }}
308 onError={props.onError}
309 style={{ width: "100%", height: "100%", opacity: loaded ? 1 : 0 }}
310 />
311 {loaded ? null : (
312 <View
313 pointerEvents="none"
314 style={[StyleSheet.absoluteFill, { alignItems: "center", justifyContent: "center" }]}
315 >
316 <Text className="text-xs text-foreground-muted">Loading image…</Text>
317 </View>
318 )}
319 </>
320 );
321}
322
304323/** Markdown image whose src is a workspace file — loads through a signed asset URL. */
305324function ThreadMarkdownImage(props: {
306325 readonly environmentId: EnvironmentId;