diff --git a/src/components/DeferReveal.tsx b/src/components/DeferReveal.tsx
new file mode 100644
index 0000000000..98c677ebda
--- /dev/null
+++ b/src/components/DeferReveal.tsx
@@ -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
+}
diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx
index 617b9bec4b..a89d85ce98 100644
--- a/src/view/com/util/images/AutoSizedImage.tsx
+++ b/src/view/com/util/images/AutoSizedImage.tsx
@@ -8,8 +8,10 @@ import {useLingui} from '@lingui/react'
import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef'
import type {Dimensions} from '#/lib/media/types'
import {isNative} from '#/platform/detection'
+import {isAndroid} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
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 {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
@@ -107,84 +109,86 @@ export function AutoSizedImage({
const contents = (
- {
- fetchedDimsRef.current = {
- width: e.source.width,
- height: e.source.height,
- }
- }}
- />
-
+
+ {
+ fetchedDimsRef.current = {
+ width: e.source.width,
+ height: e.source.height,
+ }
+ }}
+ />
+
- {(hasAlt || isCropped) && !hideBadge ? (
-
- {isCropped && (
-
+ {isCropped && (
+
-
-
- )}
- {hasAlt && (
-
+
+
+ )}
+ {hasAlt && (
+
-
- ALT
-
-
- )}
-
- ) : null}
+ largeAlt && [
+ {
+ padding: 5,
+ },
+ ],
+ ]}>
+
+ ALT
+
+
+ )}
+
+ ) : null}
+
)
diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx
index cc3eda68d2..c0840d45e1 100644
--- a/src/view/com/util/images/Gallery.tsx
+++ b/src/view/com/util/images/Gallery.tsx
@@ -7,9 +7,11 @@ import {useLingui} from '@lingui/react'
import {HandleRef} from '#/lib/hooks/useHandleRef'
import {Dimensions} from '#/lib/media/types'
+import {isAndroid} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useTheme} from '#/alf'
+import {DeferReveal} from '#/components/DeferReveal'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
@@ -70,20 +72,22 @@ export function GalleryItem({
accessibilityRole="button"
accessibilityLabel={image.alt || _(msg`Image`)}
accessibilityHint="">
- {
- thumbDimsRef.current[index] = {
- width: e.source.width,
- height: e.source.height,
- }
- }}
- />
+
+ {
+ thumbDimsRef.current[index] = {
+ width: e.source.width,
+ height: e.source.height,
+ }
+ }}
+ />
+
{hasAlt && !hideBadges ? (
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
index 64e869c94a..c85d219b63 100644
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
@@ -9,12 +9,13 @@ import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
import {shareUrl} from '#/lib/sharing'
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
import {toNiceDomain} from '#/lib/strings/url-helpers'
-import {isNative} from '#/platform/detection'
+import {isAndroid, isNative} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
import {atoms as a, useTheme} from '#/alf'
+import {DeferReveal} from '#/components/DeferReveal'
import {Divider} from '#/components/Divider'
import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {Link} from '#/components/Link'
@@ -87,13 +88,18 @@ export const ExternalLinkEmbed = ({
: t.atoms.border_contrast_low,
]}>
{imageUri && !embedPlayerParams ? (
-
+ }}>
+
+
+
+
) : undefined}
{embedPlayerParams?.isGif ? (
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
index 8ac7ee499d..172bc1cfcf 100644
--- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
@@ -23,9 +23,10 @@ import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from '#/lib/routes/types'
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 {atoms as a, useTheme} from '#/alf'
+import {DeferReveal} from '#/components/DeferReveal'
import {useDialogControl} from '#/components/Dialog'
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
import {Fill} from '#/components/Fill'
@@ -217,43 +218,45 @@ export function ExternalPlayer({
ref={viewRef}
collapsable={false}
style={[aspect, a.overflow_hidden]}>
- {link.thumb && (!isPlayerActive || isLoading) ? (
- <>
-
+
+ {link.thumb && (!isPlayerActive || isLoading) ? (
+ <>
+
+
+ >
+ ) : (
- >
- ) : (
-
- )}
-
-
+
+
>
)
diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx
index 4dbc7d588f..6b0366bc20 100644
--- a/src/view/com/util/post-embeds/GifEmbed.tsx
+++ b/src/view/com/util/post-embeds/GifEmbed.tsx
@@ -12,10 +12,11 @@ import {useLingui} from '@lingui/react'
import {HITSLOP_20} from '#/lib/constants'
import {EmbedPlayerParams} from '#/lib/strings/embed-player'
-import {isWeb} from '#/platform/detection'
+import {isAndroid, isWeb} from '#/platform/detection'
import {useAutoplayDisabled} from '#/state/preferences'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {atoms as a, useTheme} from '#/alf'
+import {DeferReveal} from '#/components/DeferReveal'
import {Fill} from '#/components/Fill'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
@@ -121,46 +122,48 @@ export function GifEmbed({
{aspectRatio: params.dimensions!.width / params.dimensions!.height},
style,
]}>
-
-
-
- {!playerState.isPlaying && (
-
+
+
- )}
- {!hideAlt && isPreferredAltText && }
-
+
+ {!playerState.isPlaying && (
+
+ )}
+ {!hideAlt && isPreferredAltText && }
+
+
)
}