about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/strings/embed-player.ts100
-rw-r--r--src/view/com/notifications/FeedItem.tsx47
-rw-r--r--src/view/com/util/images/ImageHorzList.tsx57
3 files changed, 144 insertions, 60 deletions
diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts
index 30ced1492..44e42fae1 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 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,
-      }
+  const tenorGif = parseTenorGif(urlp)
+  if (tenorGif.success) {
+    const {playerUri, dimensions} = tenorGif
+
+    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 d6c38ea61..9cd7a2917 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 style={pal.textLight}>{text}</Text>}
         {images && images.length > 0 && (
-          <ImageHorzList images={images} style={styles.additionalPostImages} />
+          <ImageHorzList
+            images={images}
+            style={styles.additionalPostImages}
+            gif={isGif}
+          />
         )}
       </>
     )
diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx
index 12eef14f7..bade2a444 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<ViewStyle>
+  gif?: boolean
 }
 
-export function ImageHorzList({images, style}: Props) {
+export function ImageHorzList({images, style, gif}: Props) {
   return (
-    <View style={[styles.flexRow, style]}>
+    <View style={[a.flex_row, a.gap_xs, style]}>
       {images.map(({thumb, alt}) => (
-        <Image
+        <View
           key={thumb}
-          source={{uri: thumb}}
-          style={styles.image}
-          accessible={true}
-          accessibilityIgnoresInvertColors
-          accessibilityHint={alt}
-          accessibilityLabel=""
-        />
+          style={[a.relative, a.flex_1, {aspectRatio: 1, maxWidth: 100}]}>
+          <Image
+            key={thumb}
+            source={{uri: thumb}}
+            style={[a.flex_1, a.rounded_xs]}
+            accessible={true}
+            accessibilityIgnoresInvertColors
+            accessibilityHint={alt}
+            accessibilityLabel=""
+          />
+          {gif && (
+            <View style={styles.altContainer}>
+              <Text style={styles.alt}>
+                <Trans>GIF</Trans>
+              </Text>
+            </View>
+          )}
+        </View>
       ))}
     </View>
   )
 }
 
 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',
   },
 })