about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/statsig/gates.ts1
-rw-r--r--src/lib/strings/embed-player.ts46
-rw-r--r--src/view/com/composer/Composer.tsx10
-rw-r--r--src/view/com/composer/ExternalEmbed.tsx70
-rw-r--r--src/view/com/util/post-embeds/ExternalLinkEmbed.tsx150
-rw-r--r--src/view/com/util/post-embeds/GifEmbed.tsx140
-rw-r--r--src/view/com/util/post-embeds/index.tsx52
7 files changed, 320 insertions, 149 deletions
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index c41083afb..84183c1d9 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -4,7 +4,6 @@ export type Gate =
   | 'disable_min_shell_on_foregrounding_v2'
   | 'disable_poll_on_discover_v2'
   | 'hide_vertical_scroll_indicators'
-  | 'new_gif_player'
   | 'show_follow_back_label_v2'
   | 'start_session_with_following_v2'
   | 'use_new_suggestions_endpoint'
diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts
index bbc58a206..b1fc75b8b 100644
--- a/src/lib/strings/embed-player.ts
+++ b/src/lib/strings/embed-player.ts
@@ -1,4 +1,4 @@
-import {Dimensions} from 'react-native'
+import {Dimensions, Platform} from 'react-native'
 
 import {isWeb} from 'platform/detection'
 const {height: SCREEN_HEIGHT} = Dimensions.get('window')
@@ -255,16 +255,6 @@ export function parseEmbedPlayerFromUrl(
   if (urlp.hostname === 'giphy.com' || urlp.hostname === 'www.giphy.com') {
     const [_, gifs, nameAndId] = 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),
-      }
-    }
-
     /*
      * nameAndId is a string that consists of the name (dash separated) and the id of the gif (the last part of the name)
      * We want to get the id of the gif, then direct to media.giphy.com/media/{id}/giphy.webp so we can
@@ -281,10 +271,7 @@ export function parseEmbedPlayerFromUrl(
           isGif: true,
           hideDetails: true,
           metaUri: `https://giphy.com/gifs/${gifId}`,
-          playerUri: `https://i.giphy.com/media/${gifId}/${
-            dimensions ? '200.mp4' : '200.webp'
-          }`,
-          dimensions,
+          playerUri: `https://i.giphy.com/media/${gifId}/200.webp`,
         }
       }
     }
@@ -350,21 +337,34 @@ export function parseEmbedPlayerFromUrl(
     }
   }
 
-  if (urlp.hostname === 'tenor.com' || urlp.hostname === 'www.tenor.com') {
-    const [_, pathOrIntl, pathOrFilename, intlFilename] =
-      urlp.pathname.split('/')
-    const isIntl = pathOrFilename === 'view'
-    const filename = isIntl ? intlFilename : pathOrFilename
+  if (urlp.hostname === 'media.tenor.com') {
+    let [_, id, filename] = urlp.pathname.split('/')
 
-    if ((pathOrIntl === 'view' || pathOrFilename === 'view') && filename) {
-      const includesExt = filename.split('.').pop() === 'gif'
+    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') {
+        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: `${url}${!includesExt ? '.gif' : ''}`,
+        playerUri: `https://t.gifs.bsky.app/${id}/${filename}`,
+        dimensions,
       }
     }
   }
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 93e2dc6b5..8d14c16e2 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -121,6 +121,7 @@ export const ComposePost = observer(function ComposePost({
     initQuote,
   )
   const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
+  const [extGif, setExtGif] = useState<Gif>()
   const [labels, setLabels] = useState<string[]>([])
   const [threadgate, setThreadgate] = useState<ThreadgateSetting[]>([])
   const gallery = useMemo(
@@ -318,7 +319,7 @@ export const ComposePost = observer(function ComposePost({
   const onSelectGif = useCallback(
     (gif: Gif) => {
       setExtLink({
-        uri: `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[0]}&ww=${gif.media_formats.gif.dims[1]}`,
+        uri: `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[1]}&ww=${gif.media_formats.gif.dims[0]}`,
         isLoading: true,
         meta: {
           url: gif.media_formats.gif.url,
@@ -328,6 +329,7 @@ export const ComposePost = observer(function ComposePost({
           description: `ALT: ${gif.content_description}`,
         },
       })
+      setExtGif(gif)
     },
     [setExtLink],
   )
@@ -473,7 +475,11 @@ export const ComposePost = observer(function ComposePost({
           {gallery.isEmpty && extLink && (
             <ExternalEmbed
               link={extLink}
-              onRemove={() => setExtLink(undefined)}
+              gif={extGif}
+              onRemove={() => {
+                setExtLink(undefined)
+                setExtGif(undefined)
+              }}
             />
           )}
           {quote ? (
diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx
index 3c2bf762d..321e29b30 100644
--- a/src/view/com/composer/ExternalEmbed.tsx
+++ b/src/view/com/composer/ExternalEmbed.tsx
@@ -1,11 +1,12 @@
 import React from 'react'
-import {TouchableOpacity, View} from 'react-native'
+import {StyleProp, TouchableOpacity, View, ViewStyle} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {ExternalEmbedDraft} from 'lib/api/index'
 import {s} from 'lib/styles'
+import {Gif} from 'state/queries/tenor'
 import {ExternalLinkEmbed} from 'view/com/util/post-embeds/ExternalLinkEmbed'
 import {atoms as a, useTheme} from '#/alf'
 import {Loader} from '#/components/Loader'
@@ -14,9 +15,11 @@ import {Text} from '#/components/Typography'
 export const ExternalEmbed = ({
   link,
   onRemove,
+  gif,
 }: {
   link?: ExternalEmbedDraft
   onRemove: () => void
+  gif?: Gif
 }) => {
   const t = useTheme()
   const {_} = useLingui()
@@ -34,45 +37,38 @@ export const ExternalEmbed = ({
 
   if (!link) return null
 
+  const loadingStyle: ViewStyle | undefined = gif
+    ? {
+        aspectRatio:
+          gif.media_formats.gif.dims[0] / gif.media_formats.gif.dims[1],
+        width: '100%',
+      }
+    : undefined
+
   return (
-    <View
-      style={[
-        a.border,
-        a.rounded_sm,
-        a.mt_2xl,
-        a.mb_xl,
-        a.overflow_hidden,
-        t.atoms.border_contrast_medium,
-      ]}>
+    <View style={[a.mb_xl, a.overflow_hidden, t.atoms.border_contrast_medium]}>
       {link.isLoading ? (
-        <View
-          style={[
-            a.align_center,
-            a.justify_center,
-            a.py_5xl,
-            t.atoms.bg_contrast_25,
-          ]}>
+        <Container style={loadingStyle}>
           <Loader size="xl" />
-        </View>
+        </Container>
       ) : link.meta?.error ? (
-        <View
-          style={[a.justify_center, a.p_md, a.gap_xs, t.atoms.bg_contrast_25]}>
+        <Container style={[a.align_start, a.p_md, a.gap_xs]}>
           <Text numberOfLines={1} style={t.atoms.text_contrast_high}>
             {link.uri}
           </Text>
           <Text numberOfLines={2} style={[{color: t.palette.negative_400}]}>
-            {link.meta.error}
+            {link.meta?.error}
           </Text>
-        </View>
+        </Container>
       ) : linkInfo ? (
-        <View style={{pointerEvents: 'none'}}>
+        <View style={{pointerEvents: !gif ? 'none' : 'auto'}}>
           <ExternalLinkEmbed link={linkInfo} />
         </View>
       ) : null}
       <TouchableOpacity
         style={{
           position: 'absolute',
-          top: 10,
+          top: 16,
           right: 10,
           height: 36,
           width: 36,
@@ -91,3 +87,29 @@ export const ExternalEmbed = ({
     </View>
   )
 }
+
+function Container({
+  style,
+  children,
+}: {
+  style?: StyleProp<ViewStyle>
+  children: React.ReactNode
+}) {
+  const t = useTheme()
+  return (
+    <View
+      style={[
+        a.mt_sm,
+        a.rounded_sm,
+        a.border,
+        a.align_center,
+        a.justify_center,
+        a.py_5xl,
+        t.atoms.bg_contrast_25,
+        t.atoms.border_contrast_medium,
+        style,
+      ]}>
+      {children}
+    </View>
+  )
+}
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
index ff7c643f6..1fe75c44e 100644
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
@@ -1,27 +1,32 @@
-import React from 'react'
-import {StyleSheet, View} from 'react-native'
+import React, {useCallback} from 'react'
+import {StyleProp, View, ViewStyle} from 'react-native'
 import {Image} from 'expo-image'
 import {AppBskyEmbedExternal} from '@atproto/api'
 
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {useGate} from 'lib/statsig/statsig'
+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 {useExternalEmbedsPrefs} from 'state/preferences'
+import {Link} from 'view/com/util/Link'
 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 {Text} from '../text/Text'
 
 export const ExternalLinkEmbed = ({
   link,
+  style,
 }: {
   link: AppBskyEmbedExternal.ViewExternal
+  style?: StyleProp<ViewStyle>
 }) => {
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()
   const externalEmbedPrefs = useExternalEmbedsPrefs()
-  const gate = useGate()
 
   const embedPlayerParams = React.useMemo(() => {
     const params = parseEmbedPlayerFromUrl(link.uri)
@@ -30,71 +35,96 @@ export const ExternalLinkEmbed = ({
       return params
     }
   }, [link.uri, externalEmbedPrefs])
-  const isCompatibleGiphy =
-    embedPlayerParams?.source === 'giphy' &&
-    embedPlayerParams.dimensions &&
-    gate('new_gif_player')
+
+  if (embedPlayerParams?.source === 'tenor') {
+    return <GifEmbed params={embedPlayerParams} link={link} />
+  }
 
   return (
-    <View style={styles.container}>
-      {link.thumb && !embedPlayerParams ? (
-        <Image
-          style={{aspectRatio: 1.91}}
-          source={{uri: link.thumb}}
-          accessibilityIgnoresInvertColors
-        />
-      ) : undefined}
-      {isCompatibleGiphy ? (
-        <View />
-      ) : embedPlayerParams?.isGif ? (
-        <ExternalGifEmbed link={link} params={embedPlayerParams} />
-      ) : embedPlayerParams ? (
-        <ExternalPlayer link={link} params={embedPlayerParams} />
-      ) : undefined}
-      <View style={[styles.info, {paddingHorizontal: isMobile ? 10 : 14}]}>
-        {!isCompatibleGiphy && (
+    <View style={[a.flex_col, a.rounded_sm, a.overflow_hidden, a.mt_sm]}>
+      <LinkWrapper link={link} style={style}>
+        {link.thumb && !embedPlayerParams ? (
+          <Image
+            style={{
+              aspectRatio: 1.91,
+              borderTopRightRadius: 6,
+              borderTopLeftRadius: 6,
+            }}
+            source={{uri: link.thumb}}
+            accessibilityIgnoresInvertColors
+          />
+        ) : undefined}
+        {embedPlayerParams?.isGif ? (
+          <ExternalGifEmbed link={link} params={embedPlayerParams} />
+        ) : embedPlayerParams ? (
+          <ExternalPlayer link={link} params={embedPlayerParams} />
+        ) : undefined}
+        <View
+          style={[
+            a.flex_1,
+            a.py_sm,
+            {
+              paddingHorizontal: isMobile ? 10 : 14,
+            },
+          ]}>
           <Text
             type="sm"
             numberOfLines={1}
-            style={[pal.textLight, styles.extUri]}>
+            style={[pal.textLight, {marginVertical: 2}]}>
             {toNiceDomain(link.uri)}
           </Text>
-        )}
 
-        {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
-          <Text type="lg-bold" numberOfLines={3} style={[pal.text]}>
-            {link.title || link.uri}
-          </Text>
-        )}
-        {link.description && !embedPlayerParams?.hideDetails ? (
-          <Text
-            type="md"
-            numberOfLines={link.thumb ? 2 : 4}
-            style={[pal.text, styles.extDescription]}>
-            {link.description}
-          </Text>
-        ) : undefined}
-      </View>
+          {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
+            <Text type="lg-bold" numberOfLines={3} style={[pal.text]}>
+              {link.title || link.uri}
+            </Text>
+          )}
+          {link.description ? (
+            <Text
+              type="md"
+              numberOfLines={link.thumb ? 2 : 4}
+              style={[pal.text, a.mt_xs]}>
+              {link.description}
+            </Text>
+          ) : undefined}
+        </View>
+      </LinkWrapper>
     </View>
   )
 }
 
-const styles = StyleSheet.create({
-  container: {
-    flexDirection: 'column',
-    borderRadius: 6,
-    overflow: 'hidden',
-  },
-  info: {
-    width: '100%',
-    bottom: 0,
-    paddingTop: 8,
-    paddingBottom: 10,
-  },
-  extUri: {
-    marginTop: 2,
-  },
-  extDescription: {
-    marginTop: 4,
-  },
-})
+function LinkWrapper({
+  link,
+  style,
+  children,
+}: {
+  link: AppBskyEmbedExternal.ViewExternal
+  style?: StyleProp<ViewStyle>
+  children: React.ReactNode
+}) {
+  const t = useTheme()
+
+  const onShareExternal = useCallback(() => {
+    if (link.uri && isNative) {
+      shareUrl(link.uri)
+    }
+  }, [link.uri])
+
+  return (
+    <Link
+      asAnchor
+      anchorNoUnderline
+      href={link.uri}
+      style={[
+        a.flex_1,
+        a.border,
+        a.rounded_sm,
+        t.atoms.border_contrast_medium,
+        style,
+      ]}
+      hoverStyle={t.atoms.border_contrast_high}
+      onLongPress={onShareExternal}>
+      {children}
+    </Link>
+  )
+}
diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx
new file mode 100644
index 000000000..32bd75df0
--- /dev/null
+++ b/src/view/com/util/post-embeds/GifEmbed.tsx
@@ -0,0 +1,140 @@
+import React from 'react'
+import {Pressable, View} from 'react-native'
+import {AppBskyEmbedExternal} from '@atproto/api'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {EmbedPlayerParams} from 'lib/strings/embed-player'
+import {useAutoplayDisabled} from 'state/preferences'
+import {atoms as a, useTheme} from '#/alf'
+import {Loader} from '#/components/Loader'
+import {GifView} from '../../../../../modules/expo-bluesky-gif-view'
+import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
+
+function PlaybackControls({
+  onPress,
+  isPlaying,
+  isLoaded,
+}: {
+  onPress: () => void
+  isPlaying: boolean
+  isLoaded: boolean
+}) {
+  const {_} = useLingui()
+  const t = useTheme()
+
+  return (
+    <Pressable
+      accessibilityRole="button"
+      accessibilityHint={_(msg`Play or pause the GIF`)}
+      accessibilityLabel={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
+      style={[
+        a.absolute,
+        a.align_center,
+        a.justify_center,
+        !isLoaded && a.border,
+        t.atoms.border_contrast_medium,
+        a.inset_0,
+        a.w_full,
+        a.h_full,
+        {
+          zIndex: 2,
+          backgroundColor: !isLoaded
+            ? t.atoms.bg_contrast_25.backgroundColor
+            : !isPlaying
+            ? 'rgba(0, 0, 0, 0.3)'
+            : undefined,
+        },
+      ]}
+      onPress={onPress}>
+      {!isLoaded ? (
+        <View>
+          <View style={[a.align_center, a.justify_center]}>
+            <Loader size="xl" />
+          </View>
+        </View>
+      ) : !isPlaying ? (
+        <View
+          style={[
+            a.rounded_full,
+            a.align_center,
+            a.justify_center,
+            {
+              backgroundColor: t.palette.primary_500,
+              width: 60,
+              height: 60,
+            },
+          ]}>
+          <FontAwesomeIcon
+            icon="play"
+            size={42}
+            color="white"
+            style={{marginLeft: 8}}
+          />
+        </View>
+      ) : undefined}
+    </Pressable>
+  )
+}
+
+export function GifEmbed({
+  params,
+  link,
+}: {
+  params: EmbedPlayerParams
+  link: AppBskyEmbedExternal.ViewExternal
+}) {
+  const {_} = useLingui()
+  const autoplayDisabled = useAutoplayDisabled()
+
+  const playerRef = React.useRef<GifView>(null)
+
+  const [playerState, setPlayerState] = React.useState<{
+    isPlaying: boolean
+    isLoaded: boolean
+  }>({
+    isPlaying: !autoplayDisabled,
+    isLoaded: false,
+  })
+
+  const onPlayerStateChange = React.useCallback(
+    (e: GifViewStateChangeEvent) => {
+      setPlayerState(e.nativeEvent)
+    },
+    [],
+  )
+
+  const onPress = React.useCallback(() => {
+    playerRef.current?.toggleAsync()
+  }, [])
+
+  return (
+    <View style={[a.rounded_sm, a.overflow_hidden, a.mt_sm]}>
+      <View
+        style={[
+          a.rounded_sm,
+          a.overflow_hidden,
+          {
+            aspectRatio: params.dimensions!.width / params.dimensions!.height,
+          },
+        ]}>
+        <PlaybackControls
+          onPress={onPress}
+          isPlaying={playerState.isPlaying}
+          isLoaded={playerState.isLoaded}
+        />
+        <GifView
+          source={params.playerUri}
+          placeholderSource={link.thumb}
+          style={[a.flex_1, a.rounded_sm]}
+          autoplay={!autoplayDisabled}
+          onPlayerStateChange={onPlayerStateChange}
+          ref={playerRef}
+          accessibilityHint={_(msg`Animated GIF`)}
+          accessibilityLabel={link.description.replace('ALT: ', '')}
+        />
+      </View>
+    </View>
+  )
+}
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index 47091fbb0..7ea5b55cf 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -1,34 +1,32 @@
-import React, {useCallback} from 'react'
+import React from 'react'
 import {
-  StyleSheet,
+  InteractionManager,
   StyleProp,
+  StyleSheet,
+  Text,
   View,
   ViewStyle,
-  Text,
-  InteractionManager,
 } from 'react-native'
 import {Image} from 'expo-image'
 import {
-  AppBskyEmbedImages,
   AppBskyEmbedExternal,
+  AppBskyEmbedImages,
   AppBskyEmbedRecord,
   AppBskyEmbedRecordWithMedia,
   AppBskyFeedDefs,
   AppBskyGraphDefs,
   ModerationDecision,
 } from '@atproto/api'
-import {Link} from '../Link'
-import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
-import {useLightboxControls, ImagesLightbox} from '#/state/lightbox'
+
+import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
 import {usePalette} from 'lib/hooks/usePalette'
-import {ExternalLinkEmbed} from './ExternalLinkEmbed'
-import {MaybeQuoteEmbed} from './QuoteEmbed'
-import {AutoSizedImage} from '../images/AutoSizedImage'
-import {ListEmbed} from './ListEmbed'
 import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
 import {ContentHider} from '../../../../components/moderation/ContentHider'
-import {isNative} from '#/platform/detection'
-import {shareUrl} from '#/lib/sharing'
+import {AutoSizedImage} from '../images/AutoSizedImage'
+import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
+import {ExternalLinkEmbed} from './ExternalLinkEmbed'
+import {ListEmbed} from './ListEmbed'
+import {MaybeQuoteEmbed} from './QuoteEmbed'
 
 type Embed =
   | AppBskyEmbedRecord.View
@@ -49,16 +47,6 @@ export function PostEmbeds({
   const pal = usePalette('default')
   const {openLightbox} = useLightboxControls()
 
-  const externalUri = AppBskyEmbedExternal.isView(embed)
-    ? embed.external.uri
-    : null
-
-  const onShareExternal = useCallback(() => {
-    if (externalUri && isNative) {
-      shareUrl(externalUri)
-    }
-  }, [externalUri])
-
   // quote post with media
   // =
   if (AppBskyEmbedRecordWithMedia.isView(embed)) {
@@ -161,18 +149,9 @@ export function PostEmbeds({
   // =
   if (AppBskyEmbedExternal.isView(embed)) {
     const link = embed.external
-
     return (
       <ContentHider modui={moderation?.ui('contentMedia')}>
-        <Link
-          asAnchor
-          anchorNoUnderline
-          href={link.uri}
-          style={[styles.extOuter, pal.view, pal.borderDark, style]}
-          hoverStyle={{borderColor: pal.colors.borderLinkHover}}
-          onLongPress={onShareExternal}>
-          <ExternalLinkEmbed link={link} />
-        </Link>
+        <ExternalLinkEmbed link={link} style={style} />
       </ContentHider>
     )
   }
@@ -187,11 +166,6 @@ const styles = StyleSheet.create({
   singleImage: {
     borderRadius: 8,
   },
-  extOuter: {
-    borderWidth: 1,
-    borderRadius: 8,
-    marginTop: 4,
-  },
   altContainer: {
     backgroundColor: 'rgba(0, 0, 0, 0.75)',
     borderRadius: 6,