mirror of
https://github.com/RSSNext/Folo.git
synced 2026-10-16 20:51:43 +08:00
fix(mobile): stop loading every image of an entry at once
Entries in the Pictures view can carry dozens of images (cosplaytele posts have 25 to 110), and MediaCarousel rendered every page of its ScrollView, so each mounted row loaded all of them. Until onLayout the carousel was 0 px tall, so FlashList measured rows at footer height, mounted every row of the page and kept them in its recycle pool. The lightbox likewise mounted the original of every page. Load images only for the visible page and its neighbors in both, and size the carousel by its aspect ratio before its width is measured. On an Android emulator with 20 cosplaytele entries (1142 images), opening the Pictures view took the native heap from 96 MB to 2.1 GB before this change and to about 170 MB after it. Refs #5108 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import type { MediaModel } from "@follow/database/schemas/types"
|
||||
import { useRecyclingState } from "@shopify/flash-list"
|
||||
import type { ImageSource } from "expo-image"
|
||||
import type { Ref } from "react"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { ScrollView, View } from "react-native"
|
||||
import Animated, {
|
||||
interpolateColor,
|
||||
@@ -41,6 +41,15 @@ export const MediaCarousel = ({
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
const containerHeight = Math.floor(containerWidth / aspectRatio)
|
||||
const hasMany = media.length > 1
|
||||
// Media without an image get no page, so a page's position can differ from its media index
|
||||
const pages = useMemo(
|
||||
() =>
|
||||
media.flatMap((m, index) => {
|
||||
const imageUrl = m.type === "video" ? m.preview_image_url : m.url
|
||||
return imageUrl ? [{ media: m, index, imageUrl }] : []
|
||||
}),
|
||||
[media],
|
||||
)
|
||||
|
||||
// The list recycles this row for other entries, which start at their first image
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
@@ -54,7 +63,9 @@ export const MediaCarousel = ({
|
||||
setContainerWidth(e.nativeEvent.layout.width)
|
||||
}}
|
||||
>
|
||||
<View ref={ref} className="relative overflow-hidden rounded-md">
|
||||
{/* Sized by the aspect ratio before the width is measured: a row laid out without its image
|
||||
is so short that the list mounts far more rows than fit on screen */}
|
||||
<View ref={ref} className="relative overflow-hidden rounded-md" style={{ aspectRatio }}>
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
onMomentumScrollEnd={(e) => {
|
||||
@@ -69,10 +80,12 @@ export const MediaCarousel = ({
|
||||
// See https://github.com/Shopify/flash-list/issues/797
|
||||
style={{ height: containerHeight }}
|
||||
>
|
||||
{media.map((m, index) => {
|
||||
const imageUrl = m.type === "video" ? m.preview_image_url : m.url
|
||||
if (!imageUrl) {
|
||||
return null
|
||||
{pages.map(({ media: m, index, imageUrl }, position) => {
|
||||
// An entry can carry dozens of images, so only the visible page and its neighbors load
|
||||
if (Math.abs(position - activeIndex) > 1) {
|
||||
return (
|
||||
<View key={imageUrl} style={{ width: containerWidth, height: containerHeight }} />
|
||||
)
|
||||
}
|
||||
const proxy = {
|
||||
height: 400,
|
||||
|
||||
@@ -320,20 +320,23 @@ function ImageView({
|
||||
>
|
||||
{images.map((imageSrc, i) => (
|
||||
<View key={imageSrc.uri}>
|
||||
<LightboxImage
|
||||
onTap={onTap}
|
||||
onZoom={onZoom}
|
||||
imageSrc={imageSrc}
|
||||
onRequestClose={onRequestClose}
|
||||
isScrollViewBeingDragged={isDragging}
|
||||
showControls={showControls}
|
||||
safeAreaRef={safeAreaRef}
|
||||
isScaled={isScaled}
|
||||
isFlyingAway={isFlyingAway}
|
||||
isActive={i === imageIndex}
|
||||
dismissSwipeTranslateY={dismissSwipeTranslateY}
|
||||
openProgress={openProgress}
|
||||
/>
|
||||
{/* An entry can carry dozens of images, so only the visible page and its neighbors load */}
|
||||
{Math.abs(i - imageIndex) <= 1 && (
|
||||
<LightboxImage
|
||||
onTap={onTap}
|
||||
onZoom={onZoom}
|
||||
imageSrc={imageSrc}
|
||||
onRequestClose={onRequestClose}
|
||||
isScrollViewBeingDragged={isDragging}
|
||||
showControls={showControls}
|
||||
safeAreaRef={safeAreaRef}
|
||||
isScaled={isScaled}
|
||||
isFlyingAway={isFlyingAway}
|
||||
isActive={i === imageIndex}
|
||||
dismissSwipeTranslateY={dismissSwipeTranslateY}
|
||||
openProgress={openProgress}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</PagerView>
|
||||
|
||||
Reference in New Issue
Block a user