Add <DeferReveal> to defer expensive views
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
import {ReactNode, useEffect, useState} from 'react'
|
||||||
|
|
||||||
|
export function DeferReveal({
|
||||||
|
children,
|
||||||
|
defer,
|
||||||
|
// To verify, toggle `forceReveal` between `true` and `false`.
|
||||||
|
iVerifiedThereAreNoLayoutJumps: _unused,
|
||||||
|
forceReveal,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
defer: boolean
|
||||||
|
iVerifiedThereAreNoLayoutJumps: true
|
||||||
|
forceReveal?: never // DEV-only
|
||||||
|
}) {
|
||||||
|
const [isReady, setIsReady] = useState(!defer)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isReady) {
|
||||||
|
setIsReady(true)
|
||||||
|
}
|
||||||
|
}, [isReady])
|
||||||
|
const finalIsReady = forceReveal ?? isReady
|
||||||
|
return finalIsReady ? children : null
|
||||||
|
}
|
||||||
@@ -8,8 +8,10 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef'
|
import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef'
|
||||||
import type {Dimensions} from '#/lib/media/types'
|
import type {Dimensions} from '#/lib/media/types'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
|
import {isAndroid} from '#/platform/detection'
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
|
import {DeferReveal} from '#/components/DeferReveal'
|
||||||
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
|
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
|
||||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -107,84 +109,86 @@ export function AutoSizedImage({
|
|||||||
|
|
||||||
const contents = (
|
const contents = (
|
||||||
<View ref={containerRef} collapsable={false} style={{flex: 1}}>
|
<View ref={containerRef} collapsable={false} style={{flex: 1}}>
|
||||||
<Image
|
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
|
||||||
style={[a.w_full, a.h_full]}
|
<Image
|
||||||
source={image.thumb}
|
style={[a.w_full, a.h_full]}
|
||||||
accessible={true} // Must set for `accessibilityLabel` to work
|
source={image.thumb}
|
||||||
accessibilityIgnoresInvertColors
|
accessible={true} // Must set for `accessibilityLabel` to work
|
||||||
accessibilityLabel={image.alt}
|
accessibilityIgnoresInvertColors
|
||||||
accessibilityHint=""
|
accessibilityLabel={image.alt}
|
||||||
onLoad={e => {
|
accessibilityHint=""
|
||||||
fetchedDimsRef.current = {
|
onLoad={e => {
|
||||||
width: e.source.width,
|
fetchedDimsRef.current = {
|
||||||
height: e.source.height,
|
width: e.source.width,
|
||||||
}
|
height: e.source.height,
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
<MediaInsetBorder />
|
/>
|
||||||
|
<MediaInsetBorder />
|
||||||
|
|
||||||
{(hasAlt || isCropped) && !hideBadge ? (
|
{(hasAlt || isCropped) && !hideBadge ? (
|
||||||
<View
|
<View
|
||||||
accessible={false}
|
accessible={false}
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
{
|
|
||||||
bottom: a.p_xs.padding,
|
|
||||||
right: a.p_xs.padding,
|
|
||||||
gap: 3,
|
|
||||||
},
|
|
||||||
largeAlt && [
|
|
||||||
{
|
{
|
||||||
gap: 4,
|
bottom: a.p_xs.padding,
|
||||||
|
right: a.p_xs.padding,
|
||||||
|
gap: 3,
|
||||||
},
|
},
|
||||||
],
|
largeAlt && [
|
||||||
]}>
|
|
||||||
{isCropped && (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.rounded_xs,
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
{
|
{
|
||||||
padding: 3,
|
gap: 4,
|
||||||
opacity: 0.8,
|
|
||||||
},
|
},
|
||||||
largeAlt && [
|
],
|
||||||
|
]}>
|
||||||
|
{isCropped && (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.rounded_xs,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
{
|
{
|
||||||
padding: 5,
|
padding: 3,
|
||||||
|
opacity: 0.8,
|
||||||
},
|
},
|
||||||
],
|
largeAlt && [
|
||||||
]}>
|
{
|
||||||
<Fullscreen
|
padding: 5,
|
||||||
fill={t.atoms.text_contrast_high.color}
|
},
|
||||||
width={largeAlt ? 18 : 12}
|
],
|
||||||
/>
|
]}>
|
||||||
</View>
|
<Fullscreen
|
||||||
)}
|
fill={t.atoms.text_contrast_high.color}
|
||||||
{hasAlt && (
|
width={largeAlt ? 18 : 12}
|
||||||
<View
|
/>
|
||||||
style={[
|
</View>
|
||||||
a.justify_center,
|
)}
|
||||||
a.rounded_xs,
|
{hasAlt && (
|
||||||
t.atoms.bg_contrast_25,
|
<View
|
||||||
{
|
style={[
|
||||||
padding: 3,
|
a.justify_center,
|
||||||
opacity: 0.8,
|
a.rounded_xs,
|
||||||
},
|
t.atoms.bg_contrast_25,
|
||||||
largeAlt && [
|
|
||||||
{
|
{
|
||||||
padding: 5,
|
padding: 3,
|
||||||
|
opacity: 0.8,
|
||||||
},
|
},
|
||||||
],
|
largeAlt && [
|
||||||
]}>
|
{
|
||||||
<Text
|
padding: 5,
|
||||||
style={[a.font_heavy, largeAlt ? a.text_xs : {fontSize: 8}]}>
|
},
|
||||||
ALT
|
],
|
||||||
</Text>
|
]}>
|
||||||
</View>
|
<Text
|
||||||
)}
|
style={[a.font_heavy, largeAlt ? a.text_xs : {fontSize: 8}]}>
|
||||||
</View>
|
ALT
|
||||||
) : null}
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</DeferReveal>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import {useLingui} from '@lingui/react'
|
|||||||
|
|
||||||
import {HandleRef} from '#/lib/hooks/useHandleRef'
|
import {HandleRef} from '#/lib/hooks/useHandleRef'
|
||||||
import {Dimensions} from '#/lib/media/types'
|
import {Dimensions} from '#/lib/media/types'
|
||||||
|
import {isAndroid} from '#/platform/detection'
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
|
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {DeferReveal} from '#/components/DeferReveal'
|
||||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
@@ -70,20 +72,22 @@ export function GalleryItem({
|
|||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={image.alt || _(msg`Image`)}
|
accessibilityLabel={image.alt || _(msg`Image`)}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
<Image
|
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
|
||||||
source={{uri: image.thumb}}
|
<Image
|
||||||
style={[a.flex_1]}
|
source={{uri: image.thumb}}
|
||||||
accessible={true}
|
style={[a.flex_1]}
|
||||||
accessibilityLabel={image.alt}
|
accessible={true}
|
||||||
accessibilityHint=""
|
accessibilityLabel={image.alt}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityHint=""
|
||||||
onLoad={e => {
|
accessibilityIgnoresInvertColors
|
||||||
thumbDimsRef.current[index] = {
|
onLoad={e => {
|
||||||
width: e.source.width,
|
thumbDimsRef.current[index] = {
|
||||||
height: e.source.height,
|
width: e.source.width,
|
||||||
}
|
height: e.source.height,
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</DeferReveal>
|
||||||
<MediaInsetBorder style={insetBorderStyle} />
|
<MediaInsetBorder style={insetBorderStyle} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{hasAlt && !hideBadges ? (
|
{hasAlt && !hideBadges ? (
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
|
|||||||
import {shareUrl} from '#/lib/sharing'
|
import {shareUrl} from '#/lib/sharing'
|
||||||
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
|
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
|
||||||
import {toNiceDomain} from '#/lib/strings/url-helpers'
|
import {toNiceDomain} from '#/lib/strings/url-helpers'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isAndroid, isNative} from '#/platform/detection'
|
||||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||||
import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
|
import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
|
||||||
import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
|
import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
|
||||||
import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
|
import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {DeferReveal} from '#/components/DeferReveal'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
@@ -87,13 +88,18 @@ export const ExternalLinkEmbed = ({
|
|||||||
: t.atoms.border_contrast_low,
|
: t.atoms.border_contrast_low,
|
||||||
]}>
|
]}>
|
||||||
{imageUri && !embedPlayerParams ? (
|
{imageUri && !embedPlayerParams ? (
|
||||||
<Image
|
<View
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: 1.91,
|
aspectRatio: 1.91,
|
||||||
}}
|
}}>
|
||||||
source={{uri: imageUri}}
|
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
|
||||||
accessibilityIgnoresInvertColors
|
<Image
|
||||||
/>
|
style={{flex: 1}}
|
||||||
|
source={{uri: imageUri}}
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
/>
|
||||||
|
</DeferReveal>
|
||||||
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
||||||
{embedPlayerParams?.isGif ? (
|
{embedPlayerParams?.isGif ? (
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import {useNavigation} from '@react-navigation/native'
|
|||||||
|
|
||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
|
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isAndroid, isNative} from '#/platform/detection'
|
||||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {DeferReveal} from '#/components/DeferReveal'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
||||||
import {Fill} from '#/components/Fill'
|
import {Fill} from '#/components/Fill'
|
||||||
@@ -217,43 +218,45 @@ export function ExternalPlayer({
|
|||||||
ref={viewRef}
|
ref={viewRef}
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
style={[aspect, a.overflow_hidden]}>
|
style={[aspect, a.overflow_hidden]}>
|
||||||
{link.thumb && (!isPlayerActive || isLoading) ? (
|
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
|
||||||
<>
|
{link.thumb && (!isPlayerActive || isLoading) ? (
|
||||||
<Image
|
<>
|
||||||
style={[a.flex_1]}
|
<Image
|
||||||
source={{uri: link.thumb}}
|
style={[a.flex_1]}
|
||||||
accessibilityIgnoresInvertColors
|
source={{uri: link.thumb}}
|
||||||
/>
|
accessibilityIgnoresInvertColors
|
||||||
|
/>
|
||||||
|
<Fill
|
||||||
|
style={[
|
||||||
|
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
||||||
|
{
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<Fill
|
<Fill
|
||||||
style={[
|
style={[
|
||||||
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
|
||||||
{
|
{
|
||||||
|
backgroundColor:
|
||||||
|
t.name === 'light' ? t.palette.contrast_975 : 'black',
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</>
|
)}
|
||||||
) : (
|
<PlaceholderOverlay
|
||||||
<Fill
|
isLoading={isLoading}
|
||||||
style={[
|
isPlayerActive={isPlayerActive}
|
||||||
{
|
onPress={onPlayPress}
|
||||||
backgroundColor:
|
|
||||||
t.name === 'light' ? t.palette.contrast_975 : 'black',
|
|
||||||
opacity: 0.3,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<Player
|
||||||
<PlaceholderOverlay
|
isPlayerActive={isPlayerActive}
|
||||||
isLoading={isLoading}
|
params={params}
|
||||||
isPlayerActive={isPlayerActive}
|
onLoad={onLoad}
|
||||||
onPress={onPlayPress}
|
/>
|
||||||
/>
|
</DeferReveal>
|
||||||
<Player
|
|
||||||
isPlayerActive={isPlayerActive}
|
|
||||||
params={params}
|
|
||||||
onLoad={onLoad}
|
|
||||||
/>
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import {useLingui} from '@lingui/react'
|
|||||||
|
|
||||||
import {HITSLOP_20} from '#/lib/constants'
|
import {HITSLOP_20} from '#/lib/constants'
|
||||||
import {EmbedPlayerParams} from '#/lib/strings/embed-player'
|
import {EmbedPlayerParams} from '#/lib/strings/embed-player'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isAndroid, isWeb} from '#/platform/detection'
|
||||||
import {useAutoplayDisabled} from '#/state/preferences'
|
import {useAutoplayDisabled} from '#/state/preferences'
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {DeferReveal} from '#/components/DeferReveal'
|
||||||
import {Fill} from '#/components/Fill'
|
import {Fill} from '#/components/Fill'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
@@ -121,46 +122,48 @@ export function GifEmbed({
|
|||||||
{aspectRatio: params.dimensions!.width / params.dimensions!.height},
|
{aspectRatio: params.dimensions!.width / params.dimensions!.height},
|
||||||
style,
|
style,
|
||||||
]}>
|
]}>
|
||||||
<View
|
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
|
||||||
style={[
|
<View
|
||||||
a.absolute,
|
style={[
|
||||||
/*
|
a.absolute,
|
||||||
* Aspect ratio was being clipped weirdly on web -esb
|
/*
|
||||||
*/
|
* Aspect ratio was being clipped weirdly on web -esb
|
||||||
{
|
*/
|
||||||
top: -2,
|
{
|
||||||
bottom: -2,
|
top: -2,
|
||||||
left: -2,
|
bottom: -2,
|
||||||
right: -2,
|
left: -2,
|
||||||
},
|
right: -2,
|
||||||
]}>
|
},
|
||||||
<PlaybackControls
|
]}>
|
||||||
onPress={onPress}
|
<PlaybackControls
|
||||||
isPlaying={playerState.isPlaying}
|
onPress={onPress}
|
||||||
isLoaded={playerState.isLoaded}
|
isPlaying={playerState.isPlaying}
|
||||||
/>
|
isLoaded={playerState.isLoaded}
|
||||||
<GifView
|
|
||||||
source={params.playerUri}
|
|
||||||
placeholderSource={thumb}
|
|
||||||
style={[a.flex_1]}
|
|
||||||
autoplay={!autoplayDisabled}
|
|
||||||
onPlayerStateChange={onPlayerStateChange}
|
|
||||||
ref={playerRef}
|
|
||||||
accessibilityHint={_(msg`Animated GIF`)}
|
|
||||||
accessibilityLabel={altText}
|
|
||||||
/>
|
|
||||||
{!playerState.isPlaying && (
|
|
||||||
<Fill
|
|
||||||
style={[
|
|
||||||
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
|
||||||
{
|
|
||||||
opacity: 0.3,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<GifView
|
||||||
{!hideAlt && isPreferredAltText && <AltText text={altText} />}
|
source={params.playerUri}
|
||||||
</View>
|
placeholderSource={thumb}
|
||||||
|
style={[a.flex_1]}
|
||||||
|
autoplay={!autoplayDisabled}
|
||||||
|
onPlayerStateChange={onPlayerStateChange}
|
||||||
|
ref={playerRef}
|
||||||
|
accessibilityHint={_(msg`Animated GIF`)}
|
||||||
|
accessibilityLabel={altText}
|
||||||
|
/>
|
||||||
|
{!playerState.isPlaying && (
|
||||||
|
<Fill
|
||||||
|
style={[
|
||||||
|
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
||||||
|
{
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!hideAlt && isPreferredAltText && <AltText text={altText} />}
|
||||||
|
</View>
|
||||||
|
</DeferReveal>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user