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:
+1
-1
@@ -93,7 +93,7 @@
|
|||||||
"prettier": "prettier --check ."
|
"prettier": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.20.8",
|
"@atproto/api": "0.20.9",
|
||||||
"@atproto/syntax": "0.6.1",
|
"@atproto/syntax": "0.6.1",
|
||||||
"@bitdrift/react-native": "^0.6.8",
|
"@bitdrift/react-native": "^0.6.8",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
|
|||||||
Generated
+5
-5
@@ -242,8 +242,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@atproto/api':
|
'@atproto/api':
|
||||||
specifier: 0.20.8
|
specifier: 0.20.9
|
||||||
version: 0.20.8
|
version: 0.20.9
|
||||||
'@atproto/syntax':
|
'@atproto/syntax':
|
||||||
specifier: 0.6.1
|
specifier: 0.6.1
|
||||||
version: 0.6.1
|
version: 0.6.1
|
||||||
@@ -877,8 +877,8 @@ packages:
|
|||||||
graphql:
|
graphql:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@atproto/api@0.20.8':
|
'@atproto/api@0.20.9':
|
||||||
resolution: {integrity: sha512-rTkA6kOmA2axSrg6VgpdXpsCFWpofnHBOn6pKg69Ju5MpIHqk4haQMgjBcVh1G3kUxzwgSAr7SYrPS3dFe5Etg==}
|
resolution: {integrity: sha512-Yuw7Ewn+yMJZ8GskbuvI3lKPW65rsXic1xjFA2Dpq6H8WjVYs6xNZ31bkwtTYDDwjKIZcJmAVbAVgdfjo4T9iw==}
|
||||||
engines: {node: '>=22'}
|
engines: {node: '>=22'}
|
||||||
|
|
||||||
'@atproto/common-web@0.5.0':
|
'@atproto/common-web@0.5.0':
|
||||||
@@ -9493,7 +9493,7 @@ snapshots:
|
|||||||
|
|
||||||
'@0no-co/graphql.web@1.2.0': {}
|
'@0no-co/graphql.web@1.2.0': {}
|
||||||
|
|
||||||
'@atproto/api@0.20.8':
|
'@atproto/api@0.20.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@atproto/common-web': 0.5.0
|
'@atproto/common-web': 0.5.0
|
||||||
'@atproto/lexicon': 0.7.1
|
'@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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
import {ChatInviteEmbed} from './ChatInviteEmbed'
|
import {ChatInviteEmbed} from './ChatInviteEmbed'
|
||||||
import {ExternalEmbed} from './ExternalEmbed'
|
import {ExternalEmbed} from './ExternalEmbed'
|
||||||
import {ModeratedFeedEmbed} from './FeedEmbed'
|
import {ModeratedFeedEmbed} from './FeedEmbed'
|
||||||
|
import {GalleryFallbackEmbed} from './GalleryFallbackEmbed'
|
||||||
import {ImageEmbed} from './ImageEmbed'
|
import {ImageEmbed} from './ImageEmbed'
|
||||||
import {ModeratedListEmbed} from './ListEmbed'
|
import {ModeratedListEmbed} from './ListEmbed'
|
||||||
import {PostPlaceholder as PostPlaceholderText} from './PostPlaceholder'
|
import {PostPlaceholder as PostPlaceholderText} from './PostPlaceholder'
|
||||||
@@ -55,7 +56,8 @@ export function Embed({embed: rawEmbed, ...rest}: EmbedProps) {
|
|||||||
switch (embed.type) {
|
switch (embed.type) {
|
||||||
case 'images':
|
case 'images':
|
||||||
case 'link':
|
case 'link':
|
||||||
case 'video': {
|
case 'video':
|
||||||
|
case 'gallery': {
|
||||||
return <MediaEmbed embed={embed} {...rest} />
|
return <MediaEmbed embed={embed} {...rest} />
|
||||||
}
|
}
|
||||||
case 'feed':
|
case 'feed':
|
||||||
@@ -148,6 +150,15 @@ function MediaEmbed({
|
|||||||
</ContentHider>
|
</ContentHider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case 'gallery': {
|
||||||
|
return (
|
||||||
|
<ContentHider
|
||||||
|
modui={rest.moderation?.ui('contentMedia')}
|
||||||
|
activeStyle={[a.mt_sm]}>
|
||||||
|
<GalleryFallbackEmbed count={embed.view.items.length} />
|
||||||
|
</ContentHider>
|
||||||
|
)
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
type $Typed,
|
type $Typed,
|
||||||
AppBskyEmbedExternal,
|
AppBskyEmbedExternal,
|
||||||
|
AppBskyEmbedGallery,
|
||||||
AppBskyEmbedImages,
|
AppBskyEmbedImages,
|
||||||
AppBskyEmbedRecord,
|
AppBskyEmbedRecord,
|
||||||
AppBskyEmbedRecordWithMedia,
|
AppBskyEmbedRecordWithMedia,
|
||||||
@@ -55,6 +56,10 @@ export type Embed =
|
|||||||
type: 'video'
|
type: 'video'
|
||||||
view: $Typed<AppBskyEmbedVideo.View>
|
view: $Typed<AppBskyEmbedVideo.View>
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'gallery'
|
||||||
|
view: $Typed<AppBskyEmbedGallery.View>
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'post_with_media'
|
type: 'post_with_media'
|
||||||
view: Embed
|
view: Embed
|
||||||
@@ -140,6 +145,11 @@ export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed {
|
|||||||
view: parseEmbedRecordView(embed.record),
|
view: parseEmbedRecordView(embed.record),
|
||||||
media: parseEmbed(embed.media),
|
media: parseEmbed(embed.media),
|
||||||
}
|
}
|
||||||
|
} else if (AppBskyEmbedGallery.isView(embed)) {
|
||||||
|
return {
|
||||||
|
type: 'gallery',
|
||||||
|
view: embed,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'unknown',
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import {Breakpoints} from './Breakpoints'
|
|||||||
import {Buttons} from './Buttons'
|
import {Buttons} from './Buttons'
|
||||||
import {Dialogs} from './Dialogs'
|
import {Dialogs} from './Dialogs'
|
||||||
import {Forms} from './Forms'
|
import {Forms} from './Forms'
|
||||||
|
import {GalleryFallback} from './GalleryFallback'
|
||||||
import {Icons} from './Icons'
|
import {Icons} from './Icons'
|
||||||
import {Links} from './Links'
|
import {Links} from './Links'
|
||||||
import {Menus} from './Menus'
|
import {Menus} from './Menus'
|
||||||
@@ -120,6 +121,7 @@ export default function Storybook() {
|
|||||||
<Breakpoints />
|
<Breakpoints />
|
||||||
<Dialogs />
|
<Dialogs />
|
||||||
<Admonitions />
|
<Admonitions />
|
||||||
|
<GalleryFallback />
|
||||||
<Settings />
|
<Settings />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user