diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts
index 30ced149..44e42fae 100644
--- a/src/lib/strings/embed-player.ts
+++ b/src/lib/strings/embed-player.ts
@@ -1,7 +1,8 @@
-import {Dimensions, Platform} from 'react-native'
+import {Dimensions} from 'react-native'
import {isSafari} from 'lib/browser'
import {isWeb} from 'platform/detection'
+
const {height: SCREEN_HEIGHT} = Dimensions.get('window')
const IFRAME_HOST = isWeb
@@ -342,40 +343,17 @@ export function parseEmbedPlayerFromUrl(
}
}
- if (urlp.hostname === 'media.tenor.com') {
- let [_, id, filename] = urlp.pathname.split('/')
+ const tenorGif = parseTenorGif(urlp)
+ if (tenorGif.success) {
+ const {playerUri, dimensions} = tenorGif
- const h = urlp.searchParams.get('hh')
- const w = urlp.searchParams.get('ww')
- let dimensions
- if (h && w) {
- dimensions = {
- height: Number(h),
- width: Number(w),
- }
- }
-
- if (id && filename && dimensions && id.includes('AAAAC')) {
- if (Platform.OS === 'web') {
- if (isSafari) {
- id = id.replace('AAAAC', 'AAAP1')
- filename = filename.replace('.gif', '.mp4')
- } else {
- id = id.replace('AAAAC', 'AAAP3')
- filename = filename.replace('.gif', '.webm')
- }
- } else {
- id = id.replace('AAAAC', 'AAAAM')
- }
-
- return {
- type: 'tenor_gif',
- source: 'tenor',
- isGif: true,
- hideDetails: true,
- playerUri: `https://t.gifs.bsky.app/${id}/${filename}`,
- dimensions,
- }
+ return {
+ type: 'tenor_gif',
+ source: 'tenor',
+ isGif: true,
+ hideDetails: true,
+ playerUri,
+ dimensions,
}
}
@@ -516,3 +494,55 @@ export function getGiphyMetaUri(url: URL) {
}
}
}
+
+export function parseTenorGif(urlp: URL):
+ | {success: false}
+ | {
+ success: true
+ playerUri: string
+ dimensions: {height: number; width: number}
+ } {
+ if (urlp.hostname !== 'media.tenor.com') {
+ return {success: false}
+ }
+
+ let [_, id, filename] = urlp.pathname.split('/')
+
+ if (!id || !filename) {
+ return {success: false}
+ }
+
+ if (!id.includes('AAAAC')) {
+ return {success: false}
+ }
+
+ const h = urlp.searchParams.get('hh')
+ const w = urlp.searchParams.get('ww')
+
+ if (!h || !w) {
+ return {success: false}
+ }
+
+ const dimensions = {
+ height: Number(h),
+ width: Number(w),
+ }
+
+ if (isWeb) {
+ if (isSafari) {
+ id = id.replace('AAAAC', 'AAAP1')
+ filename = filename.replace('.gif', '.mp4')
+ } else {
+ id = id.replace('AAAAC', 'AAAP3')
+ filename = filename.replace('.gif', '.webm')
+ }
+ } else {
+ id = id.replace('AAAAC', 'AAAAM')
+ }
+
+ return {
+ success: true,
+ playerUri: `https://t.gifs.bsky.app/${id}/${filename}`,
+ dimensions,
+ }
+}
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index d6c38ea6..9cd7a291 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -8,6 +8,7 @@ import {
} from 'react-native'
import {
AppBskyActorDefs,
+ AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
@@ -51,6 +52,7 @@ import {TimeElapsed} from '../util/TimeElapsed'
import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
+import {parseTenorGif} from '#/lib/strings/embed-player'
const MAX_AUTHORS = 5
@@ -465,17 +467,48 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
const pal = usePalette('default')
if (post && AppBskyFeedPost.isRecord(post?.record)) {
const text = post.record.text
- const images = AppBskyEmbedImages.isView(post.embed)
- ? post.embed.images
- : AppBskyEmbedRecordWithMedia.isView(post.embed) &&
- AppBskyEmbedImages.isView(post.embed.media)
- ? post.embed.media.images
- : undefined
+ let images
+ let isGif = false
+
+ if (AppBskyEmbedImages.isView(post.embed)) {
+ images = post.embed.images
+ } else if (
+ AppBskyEmbedRecordWithMedia.isView(post.embed) &&
+ AppBskyEmbedImages.isView(post.embed.media)
+ ) {
+ images = post.embed.media.images
+ } else if (
+ AppBskyEmbedExternal.isView(post.embed) &&
+ post.embed.external.thumb
+ ) {
+ let url: URL | undefined
+ try {
+ url = new URL(post.embed.external.uri)
+ } catch {}
+ if (url) {
+ const {success} = parseTenorGif(url)
+ if (success) {
+ isGif = true
+ images = [
+ {
+ thumb: post.embed.external.thumb,
+ alt: post.embed.external.title,
+ fullsize: post.embed.external.thumb,
+ },
+ ]
+ }
+ }
+ }
+
return (
<>
{text?.length > 0 && {text}}
{images && images.length > 0 && (
-
+
)}
>
)
diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx
index 12eef14f..bade2a44 100644
--- a/src/view/com/util/images/ImageHorzList.tsx
+++ b/src/view/com/util/images/ImageHorzList.tsx
@@ -2,39 +2,60 @@ import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {Image} from 'expo-image'
import {AppBskyEmbedImages} from '@atproto/api'
+import {Trans} from '@lingui/macro'
+
+import {atoms as a} from '#/alf'
+import {Text} from '#/components/Typography'
interface Props {
images: AppBskyEmbedImages.ViewImage[]
style?: StyleProp
+ gif?: boolean
}
-export function ImageHorzList({images, style}: Props) {
+export function ImageHorzList({images, style, gif}: Props) {
return (
-
+
{images.map(({thumb, alt}) => (
-
+ style={[a.relative, a.flex_1, {aspectRatio: 1, maxWidth: 100}]}>
+
+ {gif && (
+
+
+ GIF
+
+
+ )}
+
))}
)
}
const styles = StyleSheet.create({
- flexRow: {
- flexDirection: 'row',
- gap: 5,
+ altContainer: {
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
+ borderRadius: 6,
+ paddingHorizontal: 6,
+ paddingVertical: 3,
+ position: 'absolute',
+ right: 5,
+ bottom: 5,
+ zIndex: 2,
},
- image: {
- maxWidth: 100,
- aspectRatio: 1,
- flex: 1,
- borderRadius: 4,
+ alt: {
+ color: 'white',
+ fontSize: 7,
+ fontWeight: 'bold',
},
})