Add OTA fallback for gallery embed

Pre-update native builds don't recognize app.bsky.embed.gallery (Photos
v2, >4 images). Bump @atproto/api to 0.20.9 so the OTA bundle can parse
the embed, and render a friendly "Something new is here" prompt with the
photo count and a link to bsky.app/download in place of the unrenderable
gallery. Native-only; web ships with full gallery support in the same
release.

APP-2308
This commit is contained in:
vineyardbovines
2026-06-04 17:05:14 -04:00
parent 923e1635d8
commit f9023f48b1
7 changed files with 140 additions and 7 deletions
+1 -1
View File
@@ -93,7 +93,7 @@
"prettier": "prettier --check ."
},
"dependencies": {
"@atproto/api": "0.20.8",
"@atproto/api": "0.20.9",
"@atproto/syntax": "0.6.1",
"@bitdrift/react-native": "^0.6.8",
"@braintree/sanitize-url": "^6.0.2",
+5 -5
View File
@@ -242,8 +242,8 @@ importers:
.:
dependencies:
'@atproto/api':
specifier: 0.20.8
version: 0.20.8
specifier: 0.20.9
version: 0.20.9
'@atproto/syntax':
specifier: 0.6.1
version: 0.6.1
@@ -877,8 +877,8 @@ packages:
graphql:
optional: true
'@atproto/api@0.20.8':
resolution: {integrity: sha512-rTkA6kOmA2axSrg6VgpdXpsCFWpofnHBOn6pKg69Ju5MpIHqk4haQMgjBcVh1G3kUxzwgSAr7SYrPS3dFe5Etg==}
'@atproto/api@0.20.9':
resolution: {integrity: sha512-Yuw7Ewn+yMJZ8GskbuvI3lKPW65rsXic1xjFA2Dpq6H8WjVYs6xNZ31bkwtTYDDwjKIZcJmAVbAVgdfjo4T9iw==}
engines: {node: '>=22'}
'@atproto/common-web@0.5.0':
@@ -9493,7 +9493,7 @@ snapshots:
'@0no-co/graphql.web@1.2.0': {}
'@atproto/api@0.20.8':
'@atproto/api@0.20.9':
dependencies:
'@atproto/common-web': 0.5.0
'@atproto/lexicon': 0.7.1
@@ -0,0 +1,85 @@
import {Linking, View} from 'react-native'
import {plural} from '@lingui/core/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {BSKY_DOWNLOAD_URL} from '#/lib/constants'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {Sparkle_Stroke2_Corner0_Rounded as Sparkle} from '#/components/icons/Sparkle'
import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env'
/**
* OTA-able fallback that ships to native builds which don't yet know how to
* render the new gallery embed (>4 images, Photos v2). Final copy and visual
* treatment pending design from Darrin/Danielle/Alex.
*
* Native-only per APP-2308 - web builds receive the new gallery support in
* the same release that adds it.
*/
export function GalleryFallbackEmbed({count}: {count?: number}) {
const t = useTheme()
const {t: l} = useLingui()
if (!IS_NATIVE) return null
const bodyStyle = [
a.text_sm,
a.text_center,
a.leading_snug,
t.atoms.text_contrast_high,
]
return (
<View
style={[
a.mt_sm,
a.rounded_md,
a.border,
a.p_lg,
a.gap_sm,
a.align_center,
{
borderColor: t.palette.primary_200,
backgroundColor: t.palette.primary_25,
},
]}>
<Sparkle size="lg" fill={t.palette.primary_500} />
<Text style={[a.text_md, a.font_bold, a.text_center, t.atoms.text]}>
<Trans>Something new is here</Trans>
</Text>
{count ? (
<View>
<Text style={bodyStyle}>
{plural(count, {
one: 'This post has # photo.',
other: 'This post has # photos.',
})}
</Text>
<Text style={bodyStyle}>
{plural(count, {
one: 'Update your app to see it.',
other: 'Update your app to see them all.',
})}
</Text>
</View>
) : (
<Text style={bodyStyle}>
<Trans>Update your app to see it.</Trans>
</Text>
)}
<Button
label={l`Update your app`}
size="small"
color="primary"
onPress={() => {
void Linking.openURL(BSKY_DOWNLOAD_URL)
}}
style={[a.mt_xs]}>
<ButtonText>
<Trans>Update app</Trans>
</ButtonText>
</Button>
</View>
)
}
+12 -1
View File
@@ -37,6 +37,7 @@ import {
import {ChatInviteEmbed} from './ChatInviteEmbed'
import {ExternalEmbed} from './ExternalEmbed'
import {ModeratedFeedEmbed} from './FeedEmbed'
import {GalleryFallbackEmbed} from './GalleryFallbackEmbed'
import {ImageEmbed} from './ImageEmbed'
import {ModeratedListEmbed} from './ListEmbed'
import {PostPlaceholder as PostPlaceholderText} from './PostPlaceholder'
@@ -55,7 +56,8 @@ export function Embed({embed: rawEmbed, ...rest}: EmbedProps) {
switch (embed.type) {
case 'images':
case 'link':
case 'video': {
case 'video':
case 'gallery': {
return <MediaEmbed embed={embed} {...rest} />
}
case 'feed':
@@ -148,6 +150,15 @@ function MediaEmbed({
</ContentHider>
)
}
case 'gallery': {
return (
<ContentHider
modui={rest.moderation?.ui('contentMedia')}
activeStyle={[a.mt_sm]}>
<GalleryFallbackEmbed count={embed.view.items.length} />
</ContentHider>
)
}
default: {
return null
}
+10
View File
@@ -1,6 +1,7 @@
import {
type $Typed,
AppBskyEmbedExternal,
AppBskyEmbedGallery,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
@@ -55,6 +56,10 @@ export type Embed =
type: 'video'
view: $Typed<AppBskyEmbedVideo.View>
}
| {
type: 'gallery'
view: $Typed<AppBskyEmbedGallery.View>
}
| {
type: 'post_with_media'
view: Embed
@@ -140,6 +145,11 @@ export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed {
view: parseEmbedRecordView(embed.record),
media: parseEmbed(embed.media),
}
} else if (AppBskyEmbedGallery.isView(embed)) {
return {
type: 'gallery',
view: embed,
}
} else {
return {
type: 'unknown',
@@ -0,0 +1,25 @@
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {GalleryFallbackEmbed} from '#/components/Post/Embed/GalleryFallbackEmbed'
import {H1, H3} from '#/components/Typography'
export function GalleryFallback() {
return (
<View style={[a.gap_md]}>
<H1>Gallery fallback (APP-2308)</H1>
<H3>No count</H3>
<GalleryFallbackEmbed />
<H3>1 photo</H3>
<GalleryFallbackEmbed count={1} />
<H3>5 photos</H3>
<GalleryFallbackEmbed count={5} />
<H3>10 photos</H3>
<GalleryFallbackEmbed count={10} />
</View>
)
}
+2
View File
@@ -16,6 +16,7 @@ import {Breakpoints} from './Breakpoints'
import {Buttons} from './Buttons'
import {Dialogs} from './Dialogs'
import {Forms} from './Forms'
import {GalleryFallback} from './GalleryFallback'
import {Icons} from './Icons'
import {Links} from './Links'
import {Menus} from './Menus'
@@ -120,6 +121,7 @@ export default function Storybook() {
<Breakpoints />
<Dialogs />
<Admonitions />
<GalleryFallback />
<Settings />
<Button